diff --git a/README.md b/README.md index af1e0f9..14fc3a5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ aide-plugin (Claude Code 插件) ▼ 调用 aide-program (命令行工具) ├── aide init - 初始化配置 - ├── aide env - 环境检测 + ├── aide env - 环境检测(模块化) ├── aide config - 配置读写 ├── aide flow - 进度追踪 + git 集成(待实现) └── aide decide - 待定项 Web 确认(待实现) @@ -79,8 +79,9 @@ ccoptimize/ │ └── aide.md │ └── aide-program/ # Aide 命令行工具 - ├── aide.sh # Linux/Mac 入口 - ├── aide.bat # Windows 入口 + ├── bin/ + │ ├── aide.sh # Linux/Mac 入口 + │ └── aide.bat # Windows 入口 ├── aide/ # Python 代码 │ ├── __init__.py │ ├── __main__.py @@ -89,7 +90,14 @@ ccoptimize/ │ │ ├── config.py # 配置管理 │ │ └── output.py # 输出格式 │ └── env/ - │ └── ensure.py # 环境检测 + │ ├── manager.py # 环境管理器 + │ ├── registry.py # 模块注册表 + │ └── modules/ # 环境检测模块 + │ ├── base.py + │ ├── python.py + │ ├── uv.py + │ ├── venv.py + │ └── requirements.py └── docs/ # 设计文档(给人) ├── README.md ├── commands/ @@ -122,15 +130,31 @@ ccoptimize/ | 子命令 | 状态 | 说明 | |--------|------|------| | aide init | ✅ 已实现 | 初始化 .aide 目录和配置 | -| aide env ensure | ✅ 已实现 | 环境检测与修复 | +| aide env list | ✅ 已实现 | 列出所有可用模块 | +| aide env ensure | ✅ 已实现 | 模块化环境检测与修复 | | aide env ensure --runtime | ✅ 已实现 | 运行时环境检测 | +| aide env ensure --modules | ✅ 已实现 | 指定模块检测 | +| aide env ensure --all | ✅ 已实现 | 全量检测(仅检查) | +| aide env ensure --verbose | ✅ 已实现 | 详细配置输出 | | aide config get/set | ✅ 已实现 | 配置读写 | | aide flow | ⏳ 待实现 | 进度追踪 + git 集成 | | aide decide | ⏳ 待实现 | 待定项 Web 确认 | 代码位于 `aide-program/aide/` -### 3.3 设计文档 +### 3.3 环境检测模块 + +| 模块 | 类型 | 能力 | 说明 | +|------|------|------|------| +| python | A | check | Python 解释器版本 | +| uv | A | check | uv 包管理器 | +| venv | B | check, ensure | Python 虚拟环境 | +| requirements | B | check, ensure | Python 依赖管理 | + +- 类型A:无需配置即可检测 +- 类型B:需要配置路径才能检测 + +### 3.4 设计文档 | 区块 | 状态 | 位置 | |------|------|------| @@ -189,7 +213,16 @@ ccoptimize/ - 实现 `aide/decide/web/` - React 前端 - 在 `main.py` 添加 CLI 路由 -### 5.3 整体验证 +### 5.3 扩展环境模块(可选) + +可按需添加更多环境检测模块: +- node - Node.js 版本检测 +- npm - npm 依赖管理 +- java - Java JDK 检测 +- go - Go 语言检测 +- rust - Rust 工具链检测 + +### 5.4 整体验证 完成 flow 和 decide 后,需要进行完整工作流验证: 1. `/aide:init` → `/aide:prep` → `/aide:exec` 完整流程测试 @@ -221,6 +254,7 @@ ccoptimize/ ## 七、版本信息 -- 文档版本:1.0.0 -- 更新日期:2025-01-15 +- 文档版本:1.1.0 +- 更新日期:2025-12-14 - 项目阶段:设计完成,部分实现 +- 最近更新:aide env 模块化重构 diff --git a/aide-marketplace/aide-plugin/docs/skill/aide.md b/aide-marketplace/aide-plugin/docs/skill/aide.md index 864f841..ad2e77e 100644 --- a/aide-marketplace/aide-plugin/docs/skill/aide.md +++ b/aide-marketplace/aide-plugin/docs/skill/aide.md @@ -71,42 +71,88 @@ LLM 在执行任务时需要调用各种工具,但: ## 五、子命令接口规格 -### 5.1 aide env ensure +### 5.1 aide env list + +**用途**:列出所有可用的环境检测模块 + +**语法**: +``` +aide env list +``` + +**输出**: +``` +可用模块: + 模块 描述 能力 需要配置 + ──────────────────────────────────────────────────────────── + python Python 解释器版本 check 否 + uv uv 包管理器 check 否 + venv Python 虚拟环境 check, ensure 是 [path] + requirements Python 依赖管理 check, ensure 是 [path] + +当前启用: python, uv, venv, requirements +``` + +### 5.2 aide env ensure **用途**:检测并修复开发环境 **语法**: ``` -aide env ensure [--runtime] +aide env ensure [--runtime] [--modules M1,M2] [--all] [-v] ``` **参数**: | 参数 | 说明 | |------|------| -| `--runtime` | 仅检查 aide 运行时环境,不依赖配置文件 | +| `--runtime` | 仅检查 aide 运行时环境(python + uv),不依赖配置文件 | +| `--modules M1,M2` | 指定要检测的模块(逗号分隔) | +| `--all` | 检测所有已启用模块,仅检查不修复 | +| `-v, --verbose` | 显示详细配置信息(工作目录、配置路径、模块配置等) | **输出**: ``` # 成功 -✓ 环境就绪 (python:3.12, uv:0.4.0) - -# 成功(完整检查) -→ 任务原文档: task-now.md -→ 任务细则文档: task-spec.md -✓ 环境就绪 (python:3.12, uv:0.4.0, venv:.venv) +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✓ venv: .venv +✓ requirements: requirements.txt +✓ 环境就绪 (python:3.14.2, uv:uv 0.9.16, venv:.venv, requirements:requirements.txt) # 自动修复 -⚠ 已修复: 创建虚拟环境 .venv -✓ 环境就绪 (python:3.12) +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +→ venv: 虚拟环境不存在: .venv,尝试修复... +✓ venv: 已创建 +✓ 环境就绪 (...) -# 失败 -✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) - 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 +# 失败(启用模块缺少配置) +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✗ venv: 已启用但缺少配置项: path + +# --verbose 输出(供人工确认) +============================================================ +环境检测详细信息 +============================================================ + + 工作目录: /home/user/myproject + 配置文件: /home/user/myproject/.aide/config.toml + 配置存在: 是 + + 启用模块: python, uv, venv, requirements + 目标模块: python, uv, venv, requirements + + [venv] 配置: + path: .venv + path (绝对): /home/user/myproject/.venv + path (存在): 是 +... ``` -### 5.2 aide init +### 5.3 aide init **用途**:初始化 .aide 目录和默认配置 @@ -126,7 +172,7 @@ aide init ✓ 初始化完成,.aide/ 与默认配置已准备就绪 ``` -### 5.3 aide flow +### 5.4 aide flow **用途**:进度追踪 + Git 自动提交 + 流程校验 @@ -248,7 +294,7 @@ aide flow 会自动校验环节跳转是否合理: - `impl` → `flow-design` ✓(回退) - `flow-design` → `finish` ✗(跳过环节) -### 5.4 aide decide +### 5.5 aide decide **用途**:通过 Web 界面处理待定项确认 @@ -287,7 +333,7 @@ aide decide result > 注:`note` 字段仅在用户添加备注时出现 -### 5.5 aide config +### 5.6 aide config **用途**:配置读写 diff --git a/aide-marketplace/aide-plugin/skills/aide/SKILL.md b/aide-marketplace/aide-plugin/skills/aide/SKILL.md index dd7d849..841f6e5 100644 --- a/aide-marketplace/aide-plugin/skills/aide/SKILL.md +++ b/aide-marketplace/aide-plugin/skills/aide/SKILL.md @@ -22,34 +22,82 @@ Aide 是一套命令行工具,用于支持 Aide 工作流体系。所有 aide ## aide env - 环境管理 +### aide env list + +列出所有可用的环境检测模块。 + +```bash +aide env list +``` + +**输出示例**: +``` +可用模块: + 模块 描述 能力 需要配置 + ──────────────────────────────────────────────────────────── + python Python 解释器版本 check 否 + uv uv 包管理器 check 否 + venv Python 虚拟环境 check, ensure 是 [path] + requirements Python 依赖管理 check, ensure 是 [path] + +当前启用: python, uv, venv, requirements +``` + ### aide env ensure 检测并修复开发环境。 ```bash -# 检查项目开发环境 +# 检查项目开发环境(按配置启用的模块) aide env ensure # 仅检查 aide 运行时环境(不依赖配置文件) aide env ensure --runtime + +# 检测指定模块 +aide env ensure --modules python,uv + +# 检测所有已启用模块(仅检查不修复) +aide env ensure --all + +# 显示详细配置信息(供人工确认) +aide env ensure --verbose ``` **参数**: -- `--runtime`:仅检查 aide 程序自身运行所需的环境(Python 等),不读取项目配置文件 + +| 参数 | 说明 | +|------|------| +| `--runtime` | 仅检查 aide 运行时环境(python + uv) | +| `--modules M1,M2` | 指定要检测的模块(逗号分隔) | +| `--all` | 检测所有已启用模块,仅检查不修复 | +| `-v, --verbose` | 显示详细配置信息 | **输出示例**: + ``` -✓ 环境就绪 (python:3.12, uv:0.4.0) +# 成功 +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✓ venv: .venv +✓ requirements: requirements.txt +✓ 环境就绪 (python:3.14.2, uv:uv 0.9.16, venv:.venv, requirements:requirements.txt) ``` ``` -⚠ 已修复: 创建虚拟环境 .venv -✓ 环境就绪 (python:3.12) +# 自动修复 +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +→ venv: 虚拟环境不存在: .venv,尝试修复... +✓ venv: 已创建 +✓ 环境就绪 (...) ``` ``` -✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) - 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 +# 失败(启用模块缺少配置) +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✗ venv: 已启用但缺少配置项: path ``` --- diff --git a/aide-program/.gitignore b/aide-program/.gitignore new file mode 100644 index 0000000..87ba12f --- /dev/null +++ b/aide-program/.gitignore @@ -0,0 +1,2 @@ +.aide/ +.venv/ \ No newline at end of file diff --git a/aide-program/aide/aide.sh b/aide-program/aide/aide.sh new file mode 120000 index 0000000..9b4c79d --- /dev/null +++ b/aide-program/aide/aide.sh @@ -0,0 +1 @@ +./aide.sh \ No newline at end of file diff --git a/aide-program/aide/core/config.py b/aide-program/aide/core/config.py index b79a634..d39a7c9 100644 --- a/aide-program/aide/core/config.py +++ b/aide-program/aide/core/config.py @@ -13,8 +13,9 @@ from aide.core import output DEFAULT_CONFIG = """# Aide 默认配置(由 aide init 生成) # runtime: aide 自身运行要求 # task: 任务文档路径 -# env: 虚拟环境与依赖配置 +# env: 环境模块配置 # flow: 环节名称列表,供流程校验使用 + [runtime] python_min = "3.11" use_uv = true @@ -24,8 +25,20 @@ source = "task-now.md" spec = "task-spec.md" [env] -venv = ".venv" -requirements = "requirements.txt" +# 启用的模块列表 +modules = ["python", "uv", "venv", "requirements"] + +# Python 版本要求(可选,默认使用 runtime.python_min) +# [env.python] +# min_version = "3.11" + +# 虚拟环境配置(类型B模块,必须配置) +[env.venv] +path = ".venv" + +# 依赖文件配置(类型B模块,必须配置) +[env.requirements] +path = "requirements.txt" [flow] phases = ["task-optimize", "flow-design", "impl", "verify", "docs", "finish"] diff --git a/aide-program/aide/env/ensure.py b/aide-program/aide/env/ensure.py deleted file mode 100644 index 3d813e1..0000000 --- a/aide-program/aide/env/ensure.py +++ /dev/null @@ -1,119 +0,0 @@ -"""环境检测与修复逻辑。""" - -from __future__ import annotations - -import platform -import subprocess -import sys -from pathlib import Path - -from aide.core import output -from aide.core.config import ConfigManager - - -class EnvManager: - def __init__(self, root: Path): - self.root = root - - def ensure(self, runtime_only: bool, cfg: ConfigManager) -> bool: - """运行环境检测入口。""" - required_py = self._get_required_python(cfg, runtime_only) - if not self._check_python_version(required_py): - return False - uv_version = self._check_uv() - if uv_version is None: - return False - - if runtime_only: - output.ok(f"运行时环境就绪 (python:{platform.python_version()}, uv:{uv_version})") - return True - - config = cfg.ensure_config() - cfg.ensure_gitignore() - - env_config = config.get("env", {}) - venv_path = self.root / env_config.get("venv", ".venv") - req_path = self.root / env_config.get("requirements", "requirements.txt") - - self._ensure_requirements_file(req_path) - if not self._ensure_venv(venv_path): - return False - if not self._install_requirements(venv_path, req_path): - return False - - task_config = config.get("task", {}) - output.info(f"任务原文档: {task_config.get('source', 'task-now.md')}") - output.info(f"任务细则文档: {task_config.get('spec', 'task-spec.md')}") - output.ok(f"环境就绪 (python:{platform.python_version()}, uv:{uv_version}, venv:{venv_path})") - return True - - @staticmethod - def _get_required_python(cfg: ConfigManager, runtime_only: bool) -> str: - if runtime_only: - return "3.11" - data = cfg.load_config() - runtime = data.get("runtime", {}) - return str(runtime.get("python_min", "3.11")) - - @staticmethod - def _parse_version(version: str) -> tuple[int, ...]: - parts = [] - for part in version.split("."): - try: - parts.append(int(part)) - except ValueError: - break - return tuple(parts) - - def _check_python_version(self, required: str) -> bool: - current = self._parse_version(platform.python_version()) - target = self._parse_version(required) - if current >= target: - return True - output.err(f"Python 版本不足,要求>={required},当前 {platform.python_version()}") - return False - - def _check_uv(self) -> str | None: - try: - result = subprocess.run( - ["uv", "--version"], - check=True, - capture_output=True, - text=True, - ) - return result.stdout.strip() - except (subprocess.CalledProcessError, FileNotFoundError) as exc: - output.err(f"未检测到 uv,请先安装({exc})") - return None - - def _ensure_venv(self, venv_path: Path) -> bool: - if venv_path.exists(): - return True - output.info(f"创建虚拟环境: {venv_path}") - try: - subprocess.run(["uv", "venv", str(venv_path)], check=True) - output.ok("已创建虚拟环境") - return True - except subprocess.CalledProcessError as exc: - output.err(f"创建虚拟环境失败: {exc}") - return False - - @staticmethod - def _ensure_requirements_file(req_path: Path) -> None: - if req_path.exists(): - return - req_path.write_text("# 在此添加依赖\n", encoding="utf-8") - output.warn(f"未找到 {req_path.name},已创建空文件") - - def _install_requirements(self, venv_path: Path, req_path: Path) -> bool: - if not req_path.exists(): - output.err(f"缺少 {req_path}") - return False - cmd = ["uv", "pip", "install", "-r", str(req_path), "--python", str(venv_path)] - output.info("安装依赖(uv pip install -r requirements.txt)") - try: - subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) - return True - except subprocess.CalledProcessError as exc: - output.err(f"安装依赖失败: {exc}") - return False diff --git a/aide-program/aide/env/manager.py b/aide-program/aide/env/manager.py new file mode 100644 index 0000000..3ff1111 --- /dev/null +++ b/aide-program/aide/env/manager.py @@ -0,0 +1,274 @@ +"""环境管理器。""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from aide.core import output +from aide.core.config import ConfigManager +from aide.env.registry import ModuleRegistry, register_builtin_modules + +# 运行时模块(--runtime 时使用) +RUNTIME_MODULES = ["python", "uv"] + +# 默认启用的模块 +DEFAULT_MODULES = ["python", "uv", "venv", "requirements"] + + +class EnvManager: + """环境管理器。""" + + def __init__(self, root: Path, cfg: ConfigManager): + self.root = root + self.cfg = cfg + self.verbose = False + # 确保模块已注册 + register_builtin_modules() + + def list_modules(self) -> None: + """列出所有可用模块(aide env list)。""" + config = self.cfg.load_config() + enabled = self._get_enabled_modules(config) + + print("可用模块:") + print(f" {'模块':<14} {'描述':<20} {'能力':<16} {'需要配置'}") + print(" " + "─" * 60) + + for info in ModuleRegistry.list_info(): + caps = ", ".join(info.capabilities) + req_cfg = "是" if info.requires_config else "否" + if info.config_keys: + req_cfg += f" [{', '.join(info.config_keys)}]" + print(f" {info.name:<14} {info.description:<20} {caps:<16} {req_cfg}") + + print() + if enabled: + print(f"当前启用: {', '.join(enabled)}") + else: + output.warn("未配置启用模块列表") + + def ensure( + self, + runtime_only: bool = False, + modules: list[str] | None = None, + check_only: bool = False, + verbose: bool = False, + ) -> bool: + """检测并修复环境。 + + Args: + runtime_only: 仅检测运行时环境 + modules: 指定要检测的模块 + check_only: 仅检测不修复(--all 模式) + verbose: 显示详细配置信息 + + Returns: + 是否全部成功 + """ + self.verbose = verbose + config = self.cfg.load_config() + enabled_modules = self._get_enabled_modules(config) + + # verbose: 输出基础信息 + if verbose: + self._print_verbose_header(config, enabled_modules) + + # 确定要检测的模块列表 + if runtime_only: + target_modules = RUNTIME_MODULES + elif modules: + target_modules = modules + elif check_only: + # --all 模式 + if not enabled_modules: + output.warn("未配置启用模块列表,将检测所有支持的模块") + target_modules = ModuleRegistry.names() + else: + target_modules = enabled_modules + else: + target_modules = enabled_modules + + if not target_modules: + output.warn("没有要检测的模块") + return True + + if verbose: + print(f" 目标模块: {', '.join(target_modules)}") + print() + + # 执行检测 + all_success = True + results: list[tuple[str, bool, str]] = [] + + for name in target_modules: + is_enabled = name in enabled_modules + success, msg = self._process_module( + name=name, + config=config, + is_enabled=is_enabled, + check_only=check_only, + ) + results.append((name, success, msg)) + if not success and is_enabled: + all_success = False + break # 启用模块失败时停止 + + # 输出最终状态 + if all_success and not check_only: + # 构建摘要信息 + summary_parts = [] + for name, success, msg in results: + if success and msg: + summary_parts.append(f"{name}:{msg}") + if summary_parts: + output.ok(f"环境就绪 ({', '.join(summary_parts)})") + + return all_success + + def _print_verbose_header(self, config: dict[str, Any], enabled_modules: list[str]) -> None: + """输出详细模式的头部信息。""" + print("=" * 60) + print("环境检测详细信息") + print("=" * 60) + print() + print(f" 工作目录: {self.root}") + print(f" 配置文件: {self.cfg.config_path}") + print(f" 配置存在: {'是' if self.cfg.config_path.exists() else '否'}") + print() + print(f" 启用模块: {', '.join(enabled_modules) if enabled_modules else '(未配置)'}") + print() + + def _print_verbose_module(self, name: str, module_config: dict[str, Any]) -> None: + """输出模块的详细配置信息。""" + print(f" [{name}] 配置:") + if not module_config: + print(" (无配置)") + else: + for key, value in module_config.items(): + if key.startswith("_"): + continue # 跳过内部字段 + if key == "path": + # 对于路径,显示绝对路径 + abs_path = self.root / value + print(f" {key}: {value}") + print(f" {key} (绝对): {abs_path}") + print(f" {key} (存在): {'是' if abs_path.exists() else '否'}") + else: + print(f" {key}: {value}") + + def _get_enabled_modules(self, config: dict[str, Any]) -> list[str]: + """获取已启用的模块列表。""" + env_config = config.get("env", {}) + return env_config.get("modules", DEFAULT_MODULES) + + def _get_module_config(self, name: str, config: dict[str, Any]) -> dict[str, Any]: + """获取模块配置。""" + env_config = config.get("env", {}) + + # 尝试新格式 [env.模块名] + module_config = env_config.get(name, {}) + + # 兼容旧格式:如果值是字符串而不是字典,转换为 {"path": value} + if isinstance(module_config, str): + module_config = {"path": module_config} + + # 兼容旧格式:如果没有配置但存在旧格式字段 + if name == "venv" and not module_config: + if "venv" in env_config and isinstance(env_config["venv"], str): + module_config = {"path": env_config["venv"]} + elif name == "requirements" and not module_config: + if "requirements" in env_config and isinstance(env_config["requirements"], str): + module_config = {"path": env_config["requirements"]} + + # 为 requirements 模块注入 venv 路径 + if name == "requirements": + venv_config = self._get_module_config("venv", config) + if "path" in venv_config: + module_config["_venv_path"] = venv_config["path"] + + # 从 runtime 配置获取 python 版本要求 + if name == "python" and "min_version" not in module_config: + runtime = config.get("runtime", {}) + if "python_min" in runtime: + module_config["min_version"] = runtime["python_min"] + + return module_config + + def _process_module( + self, + name: str, + config: dict[str, Any], + is_enabled: bool, + check_only: bool, + ) -> tuple[bool, str]: + """处理单个模块的检测/修复。 + + Returns: + (是否成功, 版本/路径信息) + """ + module = ModuleRegistry.get(name) + if not module: + if is_enabled: + output.err(f"{name}: 未知模块") + return False, "" + else: + output.warn(f"{name}: 未知模块") + return True, "" + + module_config = self._get_module_config(name, config) + + # verbose: 输出模块配置 + if self.verbose: + self._print_verbose_module(name, module_config) + + # 检查类型B模块的配置 + valid, err_msg = module.validate_config(module_config) + if not valid: + if is_enabled: + output.err(f"{name}: 已启用但{err_msg}") + return False, "" + else: + output.warn(f"{name}: {err_msg},跳过检测") + return True, "" + + # 执行检测 + result = module.check(module_config, self.root) + + if result.success: + version_info = result.version or "" + extra = f" ({result.message})" if result.message else "" + output.ok(f"{name}: {version_info}{extra}") + return True, version_info + + # 检测失败 + if check_only: + # --all 模式:仅报告 + output.warn(f"{name}: {result.message}") + return True, "" + + if result.can_ensure and module.info.can_ensure: + # 尝试修复 + output.info(f"{name}: {result.message},尝试修复...") + ensure_result = module.ensure(module_config, self.root) + + if ensure_result.success: + msg = ensure_result.message or "已修复" + output.ok(f"{name}: {msg}") + return True, ensure_result.version or "" + else: + if is_enabled: + output.err(f"{name}: {ensure_result.message}") + return False, "" + else: + output.warn(f"{name}: {ensure_result.message}") + return True, "" + else: + # 不可修复 + if is_enabled: + extra = " (此模块不支持自动修复)" if not module.info.can_ensure else "" + output.err(f"{name}: {result.message}{extra}") + return False, "" + else: + output.warn(f"{name}: {result.message}") + return True, "" diff --git a/aide-program/aide/env/modules/__init__.py b/aide-program/aide/env/modules/__init__.py new file mode 100644 index 0000000..684515c --- /dev/null +++ b/aide-program/aide/env/modules/__init__.py @@ -0,0 +1 @@ +"""环境检测模块集合。""" diff --git a/aide-program/aide/env/modules/base.py b/aide-program/aide/env/modules/base.py new file mode 100644 index 0000000..edd9c19 --- /dev/null +++ b/aide-program/aide/env/modules/base.py @@ -0,0 +1,89 @@ +"""模块基类定义。""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + + +@dataclass +class CheckResult: + """检测结果。""" + + success: bool + version: str | None = None + message: str | None = None + can_ensure: bool = False # 失败时是否可修复 + + +@dataclass +class ModuleInfo: + """模块元信息。""" + + name: str + description: str + capabilities: list[str] = field(default_factory=lambda: ["check"]) + requires_config: bool = False # 是否需要配置(类型B模块) + config_keys: list[str] = field(default_factory=list) # 需要的配置键 + + @property + def can_ensure(self) -> bool: + """是否支持 ensure 操作。""" + return "ensure" in self.capabilities + + +class BaseModule(ABC): + """模块基类。""" + + @property + @abstractmethod + def info(self) -> ModuleInfo: + """返回模块元信息。""" + pass + + @abstractmethod + def check(self, config: dict[str, Any], root: Path) -> CheckResult: + """检测环境。 + + Args: + config: 模块配置(来自 [env.模块名]) + root: 项目根目录 + + Returns: + CheckResult: 检测结果 + """ + pass + + def ensure(self, config: dict[str, Any], root: Path) -> CheckResult: + """修复环境(可选实现)。 + + Args: + config: 模块配置 + root: 项目根目录 + + Returns: + CheckResult: 修复结果 + """ + return CheckResult( + success=False, + message="此模块不支持自动修复", + ) + + def validate_config(self, config: dict[str, Any]) -> tuple[bool, str | None]: + """验证模块配置是否完整。 + + Args: + config: 模块配置 + + Returns: + (是否有效, 错误信息) + """ + if not self.info.requires_config: + return True, None + + missing = [k for k in self.info.config_keys if k not in config] + if missing: + return False, f"缺少配置项: {', '.join(missing)}" + return True, None diff --git a/aide-program/aide/env/modules/python.py b/aide-program/aide/env/modules/python.py new file mode 100644 index 0000000..f290ff3 --- /dev/null +++ b/aide-program/aide/env/modules/python.py @@ -0,0 +1,58 @@ +"""Python 环境检测模块。""" + +from __future__ import annotations + +import platform +from pathlib import Path +from typing import Any + +from aide.env.modules.base import BaseModule, CheckResult, ModuleInfo + + +class PythonModule(BaseModule): + """Python 解释器检测模块(类型A:无需配置)。""" + + @property + def info(self) -> ModuleInfo: + return ModuleInfo( + name="python", + description="Python 解释器版本", + capabilities=["check"], + requires_config=False, + ) + + def check(self, config: dict[str, Any], root: Path) -> CheckResult: + """检测 Python 版本。""" + current_version = platform.python_version() + min_version = config.get("min_version", "3.11") + + current_parts = self._parse_version(current_version) + min_parts = self._parse_version(min_version) + + if current_parts >= min_parts: + return CheckResult( + success=True, + version=current_version, + message=f">={min_version}", + ) + else: + return CheckResult( + success=False, + version=current_version, + message=f"版本不足,要求>={min_version},当前 {current_version}", + can_ensure=False, + ) + + @staticmethod + def _parse_version(version: str) -> tuple[int, ...]: + """解析版本号字符串。""" + parts = [] + for part in version.split("."): + try: + parts.append(int(part)) + except ValueError: + break + return tuple(parts) + + +module = PythonModule() diff --git a/aide-program/aide/env/modules/requirements.py b/aide-program/aide/env/modules/requirements.py new file mode 100644 index 0000000..a315ba1 --- /dev/null +++ b/aide-program/aide/env/modules/requirements.py @@ -0,0 +1,88 @@ +"""Python 依赖管理模块。""" + +from __future__ import annotations + +import subprocess +from pathlib import Path +from typing import Any + +from aide.env.modules.base import BaseModule, CheckResult, ModuleInfo + + +class RequirementsModule(BaseModule): + """Python 依赖管理模块(类型B:需要配置)。""" + + @property + def info(self) -> ModuleInfo: + return ModuleInfo( + name="requirements", + description="Python 依赖管理", + capabilities=["check", "ensure"], + requires_config=True, + config_keys=["path"], + ) + + def check(self, config: dict[str, Any], root: Path) -> CheckResult: + """检测 requirements.txt 是否存在。""" + req_path = root / config["path"] + + if not req_path.exists(): + return CheckResult( + success=False, + message=f"文件不存在: {config['path']}", + can_ensure=True, + ) + + return CheckResult( + success=True, + version=config["path"], + ) + + def ensure(self, config: dict[str, Any], root: Path) -> CheckResult: + """创建空的 requirements.txt 并安装依赖。""" + req_path = root / config["path"] + + # 如果文件不存在,创建空文件 + if not req_path.exists(): + req_path.write_text("# 在此添加依赖\n", encoding="utf-8") + + # 获取 venv 路径(从同级配置中获取) + venv_config = config.get("_venv_path") + if not venv_config: + # 尝试使用默认路径 + venv_path = root / ".venv" + else: + venv_path = root / venv_config + + if not venv_path.exists(): + return CheckResult( + success=False, + message="虚拟环境不存在,请先创建", + ) + + # 安装依赖 + try: + subprocess.run( + ["uv", "pip", "install", "-r", str(req_path), "--python", str(venv_path)], + check=True, + capture_output=True, + ) + return CheckResult( + success=True, + version=config["path"], + message="已安装", + ) + except FileNotFoundError: + return CheckResult( + success=False, + message="安装失败: uv 未安装", + ) + except subprocess.CalledProcessError as exc: + stderr = exc.stderr.decode() if exc.stderr else str(exc) + return CheckResult( + success=False, + message=f"安装失败: {stderr}", + ) + + +module = RequirementsModule() diff --git a/aide-program/aide/env/modules/uv.py b/aide-program/aide/env/modules/uv.py new file mode 100644 index 0000000..4334363 --- /dev/null +++ b/aide-program/aide/env/modules/uv.py @@ -0,0 +1,52 @@ +"""uv 包管理器检测模块。""" + +from __future__ import annotations + +import subprocess +from pathlib import Path +from typing import Any + +from aide.env.modules.base import BaseModule, CheckResult, ModuleInfo + + +class UvModule(BaseModule): + """uv 包管理器检测模块(类型A:无需配置)。""" + + @property + def info(self) -> ModuleInfo: + return ModuleInfo( + name="uv", + description="uv 包管理器", + capabilities=["check"], + requires_config=False, + ) + + def check(self, config: dict[str, Any], root: Path) -> CheckResult: + """检测 uv 是否可用。""" + try: + result = subprocess.run( + ["uv", "--version"], + check=True, + capture_output=True, + text=True, + ) + version = result.stdout.strip() + return CheckResult( + success=True, + version=version, + ) + except FileNotFoundError: + return CheckResult( + success=False, + message="未安装,请先安装 uv", + can_ensure=False, + ) + except subprocess.CalledProcessError as exc: + return CheckResult( + success=False, + message=f"执行失败: {exc}", + can_ensure=False, + ) + + +module = UvModule() diff --git a/aide-program/aide/env/modules/venv.py b/aide-program/aide/env/modules/venv.py new file mode 100644 index 0000000..a41d047 --- /dev/null +++ b/aide-program/aide/env/modules/venv.py @@ -0,0 +1,80 @@ +"""Python 虚拟环境模块。""" + +from __future__ import annotations + +import subprocess +from pathlib import Path +from typing import Any + +from aide.env.modules.base import BaseModule, CheckResult, ModuleInfo + + +class VenvModule(BaseModule): + """Python 虚拟环境模块(类型B:需要配置)。""" + + @property + def info(self) -> ModuleInfo: + return ModuleInfo( + name="venv", + description="Python 虚拟环境", + capabilities=["check", "ensure"], + requires_config=True, + config_keys=["path"], + ) + + def check(self, config: dict[str, Any], root: Path) -> CheckResult: + """检测虚拟环境是否存在。""" + venv_path = root / config["path"] + + if not venv_path.exists(): + return CheckResult( + success=False, + message=f"虚拟环境不存在: {config['path']}", + can_ensure=True, + ) + + # 检查是否是有效的虚拟环境 + python_path = venv_path / "bin" / "python" + if not python_path.exists(): + python_path = venv_path / "Scripts" / "python.exe" # Windows + + if not python_path.exists(): + return CheckResult( + success=False, + message=f"无效的虚拟环境: {config['path']}", + can_ensure=True, + ) + + return CheckResult( + success=True, + version=config["path"], + ) + + def ensure(self, config: dict[str, Any], root: Path) -> CheckResult: + """创建虚拟环境。""" + venv_path = root / config["path"] + + try: + subprocess.run( + ["uv", "venv", str(venv_path)], + check=True, + capture_output=True, + ) + return CheckResult( + success=True, + version=config["path"], + message="已创建", + ) + except FileNotFoundError: + return CheckResult( + success=False, + message="创建失败: uv 未安装", + ) + except subprocess.CalledProcessError as exc: + return CheckResult( + success=False, + message=f"创建失败: {exc.stderr.decode() if exc.stderr else exc}", + ) + + +module = VenvModule() diff --git a/aide-program/aide/env/registry.py b/aide-program/aide/env/registry.py new file mode 100644 index 0000000..bb71d09 --- /dev/null +++ b/aide-program/aide/env/registry.py @@ -0,0 +1,50 @@ +"""模块注册表。""" + +from __future__ import annotations + +from aide.env.modules.base import BaseModule, ModuleInfo + + +class ModuleRegistry: + """模块注册表,管理所有可用的环境检测模块。""" + + _modules: dict[str, BaseModule] = {} + + @classmethod + def register(cls, module: BaseModule) -> None: + """注册模块。""" + cls._modules[module.info.name] = module + + @classmethod + def get(cls, name: str) -> BaseModule | None: + """获取指定模块。""" + return cls._modules.get(name) + + @classmethod + def all(cls) -> dict[str, BaseModule]: + """获取所有已注册模块。""" + return cls._modules.copy() + + @classmethod + def names(cls) -> list[str]: + """获取所有模块名称。""" + return list(cls._modules.keys()) + + @classmethod + def list_info(cls) -> list[ModuleInfo]: + """获取所有模块的元信息。""" + return [m.info for m in cls._modules.values()] + + @classmethod + def clear(cls) -> None: + """清空注册表(用于测试)。""" + cls._modules.clear() + + +def register_builtin_modules() -> None: + """注册内置模块。""" + from aide.env.modules import python, uv, venv, requirements + + for mod in [python, uv, venv, requirements]: + if hasattr(mod, "module"): + ModuleRegistry.register(mod.module) diff --git a/aide-program/aide/main.py b/aide-program/aide/main.py index 86acf54..c73322d 100644 --- a/aide-program/aide/main.py +++ b/aide-program/aide/main.py @@ -9,7 +9,7 @@ from typing import Any from aide.core import output from aide.core.config import ConfigManager -from aide.env.ensure import EnvManager +from aide.env.manager import EnvManager def main(argv: list[str] | None = None) -> int: @@ -32,17 +32,50 @@ def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="aide", description="Aide 工作流辅助工具") subparsers = parser.add_subparsers(dest="command") + # aide init init_parser = subparsers.add_parser("init", help="初始化 .aide 目录与默认配置") init_parser.set_defaults(func=handle_init) + # aide env env_parser = subparsers.add_parser("env", help="环境管理") env_sub = env_parser.add_subparsers(dest="env_command") + + # aide env ensure ensure_parser = env_sub.add_parser("ensure", help="检测并修复运行环境") - ensure_parser.add_argument("--runtime", action="store_true", help="仅检查 aide 运行时环境") + ensure_parser.add_argument( + "--runtime", + action="store_true", + help="仅检查 aide 运行时环境(python + uv)", + ) + ensure_parser.add_argument( + "--modules", + type=str, + help="指定要检测的模块(逗号分隔)", + ) + ensure_parser.add_argument( + "--all", + action="store_true", + dest="check_all", + help="检测所有已启用模块(仅检查不修复)", + ) + ensure_parser.add_argument( + "-v", "--verbose", + action="store_true", + help="显示详细配置信息", + ) ensure_parser.set_defaults(func=handle_env_ensure) + # aide env list + list_parser = env_sub.add_parser("list", help="列出所有可用模块") + list_parser.set_defaults(func=handle_env_list) + + # aide env(无子命令时等同于 ensure) + env_parser.set_defaults(func=handle_env_default) + + # aide config config_parser = subparsers.add_parser("config", help="配置管理") config_sub = config_parser.add_subparsers(dest="config_command") + get_parser = config_sub.add_parser("get", help="读取配置值") get_parser.add_argument("key", help="使用点号分隔的键名,如 task.source") get_parser.set_defaults(func=handle_config_get) @@ -65,14 +98,43 @@ def handle_init(args: argparse.Namespace) -> bool: return True +def handle_env_default(args: argparse.Namespace) -> bool: + """aide env(无子命令)等同于 aide env ensure。""" + if args.env_command is None: + # 无子命令,执行默认的 ensure + root = Path.cwd() + cfg = ConfigManager(root) + manager = EnvManager(root, cfg) + return manager.ensure() + return True + + def handle_env_ensure(args: argparse.Namespace) -> bool: - if args.env_command != "ensure": - output.err("请指定 env 子命令,如: aide env ensure") - return False + """aide env ensure 处理。""" root = Path.cwd() cfg = ConfigManager(root) - manager = EnvManager(root) - return manager.ensure(runtime_only=args.runtime, cfg=cfg) + manager = EnvManager(root, cfg) + + # 解析 --modules 参数 + modules = None + if args.modules: + modules = [m.strip() for m in args.modules.split(",") if m.strip()] + + return manager.ensure( + runtime_only=args.runtime, + modules=modules, + check_only=args.check_all, + verbose=args.verbose, + ) + + +def handle_env_list(args: argparse.Namespace) -> bool: + """aide env list 处理。""" + root = Path.cwd() + cfg = ConfigManager(root) + manager = EnvManager(root, cfg) + manager.list_modules() + return True def handle_config_get(args: argparse.Namespace) -> bool: diff --git a/aide-program/bin/aide b/aide-program/bin/aide new file mode 120000 index 0000000..9b4c79d --- /dev/null +++ b/aide-program/bin/aide @@ -0,0 +1 @@ +./aide.sh \ No newline at end of file diff --git a/aide-program/aide.bat b/aide-program/bin/aide.bat similarity index 100% rename from aide-program/aide.bat rename to aide-program/bin/aide.bat diff --git a/aide-program/aide.sh b/aide-program/bin/aide.sh similarity index 76% rename from aide-program/aide.sh rename to aide-program/bin/aide.sh index fffe429..558764b 100755 --- a/aide-program/aide.sh +++ b/aide-program/bin/aide.sh @@ -10,6 +10,6 @@ if [ ! -x "$VENV_PY" ]; then exit 1 fi -cd "$PROJECT_ROOT" -export PYTHONPATH="${PROJECT_ROOT}/aide-program${PYTHONPATH:+:$PYTHONPATH}" +# 不切换目录,保持用户的工作目录 +export PYTHONPATH="${PROJECT_ROOT}${PYTHONPATH:+:$PYTHONPATH}" exec "$VENV_PY" -m aide "$@" diff --git a/aide-program/docs/commands/env-redesign.md b/aide-program/docs/commands/env-redesign.md deleted file mode 100644 index dae8f00..0000000 --- a/aide-program/docs/commands/env-redesign.md +++ /dev/null @@ -1,396 +0,0 @@ -# aide env 重新设计 - 实现计划 - -## 一、设计概要 - -### 1.1 命令结构 - -``` -aide env # 等同于 aide env ensure -aide env ensure [options] # 检测并修复 -aide env list # 列出所有可用模块 -``` - -### 1.2 参数 - -| 参数 | 说明 | -|------|------| -| `--runtime` | 仅检测 aide 运行时环境(python + uv) | -| `--modules M1,M2` | 指定要检测的模块(逗号分隔) | -| `--all` | 检测所有已启用模块,仅检查不修复 | - -### 1.3 模块分类 - -**类型A:自包含模块(无需配置即可检测)** -- python, uv, java, go, rust, gcc, cmake, node, flutter - -**类型B:路径依赖模块(必须有配置才能检测)** -- venv, requirements, npm - ---- - -## 二、目录结构变更 - -``` -aide-program/aide/ -├── __init__.py -├── __main__.py -├── main.py # [修改] 更新 CLI 路由 -├── core/ -│ ├── __init__.py -│ ├── config.py # [修改] 更新默认配置 -│ └── output.py -└── env/ - ├── __init__.py - ├── ensure.py # [删除] 旧实现 - ├── manager.py # [新建] 环境管理器主入口 - ├── registry.py # [新建] 模块注册表 - └── modules/ # [新建] 模块目录 - ├── __init__.py - ├── base.py # [新建] 模块基类 - ├── python.py # [新建] Python 模块 - ├── uv.py # [新建] uv 模块 - ├── venv.py # [新建] venv 模块 - ├── requirements.py # [新建] requirements 模块 - ├── node.py # [新建] Node.js 模块 - ├── npm.py # [新建] npm 模块 - └── ... # 其他模块按需添加 -``` - ---- - -## 三、配置文件变更 - -### 3.1 新默认配置 - -```toml -# Aide 默认配置(由 aide init 生成) - -[runtime] -python_min = "3.11" -use_uv = true - -[task] -source = "task-now.md" -spec = "task-spec.md" - -[env] -# 启用的模块列表 -modules = ["python", "uv", "venv", "requirements"] - -# 类型A模块配置(可选,指定版本要求) -[env.python] -min_version = "3.11" - -# 类型B模块配置(必需,指定路径) -[env.venv] -path = ".venv" - -[env.requirements] -path = "requirements.txt" - -[flow] -phases = ["task-optimize", "flow-design", "impl", "verify", "docs", "finish"] -``` - -### 3.2 配置兼容性 - -旧配置格式: -```toml -[env] -venv = ".venv" -requirements = "requirements.txt" -``` - -新配置格式: -```toml -[env] -modules = ["python", "uv", "venv", "requirements"] - -[env.venv] -path = ".venv" - -[env.requirements] -path = "requirements.txt" -``` - -**迁移策略**:读取时兼容旧格式,写入时使用新格式。 - ---- - -## 四、核心类设计 - -### 4.1 模块基类 (`env/modules/base.py`) - -```python -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Any -from pathlib import Path - -@dataclass -class CheckResult: - """检测结果""" - success: bool - version: str | None = None - message: str | None = None - can_ensure: bool = False # 失败时是否可修复 - -@dataclass -class ModuleInfo: - """模块元信息""" - name: str - description: str - capabilities: list[str] # ["check"] 或 ["check", "ensure"] - requires_config: bool # 是否需要配置(类型B) - config_keys: list[str] # 需要的配置键,如 ["path"] - -class BaseModule(ABC): - """模块基类""" - - @property - @abstractmethod - def info(self) -> ModuleInfo: - """返回模块元信息""" - pass - - @abstractmethod - def check(self, config: dict[str, Any], root: Path) -> CheckResult: - """检测环境""" - pass - - def ensure(self, config: dict[str, Any], root: Path) -> CheckResult: - """修复环境(可选实现)""" - return CheckResult( - success=False, - message="此模块不支持自动修复" - ) -``` - -### 4.2 模块注册表 (`env/registry.py`) - -```python -from aide.env.modules.base import BaseModule, ModuleInfo - -class ModuleRegistry: - """模块注册表""" - - _modules: dict[str, BaseModule] = {} - - @classmethod - def register(cls, module: BaseModule) -> None: - cls._modules[module.info.name] = module - - @classmethod - def get(cls, name: str) -> BaseModule | None: - return cls._modules.get(name) - - @classmethod - def all(cls) -> dict[str, BaseModule]: - return cls._modules.copy() - - @classmethod - def list_info(cls) -> list[ModuleInfo]: - return [m.info for m in cls._modules.values()] - -# 自动注册所有模块 -def _auto_register(): - from aide.env.modules import python, uv, venv, requirements, node, npm - # 每个模块文件导出一个 module 实例 - for mod in [python, uv, venv, requirements, node, npm]: - if hasattr(mod, 'module'): - ModuleRegistry.register(mod.module) - -_auto_register() -``` - -### 4.3 环境管理器 (`env/manager.py`) - -```python -from pathlib import Path -from typing import Any - -from aide.core import output -from aide.core.config import ConfigManager -from aide.env.registry import ModuleRegistry -from aide.env.modules.base import CheckResult - -class EnvManager: - """环境管理器""" - - def __init__(self, root: Path, cfg: ConfigManager): - self.root = root - self.cfg = cfg - - def list_modules(self) -> None: - """列出所有可用模块""" - # 实现 aide env list - pass - - def ensure( - self, - runtime_only: bool = False, - modules: list[str] | None = None, - check_only: bool = False, # --all 时为 True - ) -> bool: - """检测并修复环境""" - # 主逻辑实现 - pass - - def _get_enabled_modules(self) -> list[str]: - """获取已启用的模块列表""" - pass - - def _get_module_config(self, name: str) -> dict[str, Any]: - """获取模块配置""" - pass - - def _check_module(self, name: str, is_enabled: bool) -> tuple[bool, CheckResult]: - """检测单个模块""" - pass - - def _ensure_module(self, name: str, is_enabled: bool) -> tuple[bool, CheckResult]: - """检测并修复单个模块""" - pass -``` - ---- - -## 五、执行逻辑详解 - -### 5.1 `aide env ensure` 流程 - -``` -1. 读取配置 -2. 获取 modules 列表(已启用模块) -3. 对每个模块: - a. 获取模块实例 - b. 获取模块配置 - c. 检查类型B模块是否有必需配置 - - 已启用 + 无配置 → ✗ 错误,停止 - - 未启用 + 无配置 → ⚠ 警告,跳过 - d. 执行 check() - e. 如果失败且可修复:执行 ensure() - f. 根据启用状态决定输出级别 -4. 输出最终状态 -``` - -### 5.2 `aide env ensure --all` 流程 - -``` -1. 读取配置 -2. 获取 modules 列表 - - 有列表 → 使用列表 - - 无列表 → ⚠ 警告 + 使用所有已注册模块 -3. 对每个模块: - a. 仅执行 check()(不修复) - b. 输出检测结果 -4. 输出汇总 -``` - -### 5.3 `aide env ensure --modules X,Y` 流程 - -``` -1. 解析指定的模块列表 -2. 读取配置,获取已启用列表 -3. 对每个指定模块: - a. 判断是否在启用列表中 - b. 检查类型B模块配置 - c. 执行 check() + ensure() - d. 根据启用状态决定输出级别 -4. 输出最终状态 -``` - ---- - -## 六、输出级别规则 - -| 场景 | 在启用列表 | 有配置 | 结果 | 输出 | 行为 | -|------|-----------|--------|------|------|------| -| ensure | ✓ | ✓/NA | 成功 | ✓ | 继续 | -| ensure | ✓ | ✓/NA | 失败+可修复 | → | 修复 | -| ensure | ✓ | ✓/NA | 失败+不可修复 | ✗ | **停止** | -| ensure | ✓ | ✗(B类) | - | ✗ | **停止** | -| --modules | ✗ | ✓/NA | 成功 | ✓ | 继续 | -| --modules | ✗ | ✓/NA | 失败 | ⚠ | 继续 | -| --modules | ✗ | ✗(B类) | - | ⚠ | 跳过 | -| --all | any | any | any | ✓/⚠ | 仅检测 | - ---- - -## 七、实现步骤 - -### 阶段1:基础架构 - -1. 创建 `env/modules/` 目录结构 -2. 实现 `base.py` 模块基类 -3. 实现 `registry.py` 模块注册表 -4. 更新 `core/config.py` 默认配置 - -### 阶段2:核心模块实现 - -5. 实现 `python.py` 模块 -6. 实现 `uv.py` 模块 -7. 实现 `venv.py` 模块 -8. 实现 `requirements.py` 模块 - -### 阶段3:管理器与 CLI - -9. 实现 `manager.py` 环境管理器 -10. 更新 `main.py` CLI 路由 -11. 删除旧的 `ensure.py` - -### 阶段4:扩展模块(可选) - -12. 实现 `node.py` 模块 -13. 实现 `npm.py` 模块 -14. 其他模块按需添加 - -### 阶段5:文档与测试 - -15. 更新 `docs/commands/env.md` 设计文档 -16. 更新 `docs/formats/config.md` 配置文档 -17. 添加测试用例 - ---- - -## 八、向后兼容 - -### 8.1 命令兼容 - -| 旧命令 | 新行为 | -|--------|--------| -| `aide env ensure` | 保持不变 | -| `aide env ensure --runtime` | 保持不变 | - -### 8.2 配置兼容 - -读取配置时检测旧格式并转换: - -```python -def _migrate_config(config: dict) -> dict: - """兼容旧配置格式""" - env = config.get("env", {}) - - # 如果没有 modules 字段,使用默认值 - if "modules" not in env: - env["modules"] = ["python", "uv", "venv", "requirements"] - - # 如果使用旧的 venv/requirements 字段 - if "venv" in env and not isinstance(env["venv"], dict): - old_venv = env.pop("venv") - env.setdefault("venv", {})["path"] = old_venv - - if "requirements" in env and not isinstance(env["requirements"], dict): - old_req = env.pop("requirements") - env.setdefault("requirements", {})["path"] = old_req - - return config -``` - ---- - -## 九、相关文档 - -- [aide env 设计文档](./env.md) - 需更新 -- [配置格式文档](../formats/config.md) - 需更新 -- [aide skill 设计文档](../../../aide-marketplace/aide-plugin/docs/skill/aide.md) - 需更新 diff --git a/aide-program/docs/commands/env.md b/aide-program/docs/commands/env.md index d9e7750..0eef833 100644 --- a/aide-program/docs/commands/env.md +++ b/aide-program/docs/commands/env.md @@ -9,83 +9,127 @@ | 环境不一致 | 命令执行失败,打断业务流程 | | 手动检查繁琐 | 每次都要检查 Python、虚拟环境、依赖 | | 修复方式不统一 | 不同人有不同的修复习惯 | +| 检测项不可扩展 | 无法按需添加新的环境检测 | ### 1.2 设计目标 -提供**统一的环境检测与修复**: -- 自动检测环境问题 +提供**模块化、可配置的环境检测与修复**: +- 模块化检测项,支持扩展 +- 可配置启用哪些模块 - 能修复的自动修复 - 不能修复的给出明确建议 +- 详细模式供人工确认 --- -## 二、职责 +## 二、命令结构 -### 2.1 做什么 +``` +aide env # 等同于 aide env ensure +aide env ensure [options] # 检测并修复 +aide env list # 列出所有可用模块 +``` -1. 检测 Python 版本是否满足要求 -2. 检测 uv 是否可用 -3. 检测/创建虚拟环境 -4. 安装依赖 -5. 输出项目配置信息 +### 2.1 aide env ensure -### 2.2 不做什么 +检测环境并尝试修复问题。 -- 不修改业务代码 -- 不执行业务逻辑 -- 不进行流程追踪 +**参数:** + +| 参数 | 说明 | +|------|------| +| `--runtime` | 仅检测 aide 运行时环境(python + uv) | +| `--modules M1,M2` | 指定要检测的模块(逗号分隔) | +| `--all` | 检测所有已启用模块,仅检查不修复 | +| `-v, --verbose` | 显示详细配置信息 | + +### 2.2 aide env list + +列出所有可用的环境检测模块及其状态。 --- -## 三、接口规格 +## 三、模块系统 -### 3.1 命令语法 +### 3.1 模块分类 -``` -aide env ensure [--runtime] -``` +**类型A:自包含模块(无需配置即可检测)** -### 3.2 参数 - -| 参数 | 类型 | 说明 | +| 模块 | 描述 | 能力 | |------|------|------| -| `--runtime` | 可选 | 仅检查 aide 运行时环境,不依赖配置文件 | +| `python` | Python 解释器版本 | check | +| `uv` | uv 包管理器 | check | -### 3.3 输出 +**类型B:路径依赖模块(必须有配置才能检测)** -**成功(runtime 模式)**: -``` -✓ 运行时环境就绪 (python:3.12, uv:0.4.0) -``` +| 模块 | 描述 | 能力 | 必需配置 | +|------|------|------|----------| +| `venv` | Python 虚拟环境 | check, ensure | `path` | +| `requirements` | Python 依赖管理 | check, ensure | `path` | -**成功(完整模式)**: -``` -→ 任务原文档: task-now.md -→ 任务细则文档: task-spec.md -✓ 环境就绪 (python:3.12, uv:0.4.0, venv:.venv) -``` +### 3.2 模块能力 -**自动修复**: -``` -→ 创建虚拟环境: .venv -✓ 已创建虚拟环境 -⚠ 未找到 requirements.txt,已创建空文件 -→ 安装依赖(uv pip install -r requirements.txt) -✓ 环境就绪 (python:3.12, uv:0.4.0, venv:.venv) -``` - -**失败**: -``` -✗ Python 版本不足,要求>=3.11,当前 3.9 -``` - -``` -✗ 未检测到 uv,请先安装(FileNotFoundError) -``` +- `check`:检测环境是否可用 +- `ensure`:检测失败时尝试自动修复 --- -## 四、业务流程 +## 四、配置 + +### 4.1 配置结构 + +```toml +[env] +# 启用的模块列表 +modules = ["python", "uv", "venv", "requirements"] + +# 类型A模块配置(可选) +[env.python] +min_version = "3.11" + +# 类型B模块配置(必需) +[env.venv] +path = ".venv" + +[env.requirements] +path = "requirements.txt" +``` + +### 4.2 配置兼容性 + +支持旧格式配置: + +```toml +[env] +venv = ".venv" +requirements = "requirements.txt" +``` + +读取时自动转换为新格式。 + +--- + +## 五、执行逻辑 + +### 5.1 输出级别规则 + +| 场景 | 在启用列表 | 有配置 | 结果 | 输出 | 行为 | +|------|-----------|--------|------|------|------| +| ensure | ✓ | ✓/NA | 成功 | ✓ | 继续 | +| ensure | ✓ | ✓/NA | 失败+可修复 | → | 修复 | +| ensure | ✓ | ✓/NA | 失败+不可修复 | ✗ | **停止** | +| ensure | ✓ | ✗(B类) | - | ✗ | **停止** | +| --modules | ✗ | ✓/NA | 成功 | ✓ | 继续 | +| --modules | ✗ | ✓/NA | 失败 | ⚠ | 继续 | +| --modules | ✗ | ✗(B类) | - | ⚠ | 跳过 | +| --all | any | any | any | ✓/⚠ | 仅检测 | + +**核心原则:** +- 启用模块失败 = 错误(✗) = 必须解决 +- 未启用模块失败 = 警告(⚠) = 可忽略 +- 启用的B类模块无配置 = 错误(✗) = 配置错误 + +### 5.2 业务流程 ``` @startuml @@ -93,60 +137,74 @@ skinparam defaultFontName "PingFang SC" start -if (--runtime 参数?) then (是) - :required_py = "3.11" (硬编码); +:读取配置; +:获取启用模块列表; + +if (--runtime?) then (是) + :target = [python, uv]; +else if (--modules?) then (是) + :target = 指定模块; +else if (--all?) then (是) + :target = 启用模块; + :check_only = true; else (否) - :从配置文件读取 required_py; + :target = 启用模块; endif -:检查 Python 版本; -if (版本满足?) then (是) -else (否) - :输出错误信息; - stop +if (verbose?) then (是) + :输出详细头部信息; endif -:检查 uv 可用性; -if (uv 可用?) then (是) -else (否) - :输出错误信息; - stop -endif +:遍历 target 模块; -if (--runtime 参数?) then (是) - :输出运行时环境就绪; - stop -endif +repeat + :获取模块实例; + :获取模块配置; -:读取配置文件; -:确保 .gitignore 包含 .aide/; - -:读取 venv 路径配置; -if (虚拟环境存在?) then (是) -else (否) - :使用 uv venv 创建; - if (创建成功?) then (是) - else (否) - :输出错误信息; - stop + if (verbose?) then (是) + :输出模块配置详情; endif -endif -:读取 requirements 路径配置; -if (requirements.txt 存在?) then (是) -else (否) - :创建空文件; - :输出警告; -endif + if (B类模块 && 无配置?) then (是) + if (在启用列表?) then (是) + :输出错误; + stop + else (否) + :输出警告,跳过; + endif + endif -:使用 uv pip install 安装依赖; -if (安装成功?) then (是) -else (否) - :输出错误信息; - stop -endif + :执行 check(); + + if (成功?) then (是) + :输出成功; + else (否) + if (check_only?) then (是) + :输出警告; + else if (可修复?) then (是) + :执行 ensure(); + if (修复成功?) then (是) + :输出成功; + else (否) + if (在启用列表?) then (是) + :输出错误; + stop + else (否) + :输出警告; + endif + endif + else (否) + if (在启用列表?) then (是) + :输出错误; + stop + else (否) + :输出警告; + endif + endif + endif + +repeat while (还有模块?) -:输出任务文档路径配置; :输出环境就绪; stop @@ -155,103 +213,147 @@ stop --- -## 五、数据结构 +## 六、输出示例 -### 5.1 配置依赖 - -从 `.aide/config.toml` 读取: +### 6.1 aide env list ``` -[runtime] -python_min # Python 最低版本要求 +可用模块: + 模块 描述 能力 需要配置 + ──────────────────────────────────────────────────────────── + python Python 解释器版本 check 否 + uv uv 包管理器 check 否 + venv Python 虚拟环境 check, ensure 是 [path] + requirements Python 依赖管理 check, ensure 是 [path] -[env] -venv # 虚拟环境路径 -requirements # 依赖文件路径 - -[task] -source # 任务原文档路径 -spec # 任务细则文档路径 +当前启用: python, uv, venv, requirements ``` -### 5.2 方法签名原型 +### 6.2 aide env ensure + +**成功:** +``` +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✓ venv: .venv +✓ requirements: requirements.txt +✓ 环境就绪 (python:3.14.2, uv:uv 0.9.16, venv:.venv, requirements:requirements.txt) +``` + +**需修复:** +``` +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +→ venv: 虚拟环境不存在: .venv,尝试修复... +✓ venv: 已创建 +✓ requirements: requirements.txt +✓ 环境就绪 (...) +``` + +**启用模块失败:** +``` +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✗ venv: 已启用但缺少配置项: path +``` + +### 6.3 aide env ensure --verbose ``` -class EnvManager: - root: Path # 项目根目录 +============================================================ +环境检测详细信息 +============================================================ - ensure(runtime_only: bool, cfg: ConfigManager) -> bool - # 主入口,返回是否成功 + 工作目录: /home/user/myproject + 配置文件: /home/user/myproject/.aide/config.toml + 配置存在: 是 - _get_required_python(cfg: ConfigManager, runtime_only: bool) -> str - # 获取 Python 版本要求 + 启用模块: python, uv, venv, requirements - _parse_version(version: str) -> tuple[int, ...] - # 解析版本号字符串 + 目标模块: python, uv, venv, requirements - _check_python_version(required: str) -> bool - # 检查 Python 版本 + [python] 配置: + min_version: 3.11 +✓ python: 3.14.2 (>=3.11) + [uv] 配置: + (无配置) +✓ uv: uv 0.9.16 + [venv] 配置: + path: .venv + path (绝对): /home/user/myproject/.venv + path (存在): 是 +✓ venv: .venv + [requirements] 配置: + path: requirements.txt + path (绝对): /home/user/myproject/requirements.txt + path (存在): 是 +✓ requirements: requirements.txt +✓ 环境就绪 (...) +``` - _check_uv() -> str | None - # 检查 uv,返回版本号或 None +### 6.4 aide env ensure --runtime - _ensure_venv(venv_path: Path) -> bool - # 确保虚拟环境存在 +``` +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✓ 环境就绪 (python:3.14.2, uv:uv 0.9.16) +``` - _ensure_requirements_file(req_path: Path) -> None - # 确保 requirements.txt 存在 +### 6.5 aide env ensure --all - _install_requirements(venv_path: Path, req_path: Path) -> bool - # 安装依赖 +``` +✓ python: 3.14.2 (>=3.11) +✓ uv: uv 0.9.16 +✓ venv: .venv +✓ requirements: requirements.txt ``` --- -## 六、依赖 +## 七、代码结构 -| 依赖项 | 类型 | 说明 | -|--------|------|------| -| ConfigManager | 内部模块 | 配置读写 | -| output | 内部模块 | 输出格式化 | -| uv | 外部工具 | 虚拟环境和依赖管理 | +``` +aide/env/ +├── __init__.py +├── manager.py # 环境管理器主入口 +├── registry.py # 模块注册表 +└── modules/ + ├── __init__.py + ├── base.py # 模块基类 + ├── python.py # Python 模块 + ├── uv.py # uv 模块 + ├── venv.py # venv 模块 + └── requirements.py # requirements 模块 +``` + +### 7.1 模块基类 + +```python +class BaseModule(ABC): + @property + @abstractmethod + def info(self) -> ModuleInfo: ... + + @abstractmethod + def check(self, config: dict, root: Path) -> CheckResult: ... + + def ensure(self, config: dict, root: Path) -> CheckResult: ... + + def validate_config(self, config: dict) -> tuple[bool, str | None]: ... +``` + +### 7.2 添加新模块 + +1. 在 `aide/env/modules/` 创建模块文件 +2. 继承 `BaseModule` 实现 `info` 和 `check` 方法 +3. 如支持修复,实现 `ensure` 方法 +4. 导出 `module` 实例 +5. 在 `registry.py` 的 `register_builtin_modules()` 中注册 --- -## 七、被依赖 - -| 依赖方 | 说明 | -|--------|------| -| /aide:init | 调用 env ensure --runtime 和 env ensure | -| aide init | 内部可能调用 env 检查 | - ---- - -## 八、修改指南 - -### 8.1 修改检测逻辑 - -1. 更新本文档的业务流程图 -2. 修改 `aide/env/ensure.py` -3. 如有新的输出,更新输出示例 - -### 8.2 添加新的检测项 - -1. 在本文档添加检测项说明 -2. 在 `EnvManager` 添加对应方法 -3. 在 `ensure()` 中调用 -4. 更新 [aide skill 设计文档](../../../aide-marketplace/aide-plugin/docs/skill/aide.md) - -### 8.3 修改配置依赖 - -1. 更新本文档的"配置依赖"章节 -2. 修改代码实现 -3. 同步更新 [配置格式文档](../formats/config.md) - ---- - -## 九、相关文档 +## 八、相关文档 - [program 导览](../README.md) - [配置格式文档](../formats/config.md) - [aide skill 设计文档](../../../aide-marketplace/aide-plugin/docs/skill/aide.md) -- [/aide:init 命令设计](../../../aide-marketplace/aide-plugin/docs/commands/init.md) diff --git a/aide-program/docs/formats/config.md b/aide-program/docs/formats/config.md index c15b97c..adbeec1 100644 --- a/aide-program/docs/formats/config.md +++ b/aide-program/docs/formats/config.md @@ -32,10 +32,22 @@ use_uv = true # 是否使用 uv 管理依赖 source = "task-now.md" # 任务原文档默认路径 spec = "task-spec.md" # 任务细则文档默认路径 -# env: 虚拟环境与依赖配置 +# env: 环境模块配置 [env] -venv = ".venv" # 虚拟环境路径 -requirements = "requirements.txt" # 依赖文件路径 +# 启用的模块列表 +modules = ["python", "uv", "venv", "requirements"] + +# Python 版本要求(可选,默认使用 runtime.python_min) +# [env.python] +# min_version = "3.11" + +# 虚拟环境配置(类型B模块,必须配置) +[env.venv] +path = ".venv" + +# 依赖文件配置(类型B模块,必须配置) +[env.requirements] +path = "requirements.txt" # flow: 流程配置 [flow] @@ -67,18 +79,44 @@ phases = ["task-optimize", "flow-design", "impl", "verify", "docs", "finish"] **使用场景**: - `/aide:prep` 未传参数时,使用 `source` 作为默认路径 - `/aide:exec` 未传参数时,使用 `spec` 作为默认路径 -- `aide env ensure` 输出这两个路径供 LLM 记录 ### 4.3 [env] 环境配置 +#### 4.3.1 模块列表 + | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| -| `venv` | string | `".venv"` | 虚拟环境目录路径 | -| `requirements` | string | `"requirements.txt"` | 依赖文件路径 | +| `modules` | array | `["python", "uv", "venv", "requirements"]` | 启用的环境检测模块 | + +**可用模块**: +- `python` - Python 解释器版本检测 +- `uv` - uv 包管理器检测 +- `venv` - Python 虚拟环境管理 +- `requirements` - Python 依赖管理 + +#### 4.3.2 模块配置 + +**类型A模块(可选配置)**: + +```toml +[env.python] +min_version = "3.11" # Python 最低版本,默认使用 runtime.python_min +``` + +**类型B模块(必须配置)**: + +```toml +[env.venv] +path = ".venv" # 虚拟环境目录路径 + +[env.requirements] +path = "requirements.txt" # 依赖文件路径 +``` **使用场景**: -- `aide env ensure` 检查/创建虚拟环境 -- `aide env ensure` 安装依赖 +- `aide env ensure` 按 `modules` 列表检测环境 +- `aide env list` 显示所有可用模块及启用状态 +- `aide env ensure --modules X,Y` 检测指定模块 ### 4.4 [flow] 流程配置 @@ -105,8 +143,11 @@ aide config get aide config get task.source # 输出: → task.source = 'task-now.md' -aide config get flow.phases -# 输出: → flow.phases = ['task-optimize', 'flow-design', 'impl', 'verify', 'docs', 'finish'] +aide config get env.modules +# 输出: → env.modules = ['python', 'uv', 'venv', 'requirements'] + +aide config get env.venv.path +# 输出: → env.venv.path = '.venv' aide config get runtime.python_min # 输出: → runtime.python_min = '3.11' @@ -123,8 +164,8 @@ aide config set aide config set task.source "my-task.md" # 输出: ✓ 已更新 task.source = 'my-task.md' -aide config set runtime.python_min "3.12" -# 输出: ✓ 已更新 runtime.python_min = '3.12' +aide config set env.venv.path ".venv-dev" +# 输出: ✓ 已更新 env.venv.path = '.venv-dev' ``` **值类型自动解析**: @@ -149,18 +190,63 @@ aide config set runtime.python_min "3.12" - 配置项不存在时,`aide config get` 输出警告 - 建议先执行 `aide init` 确保配置文件存在 +### 6.3 模块配置规则 + +- 类型A模块(python, uv):配置可选,有默认行为 +- 类型B模块(venv, requirements):如果在 `modules` 列表中启用,必须有对应配置 +- 启用的B类模块无配置时,`aide env ensure` 会报错 + --- -## 七、扩展配置 +## 七、配置兼容性 -### 7.1 添加新配置项 +### 7.1 旧格式支持 + +aide 兼容旧版配置格式: + +```toml +[env] +venv = ".venv" +requirements = "requirements.txt" +``` + +读取时自动转换为新格式: + +```toml +[env.venv] +path = ".venv" + +[env.requirements] +path = "requirements.txt" +``` + +### 7.2 默认模块列表 + +如果配置中没有 `env.modules` 字段,使用默认值: + +```toml +modules = ["python", "uv", "venv", "requirements"] +``` + +--- + +## 八、扩展配置 + +### 8.1 添加新配置项 1. 在本文档添加字段说明 2. 更新 `ConfigManager` 中的 `DEFAULT_CONFIG` 3. 在相关代码中读取新配置 4. 更新相关设计文档 -### 7.2 配置项命名规范 +### 8.2 添加新环境模块 + +1. 在 `aide/env/modules/` 创建模块文件 +2. 在 `registry.py` 注册模块 +3. 更新本文档的模块列表 +4. 更新 `aide env` 设计文档 + +### 8.3 配置项命名规范 - 使用小写字母和下划线 - 使用点号分隔层级:`section.key` @@ -168,7 +254,7 @@ aide config set runtime.python_min "3.12" --- -## 八、相关文档 +## 九、相关文档 - [program 导览](../README.md) - [aide init 设计](../commands/init.md) diff --git a/aide-program/requirements.txt b/aide-program/requirements.txt new file mode 100644 index 0000000..65d9e0c --- /dev/null +++ b/aide-program/requirements.txt @@ -0,0 +1 @@ +# 在此添加依赖