feat(docs): 添加开发环境能力清单及相关工具脚本

- 在贡献指南中添加开发环境能力清单链接和说明
- 创建详细的开发环境能力清单文档,记录项目所需的运行时和工具
- 添加 .ai/environment/tools.ai.yaml 文件,为 AI 提供精简的环境能力信息
- 添加 .ai/environment/tools.raw.yaml 文件,存储完整的环境检测数据
- 创建 collect-dev-environment.sh 脚本,用于收集和输出环境信息
- 创建 generate-ai-environment.py 脚本,从原始数据生成 AI 友好的环境清单
- 在 .gitignore 中添加 .venv/ 工具目录忽略规则
This commit is contained in:
GeWuYou 2026-03-21 12:37:56 +08:00
parent ba4ee24ce7
commit 3130a3bab2
8 changed files with 763 additions and 1 deletions

View File

@ -0,0 +1,62 @@
schema_version: 1
generated_at_utc: "2026-03-21T04:01:27Z"
generated_from: ".ai/environment/tools.raw.yaml"
generator: "scripts/generate-ai-environment.py"
platform:
family: "wsl-linux"
os: "Linux"
distro: "Ubuntu 24.04.4 LTS"
shell: "bash"
capabilities:
dotnet: true
python: true
node: true
bun: true
docker: true
fast_search: true
json_cli: true
tool_selection:
search:
preferred: "rg"
fallback: "grep"
use_for: "Repository text search."
json:
preferred: "jq"
fallback: "python3"
use_for: "Inspecting or transforming JSON command output."
shell:
preferred: "bash"
fallback: "sh"
use_for: "Repository shell scripts and command execution."
scripting:
preferred: "python3"
fallback: "bash"
use_for: "Non-trivial local automation and helper scripts."
docs_package_manager:
preferred: "bun"
fallback: "npm"
use_for: "Installing and previewing the docs site."
build_and_test:
preferred: "dotnet"
fallback: "unavailable"
use_for: "Build, test, restore, and solution validation."
python:
available: true
helper_packages:
requests: true
rich: true
openai: false
tiktoken: false
pydantic: false
pytest: false
preferences:
prefer_project_listed_tools: true
prefer_python_for_non_trivial_automation: true
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."

View File

@ -0,0 +1,89 @@
schema_version: 1
generated_at_utc: "2026-03-21T04:00:19Z"
generator: "scripts/collect-dev-environment.sh"
platform:
os: "Linux"
distro: "Ubuntu 24.04.4 LTS"
version: "24.04"
kernel: "5.15.167.4-microsoft-standard-WSL2"
wsl: true
wsl_version: "2.4.13"
shell: "bash"
required_runtimes:
dotnet:
installed: true
version: "10.0.104"
path: "/usr/bin/dotnet"
purpose: "Builds and tests the GFramework solution."
python3:
installed: true
version: "Python 3.12.3"
path: "/usr/bin/python3"
purpose: "Runs local automation and environment collection scripts."
node:
installed: true
version: "v20.20.1"
path: "/usr/bin/node"
purpose: "Provides the JavaScript runtime used by docs tooling."
bun:
installed: true
version: "1.3.10"
path: "/root/.bun/bin/bun"
purpose: "Installs and previews the VitePress documentation site."
required_tools:
git:
installed: true
version: "git version 2.43.0"
path: "/usr/bin/git"
purpose: "Source control and patch review."
bash:
installed: true
version: "GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)"
path: "/usr/bin/bash"
purpose: "Executes repository scripts and shell automation."
rg:
installed: true
version: "ripgrep 15.1.0 (rev af60c2de9d)"
path: "/root/.bun/install/global/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/path/rg"
purpose: "Fast text search across the repository."
jq:
installed: true
version: "jq-1.7"
path: "/usr/bin/jq"
purpose: "Inspecting and transforming JSON outputs."
project_tools:
docker:
installed: true
version: "Docker version 29.2.1, build a5c7197"
path: "/usr/bin/docker"
purpose: "Runs MegaLinter and other containerized validation tools."
python_packages:
requests:
installed: true
version: "2.31.0"
purpose: "Simple HTTP calls in local helper scripts."
rich:
installed: true
version: "13.7.1"
purpose: "Readable CLI output for local Python helpers."
openai:
installed: false
version: "not-installed"
purpose: "Optional scripted access to OpenAI APIs."
tiktoken:
installed: false
version: "not-installed"
purpose: "Optional token counting for prompt and context inspection."
pydantic:
installed: false
version: "not-installed"
purpose: "Optional typed config and schema validation for helper scripts."
pytest:
installed: false
version: "not-installed"
purpose: "Optional lightweight testing for Python helper scripts."

4
.gitignore vendored
View File

