mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 02:24:30 +08:00
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:
parent
ba4ee24ce7
commit
3130a3bab2
62
.ai/environment/tools.ai.yaml
Normal file
62
.ai/environment/tools.ai.yaml
Normal 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."
|
||||
89
.ai/environment/tools.raw.yaml
Normal file
89
.ai/environment/tools.raw.yaml
Normal 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
4
.gitignore
vendored
@ -12,4 +12,6 @@ opencode.json
|
||||
.omc/
|
||||
docs/.omc/
|
||||
docs/.vitepress/cache/
|
||||
local-plan/
|
||||
local-plan/
|
||||
# tool
|
||||
.venv/
|
||||
@ -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`.
|
||||
|
||||
## 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)
|
||||
|
||||
All generated or modified code MUST include clear and meaningful comments where required by the rules below.
|
||||
|
||||
@ -118,6 +118,10 @@ GFramework 是一个开源的游戏开发框架,我们欢迎所有形式的贡
|
||||
|
||||
## 开发环境设置
|
||||
|
||||
当前推荐的项目相关环境、CLI 与 AI 可用工具清单请查看:
|
||||
|
||||
- [开发环境能力清单](./contributor/development-environment.md)
|
||||
|
||||
### 前置要求
|
||||
|
||||
- **.NET SDK**:8.0、9.0 或 10.0
|
||||
|
||||
96
docs/zh-CN/contributor/development-environment.md
Normal file
96
docs/zh-CN/contributor/development-environment.md
Normal 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`。
|
||||
278
scripts/collect-dev-environment.sh
Normal file
278
scripts/collect-dev-environment.sh
Normal 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
|
||||
224
scripts/generate-ai-environment.py
Normal file
224
scripts/generate-ai-environment.py
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user