@ -12,4 +12,6 @@ opencode.json
.omc/ .omc/
docs/.omc/ docs/.omc/
docs/.vitepress/cache/ docs/.vitepress/cache/
local-plan/ local-plan/
# tool
.venv/

View File

@ -4,6 +4,13 @@ This document is the single source of truth for coding behavior in this reposito
All AI agents and contributors must follow these rules when writing, reviewing, or modifying code in `GFramework`. All AI agents and contributors must follow these rules when writing, reviewing, or modifying code in `GFramework`.
## Environment Capability Inventory
- Before choosing runtimes or CLI tools, read `@.ai/environment/tools.ai.yaml`.
- Use `@.ai/environment/tools.raw.yaml` only when you need the full collected facts behind the AI-facing hints.
- Prefer the project-relevant tools listed there instead of assuming every installed system tool is fair game.
- If the real environment differs from the inventory, use the project-relevant installed tool and report the mismatch.
## Commenting Rules (MUST) ## Commenting Rules (MUST)
All generated or modified code MUST include clear and meaningful comments where required by the rules below. All generated or modified code MUST include clear and meaningful comments where required by the rules below.

View File

@ -118,6 +118,10 @@ GFramework 是一个开源的游戏开发框架,我们欢迎所有形式的贡
## 开发环境设置 ## 开发环境设置
当前推荐的项目相关环境、CLI 与 AI 可用工具清单请查看:
- [开发环境能力清单](./contributor/development-environment.md)
### 前置要求 ### 前置要求
- **.NET SDK**8.0、9.0 或 10.0 - **.NET SDK**8.0、9.0 或 10.0

View File

@ -0,0 +1,96 @@
# 开发环境能力清单
这份文档只记录对 `GFramework` 当前开发和 AI 协作真正有用的环境能力,不收录与本项目无关的系统工具。
如果某个工具没有出现在这里默认表示它对当前仓库不是必需项AI 也不应因为“系统里刚好装了”就优先使用它。
## 当前环境基线
当前仓库验证基线是:
- **运行环境**WSL2
- **发行版**Ubuntu 24.04 LTS
- **Shell**`bash`
机器可读的环境数据分成两层:
- `GFramework/.ai/environment/tools.raw.yaml`:完整事实采集
- `GFramework/.ai/environment/tools.ai.yaml`:给 AI 看的精简决策提示
AI 应优先读取 `tools.ai.yaml`,只有在需要追溯完整事实时才查看 `tools.raw.yaml`
## 当前项目需要的运行时
| 工具 | 是否需要 | 在 GFramework 中的用途 |
|-----------|------|---------------------------------|
| `dotnet` | 必需 | 构建、测试、打包整个解决方案 |
| `python3` | 推荐 | 运行本地辅助脚本、环境采集和轻量自动化 |
| `node` | 推荐 | 作为文档工具链的 JavaScript 运行时 |
| `bun` | 推荐 | 安装并预览 `docs/` 下的 VitePress 文档站点 |
## 当前项目需要的命令行工具
| 工具 | 是否需要 | 在 GFramework 中的用途 |
|----------|------|-----------------------------------------------|
| `git` | 必需 | 提交代码、查看 diff、审查变更 |
| `bash` | 必需 | 执行仓库脚本,例如 `scripts/validate-csharp-naming.sh` |
| `rg` | 必需 | 在仓库中快速搜索代码和文档 |
| `jq` | 推荐 | 处理 JSON 输出,便于本地脚本和 AI 做结构化检查 |
| `docker` | 可选 | 运行 MegaLinter 等容器化检查工具 |
这里只保留和当前仓库直接相关的 CLI。像 `kubectl``terraform``helm``java`、数据库客户端等工具,即使系统已安装,也不进入正式清单。
## Python 包
Python 包只记录两类内容:
- 当前环境里已经存在、对开发辅助有价值的包
- 明确对 AI/脚本化开发有帮助、后续可能会安装的包
| 包 | 当前状态 | 用途 |
|------------|---------|---------------------|
| `requests` | 当前环境已安装 | 用于简单 HTTP 调用和脚本集成 |
| `rich` | 当前环境已安装 | 用于更易读的终端输出 |
| `openai` | 当前环境可选 | 用于脚本化调用 OpenAI API |
| `tiktoken` | 当前环境可选 | 用于 token 估算和上下文检查 |
| `pydantic` | 当前环境可选 | 用于结构化配置和模式校验 |
| `pytest` | 当前环境可选 | 用于 Python 辅助脚本的小型测试 |
如果某个 Python 包与当前仓库没有直接关系,就不要加入清单。
## AI 使用约定
AI 在这个仓库里应优先使用:
- `rg` 做文本搜索
- `jq` 做 JSON 检查
- `bash` 执行仓库脚本
- `dotnet` 做构建和测试
- `bun` 做文档预览
- `python3 + requests` 做轻量本地辅助脚本
AI 不应直接把原始探测数据当成决策规则;应以 `tools.ai.yaml` 中的推荐和 fallback 为准。如果确实需要引入新工具,应先更新环境清单,再在任务中使用。
## 如何刷新环境清单
使用仓库脚本先采集原始环境,再生成 AI 版本:
```bash
# 输出原始环境清单到终端
bash scripts/collect-dev-environment.sh --check
# 写回原始清单
bash scripts/collect-dev-environment.sh --write
# 由原始清单生成 AI 决策清单
python3 scripts/generate-ai-environment.py
```
## 维护规则
- 目标不是记录“这台机器装了什么”而是记录“GFramework 开发和 AI 协作实际该用什么”。
- 新工具只有在满足以下条件之一时才应加入清单:
- 当前仓库构建、测试、文档或验证直接依赖它
- AI 在当前仓库中会高频使用,且能明显提升效率
- 新贡献者配置当前仓库开发环境时确实需要知道它
- 不满足上述条件的工具,不写入文档,也不写入 `.ai/environment/tools.raw.yaml` / `.ai/environment/tools.ai.yaml`

View File

@ -0,0 +1,278 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
OUTPUT_PATH="${ROOT_DIR}/.ai/environment/tools.raw.yaml"
MODE="${1:---check}"
usage() {
cat <<'EOF'
Usage:
bash scripts/collect-dev-environment.sh --check
bash scripts/collect-dev-environment.sh --write
Modes:
--check Print the raw project-relevant environment inventory.
--write Write the raw inventory to .ai/environment/tools.raw.yaml.
EOF
}
ensure_supported_mode() {
case "${MODE}" in
--check|--write)
;;
*)
usage
exit 1
;;
esac
}
command_path() {
local tool="$1"
if command -v "${tool}" >/dev/null 2>&1; then
command -v "${tool}"
else
printf '%s' ""
fi
}
command_installed() {
local tool="$1"
if command -v "${tool}" >/dev/null 2>&1; then
printf 'true'
else
printf 'false'
fi
}
command_version() {
local tool="$1"
if ! command -v "${tool}" >/dev/null 2>&1; then
printf '%s' "not-installed"
return
fi
case "${tool}" in
dotnet)
dotnet --version 2>/dev/null || printf '%s' "unknown"
;;
python3)
python3 --version 2>/dev/null || printf '%s' "unknown"
;;
node)
node --version 2>/dev/null || printf '%s' "unknown"
;;
npm)
npm --version 2>/dev/null || printf '%s' "unknown"
;;
bun)
bun --version 2>/dev/null || printf '%s' "unknown"
;;
git)
git --version 2>/dev/null || printf '%s' "unknown"
;;
rg)
rg --version 2>/dev/null | head -n 1 || printf '%s' "unknown"
;;
jq)
jq --version 2>/dev/null || printf '%s' "unknown"
;;
docker)
docker --version 2>/dev/null || printf '%s' "unknown"
;;
bash)
bash --version 2>/dev/null | head -n 1 || printf '%s' "unknown"
;;
*)
"${tool}" --version 2>/dev/null | head -n 1 || printf '%s' "unknown"
;;
esac
}
python_package_version() {
local package_name="$1"
python3 - "${package_name}" <<'PY'
from importlib import metadata
import sys
package_name = sys.argv[1]
try:
print(metadata.version(package_name))
except metadata.PackageNotFoundError:
print("not-installed")
PY
}
python_package_installed() {
local package_name="$1"
local version
version="$(python_package_version "${package_name}")"
if [[ "${version}" == "not-installed" ]]; then
printf 'false'
else
printf 'true'
fi
}
read_os_release() {
local key="$1"
python3 - "$key" <<'PY'
import pathlib
import sys
target_key = sys.argv[1]
values = {}
for line in pathlib.Path("/etc/os-release").read_text(encoding="utf-8").splitlines():
if "=" not in line:
continue
key, value = line.split("=", 1)
values[key] = value.strip().strip('"')
print(values.get(target_key, "unknown"))
PY
}
collect_inventory() {
local os_name distro version_id kernel shell_name wsl_enabled wsl_version timestamp
os_name="$(uname -s)"
distro="$(read_os_release PRETTY_NAME)"
version_id="$(read_os_release VERSION_ID)"
kernel="$(uname -r)"
shell_name="$(basename "${SHELL:-bash}")"
timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
if grep -qi microsoft /proc/version 2>/dev/null; then
wsl_enabled="true"
else
wsl_enabled="false"
fi
if command -v wslinfo >/dev/null 2>&1; then
wsl_version="$(wslinfo --wsl-version 2>/dev/null || printf '%s' "unknown")"
else
wsl_version="unknown"
fi
cat <<EOF
schema_version: 1
generated_at_utc: "${timestamp}"
generator: "scripts/collect-dev-environment.sh"
platform:
os: "${os_name}"
distro: "${distro}"
version: "${version_id}"
kernel: "${kernel}"
wsl: ${wsl_enabled}
wsl_version: "${wsl_version}"
shell: "${shell_name}"
required_runtimes:
dotnet:
installed: $(command_installed dotnet)
version: "$(command_version dotnet)"
path: "$(command_path dotnet)"
purpose: "Builds and tests the GFramework solution."
python3:
installed: $(command_installed python3)
version: "$(command_version python3)"
path: "$(command_path python3)"
purpose: "Runs local automation and environment collection scripts."
node:
installed: $(command_installed node)
version: "$(command_version node)"
path: "$(command_path node)"
purpose: "Provides the JavaScript runtime used by docs tooling."
bun:
installed: $(command_installed bun)
version: "$(command_version bun)"
path: "$(command_path bun)"
purpose: "Installs and previews the VitePress documentation site."
required_tools:
git:
installed: $(command_installed git)
version: "$(command_version git)"
path: "$(command_path git)"
purpose: "Source control and patch review."
bash:
installed: $(command_installed bash)
version: "$(command_version bash)"
path: "$(command_path bash)"
purpose: "Executes repository scripts and shell automation."
rg:
installed: $(command_installed rg)
version: "$(command_version rg)"
path: "$(command_path rg)"
purpose: "Fast text search across the repository."
jq:
installed: $(command_installed jq)
version: "$(command_version jq)"
path: "$(command_path jq)"
purpose: "Inspecting and transforming JSON outputs."
project_tools:
docker:
installed: $(command_installed docker)
version: "$(command_version docker)"
path: "$(command_path docker)"
purpose: "Runs MegaLinter and other containerized validation tools."
python_packages:
requests:
installed: $(python_package_installed requests)
version: "$(python_package_version requests)"
purpose: "Simple HTTP calls in local helper scripts."
rich:
installed: $(python_package_installed rich)
version: "$(python_package_version rich)"
purpose: "Readable CLI output for local Python helpers."
openai:
installed: $(python_package_installed openai)
version: "$(python_package_version openai)"
purpose: "Optional scripted access to OpenAI APIs."
tiktoken:
installed: $(python_package_installed tiktoken)
version: "$(python_package_version tiktoken)"
purpose: "Optional token counting for prompt and context inspection."
pydantic:
installed: $(python_package_installed pydantic)
version: "$(python_package_version pydantic)"
purpose: "Optional typed config and schema validation for helper scripts."
pytest:
installed: $(python_package_installed pytest)
version: "$(python_package_version pytest)"
purpose: "Optional lightweight testing for Python helper scripts."
EOF
}
ensure_supported_mode
if [[ "${MODE}" == "--write" ]]; then
mkdir -p "$(dirname "${OUTPUT_PATH}")"
collect_inventory > "${OUTPUT_PATH}"
printf 'Wrote %s\n' "${OUTPUT_PATH}"
else
collect_inventory
fi
ensure_supported_mode
if [[ "${MODE}" == "--write" ]]; then
mkdir -p "$(dirname "${OUTPUT_PATH}")"
collect_inventory > "${OUTPUT_PATH}"
printf 'Wrote %s\n' "${OUTPUT_PATH}"
else
collect_inventory
fi

View File

@ -0,0 +1,224 @@
#!/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 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 = {
"preferred": choose("rg" if has_rg else None, "grep"),
"fallback": "grep" if has_rg else "unavailable",
"use_for": "Repository text search.",
}
json = {
"preferred": choose("jq" if has_jq else None, "python3" if has_python else None),
"fallback": "python3" if has_jq and has_python else "unavailable",
"use_for": "Inspecting or transforming JSON command output.",
}
scripting = {
"preferred": choose("python3" if has_python else None, "bash" if has_bash else None),
"fallback": "bash" if has_python and has_bash else "unavailable",
"use_for": "Non-trivial local automation and helper scripts.",
}
shell = {
"preferred": choose("bash" if has_bash else None, "sh"),
"fallback": "sh" if has_bash else "unavailable",
"use_for": "Repository shell scripts and command execution.",
}
docs = {
"preferred": choose("bun" if has_bun else None, "npm" if has_node else None),
"fallback": "npm" if has_bun and has_node else "unavailable",
"use_for": "Installing and previewing the docs site.",
}
build = {
"preferred": choose("dotnet" if has_dotnet else None, None),
"fallback": "unavailable",
"use_for": "Build, test, restore, and solution validation.",
}
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()