From b2c26cb9dbf3f46ed90e7ae5cf316ce8d9a926a7 Mon Sep 17 00:00:00 2001 From: "sayurinana(vm)" Date: Sat, 13 Dec 2025 04:37:41 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=83=20docs:=20=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aide-program/README.md | 346 +++++++++++ aide-program/docs/01-入口脚本设计.md | 554 ++++++++++++++++++ aide-program/docs/02-aide-init设计.md | 545 +++++++++++++++++ aide-program/docs/03-aide-env设计.md | 599 +++++++++++++++++++ aide-program/docs/04-aide-config设计.md | 745 ++++++++++++++++++++++++ aide-program/docs/05-配置文件规范.md | 698 ++++++++++++++++++++++ aide-program/docs/06-输出格式规范.md | 428 ++++++++++++++ aide-program/docs/07-测试规范.md | 708 ++++++++++++++++++++++ reply/re-04.md | 5 + 9 files changed, 4628 insertions(+) create mode 100644 aide-program/README.md create mode 100644 aide-program/docs/01-入口脚本设计.md create mode 100644 aide-program/docs/02-aide-init设计.md create mode 100644 aide-program/docs/03-aide-env设计.md create mode 100644 aide-program/docs/04-aide-config设计.md create mode 100644 aide-program/docs/05-配置文件规范.md create mode 100644 aide-program/docs/06-输出格式规范.md create mode 100644 aide-program/docs/07-测试规范.md create mode 100644 reply/re-04.md diff --git a/aide-program/README.md b/aide-program/README.md new file mode 100644 index 0000000..78ee37d --- /dev/null +++ b/aide-program/README.md @@ -0,0 +1,346 @@ +# Aide 程序系统开发文档 + +## 一、项目概述 + +### 1.1 项目定位 + +Aide 是一套命令行工具集,用于支持 AI 辅助开发工作流。本项目实现 aide 程序的核心基础功能,包括: + +- 项目初始化(aide init) +- 环境管理(aide env) +- 配置管理(aide config) + +**注意**:本阶段不包含 `aide flow`(进度追踪)和 `aide decide`(待定项确认)的实现,这两个功能将在后续阶段开发。 + +### 1.2 技术栈要求 + +- **Python 3.10+**:主要编程语言 +- **Shell 脚本**:跨平台入口封装(aide.sh / aide.bat) +- **TOML**:配置文件格式 +- **标准库优先**:尽量使用 Python 标准库,减少外部依赖 + +### 1.3 设计原则 + +1. **确定性**:相同输入产生相同输出,避免不确定性 +2. **精简输出**:成功时输出极简,失败时输出详细 +3. **幂等性**:重复执行不产生副作用 +4. **自文档化**:配置文件包含详细注释 +5. **跨平台**:支持 Linux、macOS、Windows + +--- + +## 二、目录结构 + +``` +aide-program/ +├── README.md # 本文件:项目总览 +├── docs/ # 详细设计文档 +│ ├── 01-入口脚本设计.md # aide.sh / aide.bat 设计 +│ ├── 02-aide-init设计.md # aide init 命令设计 +│ ├── 03-aide-env设计.md # aide env 命令设计 +│ ├── 04-aide-config设计.md # aide config 命令设计 +│ ├── 05-配置文件规范.md # config.toml 格式规范 +│ ├── 06-输出格式规范.md # 统一输出格式规范 +│ └── 07-测试规范.md # 测试要求和用例 +├── src/ # 源代码目录(开发时创建) +│ ├── aide.sh # Linux/macOS 入口 +│ ├── aide.bat # Windows 入口 +│ ├── main.py # Python 主入口 +│ ├── core/ # 核心模块 +│ │ ├── __init__.py +│ │ ├── config.py # 配置读写 +│ │ └── output.py # 输出格式化 +│ ├── commands/ # 命令实现 +│ │ ├── __init__.py +│ │ ├── init.py # aide init +│ │ ├── env.py # aide env +│ │ └── config_cmd.py # aide config +│ └── utils/ # 工具函数 +│ ├── __init__.py +│ └── validators.py # 验证函数 +└── tests/ # 测试目录(开发时创建) + ├── test_init.py + ├── test_env.py + └── test_config.py +``` + +--- + +## 三、命令清单 + +### 3.1 本阶段实现的命令 + +| 命令 | 功能 | 优先级 | +|------|------|--------| +| `aide init` | 初始化 .aide 目录和配置文件 | P0 | +| `aide env ensure` | 检测并修复项目开发环境 | P0 | +| `aide env ensure --runtime` | 检测 aide 运行时环境 | P0 | +| `aide config get ` | 获取配置值 | P1 | +| `aide config set ` | 设置配置值 | P1 | + +### 3.2 后续阶段实现的命令 + +| 命令 | 功能 | 说明 | +|------|------|------| +| `aide flow ...` | 进度追踪和 git 集成 | 后续实现 | +| `aide decide ...` | 待定项确认 Web 服务 | 后续实现 | + +--- + +## 四、开发流程 + +### 4.1 阅读顺序 + +建议按以下顺序阅读文档: + +1. **README.md**(本文件)- 了解项目全貌 +2. **docs/06-输出格式规范.md** - 理解统一输出格式 +3. **docs/05-配置文件规范.md** - 理解配置文件结构 +4. **docs/01-入口脚本设计.md** - 理解命令调用流程 +5. **docs/02-aide-init设计.md** - 实现 aide init +6. **docs/03-aide-env设计.md** - 实现 aide env +7. **docs/04-aide-config设计.md** - 实现 aide config +8. **docs/07-测试规范.md** - 编写测试用例 + +### 4.2 开发步骤 + +1. **搭建基础框架** + - 创建目录结构 + - 实现入口脚本(aide.sh / aide.bat) + - 实现 main.py 命令分发 + - 实现 core/output.py 输出格式化 + +2. **实现核心模块** + - 实现 core/config.py 配置读写 + - 实现 utils/validators.py 验证函数 + +3. **实现命令** + - 实现 aide init + - 实现 aide env ensure --runtime + - 实现 aide env ensure + - 实现 aide config get/set + +4. **编写测试** + - 单元测试 + - 集成测试 + - 跨平台测试 + +5. **文档和打包** + - 编写用户文档 + - 准备分发包 + +### 4.3 质量要求 + +1. **代码质量** + - 遵循 PEP 8 代码规范 + - 函数和类必须有文档字符串 + - 关键逻辑必须有注释 + +2. **测试覆盖** + - 核心功能测试覆盖率 ≥ 80% + - 所有错误路径必须有测试用例 + - 跨平台兼容性测试 + +3. **用户体验** + - 输出信息清晰易懂 + - 错误提示包含解决建议 + - 命令执行速度快(< 1秒) + +--- + +## 五、核心概念 + +### 5.1 输出格式 + +所有 aide 命令遵循统一的输出格式: + +| 前缀 | 含义 | 使用场景 | +|------|------|---------| +| `✓` | 成功 | 操作成功完成 | +| `⚠` | 警告 | 有问题但可继续 | +| `✗` | 错误 | 操作失败 | +| `→` | 信息 | 进行中或提示信息 | + +**静默原则**:无输出 = 正常完成(适用于幂等操作) + +### 5.2 配置文件 + +- **位置**:`.aide/config.toml` +- **格式**:TOML +- **特点**:自文档化,包含详细注释 +- **访问**:通过 `aide config` 命令或 core/config.py 模块 + +### 5.3 数据存储 + +所有 aide 数据统一存放在项目根目录的 `.aide/` 下: + +``` +.aide/ +├── config.toml # 项目配置 +├── flow-status.json # 任务进度(后续实现) +├── decisions/ # 待定项记录(后续实现) +└── logs/ # 操作日志(可选) +``` + +### 5.4 错误处理 + +1. **预期错误**:返回明确的错误信息和建议 +2. **非预期错误**:记录详细日志,返回简化错误信息 +3. **退出码**: + - 0:成功 + - 1:一般错误 + - 2:参数错误 + - 3:环境错误 + +--- + +## 六、依赖管理 + +### 6.1 Python 依赖 + +**核心依赖**(必需): +- Python 3.10+ +- 标准库:os, sys, pathlib, subprocess, json, configparser + +**可选依赖**: +- tomli / tomllib(Python 3.11+ 内置):TOML 解析 +- tomli-w:TOML 写入 + +### 6.2 系统依赖 + +**必需**: +- Python 3.10+ + +**可选**(用于项目环境检测): +- git +- uv / pip + +--- + +## 七、交付物 + +### 7.1 必需交付物 + +1. **源代码** + - 完整的 Python 代码 + - 入口脚本(aide.sh / aide.bat) + - 所有必需的模块和工具函数 + +2. **测试代码** + - 单元测试 + - 集成测试 + - 测试数据和 fixtures + +3. **文档** + - 用户使用文档 + - 开发者文档(如有特殊设计) + - CHANGELOG + +### 7.2 可选交付物 + +1. **打包脚本** + - 用于生成分发包的脚本 + - 安装说明 + +2. **CI/CD 配置** + - GitHub Actions 或其他 CI 配置 + - 自动化测试流程 + +--- + +## 八、注意事项 + +### 8.1 设计约束 + +1. **不要实现 aide flow**:进度追踪功能后续实现 +2. **不要实现 aide decide**:待定项确认功能后续实现 +3. **不要硬编码路径**:所有路径通过配置或参数传入 +4. **不要假设环境**:所有环境依赖必须检测和验证 + +### 8.2 兼容性要求 + +1. **Python 版本**:支持 3.10+ +2. **操作系统**:Linux、macOS、Windows +3. **路径分隔符**:使用 pathlib 处理跨平台路径 +4. **编码**:统一使用 UTF-8 + +### 8.3 安全考虑 + +1. **配置文件权限**:不存储敏感信息 +2. **命令注入**:所有外部命令调用必须参数化 +3. **路径遍历**:验证所有文件路径 +4. **输入验证**:验证所有用户输入 + +--- + +## 九、参考资料 + +### 9.1 项目文档 + +- `../aide-requirements.md`:Aide 系统需求规格 +- `../aide-marketplace/aide-plugin/`:Commands 和 Skills 定义 +- `../discuss/`:设计讨论和决策记录 + +### 9.2 外部资源 + +- [TOML 规范](https://toml.io/) +- [PEP 8 代码规范](https://pep8.org/) +- [Python pathlib 文档](https://docs.python.org/3/library/pathlib.html) + +--- + +## 十、联系方式 + +如有疑问或需要澄清,请: + +1. 查阅 `docs/` 目录下的详细设计文档 +2. 参考 `../aide-requirements.md` 了解整体设计 +3. 查看 `../discuss/` 目录了解设计决策 + +--- + +## 附录:快速开始 + +### A.1 验证环境 + +```bash +# 检查 Python 版本 +python3 --version # 应该 >= 3.10 + +# 检查必要的库 +python3 -c "import tomllib" # Python 3.11+ +# 或 +python3 -c "import tomli" # Python 3.10 +``` + +### A.2 创建开发环境 + +```bash +# 创建虚拟环境 +cd aide-program +python3 -m venv .venv + +# 激活虚拟环境 +source .venv/bin/activate # Linux/macOS +# 或 +.venv\Scripts\activate # Windows + +# 安装依赖(如需要) +pip install tomli tomli-w +``` + +### A.3 运行测试 + +```bash +# 运行所有测试 +python3 -m pytest tests/ + +# 运行特定测试 +python3 -m pytest tests/test_init.py +``` + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 +**状态**:待开发 diff --git a/aide-program/docs/01-入口脚本设计.md b/aide-program/docs/01-入口脚本设计.md new file mode 100644 index 0000000..31c93ae --- /dev/null +++ b/aide-program/docs/01-入口脚本设计.md @@ -0,0 +1,554 @@ +# 入口脚本设计 + +## 一、概述 + +### 1.1 设计目标 + +提供跨平台的统一命令行入口,使用户可以通过 `aide ` 的方式调用所有功能。 + +### 1.2 入口脚本清单 + +| 脚本 | 平台 | 说明 | +|------|------|------| +| `aide.sh` | Linux/macOS | Shell 脚本入口 | +| `aide.bat` | Windows | 批处理脚本入口 | +| `main.py` | 所有平台 | Python 主程序 | + +--- + +## 二、aide.sh 设计(Linux/macOS) + +### 2.1 功能要求 + +1. **定位 Python**:查找可用的 Python 3.10+ 解释器 +2. **定位脚本目录**:确定 main.py 的位置 +3. **传递参数**:将所有命令行参数传递给 Python 程序 +4. **处理错误**:Python 未找到时给出明确提示 + +### 2.2 实现示例 + +```bash +#!/usr/bin/env bash +# Aide 命令行工具入口脚本(Linux/macOS) +# 版本: 1.0 + +set -e # 遇到错误立即退出 + +# 获取脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Python 主程序路径 +MAIN_PY="${SCRIPT_DIR}/main.py" + +# 查找 Python 解释器 +find_python() { + # 尝试查找 Python 3.10+ + for cmd in python3.12 python3.11 python3.10 python3 python; do + if command -v "$cmd" &> /dev/null; then + # 检查版本 + version=$("$cmd" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') + major=$(echo "$version" | cut -d. -f1) + minor=$(echo "$version" | cut -d. -f2) + + if [ "$major" -eq 3 ] && [ "$minor" -ge 10 ]; then + echo "$cmd" + return 0 + fi + fi + done + + return 1 +} + +# 查找 Python +PYTHON=$(find_python) + +if [ -z "$PYTHON" ]; then + echo "✗ Python 3.10+ 未找到" >&2 + echo " 建议: 安装 Python 3.10 或更高版本" >&2 + echo " 文档: https://www.python.org/downloads/" >&2 + exit 3 +fi + +# 检查 main.py 是否存在 +if [ ! -f "$MAIN_PY" ]; then + echo "✗ 找不到 main.py" >&2 + echo " 位置: $MAIN_PY" >&2 + exit 1 +fi + +# 执行 Python 程序 +exec "$PYTHON" "$MAIN_PY" "$@" +``` + +### 2.3 安装方式 + +```bash +# 方式1:添加到 PATH +export PATH="/path/to/aide:$PATH" + +# 方式2:创建符号链接 +ln -s /path/to/aide/aide.sh /usr/local/bin/aide + +# 方式3:添加别名 +alias aide='/path/to/aide/aide.sh' +``` + +--- + +## 三、aide.bat 设计(Windows) + +### 3.1 功能要求 + +1. **定位 Python**:查找可用的 Python 3.10+ 解释器 +2. **定位脚本目录**:确定 main.py 的位置 +3. **传递参数**:将所有命令行参数传递给 Python 程序 +4. **处理错误**:Python 未找到时给出明确提示 + +### 3.2 实现示例 + +```batch +@echo off +REM Aide 命令行工具入口脚本(Windows) +REM 版本: 1.0 + +setlocal enabledelayedexpansion + +REM 获取脚本所在目录 +set "SCRIPT_DIR=%~dp0" + +REM Python 主程序路径 +set "MAIN_PY=%SCRIPT_DIR%main.py" + +REM 查找 Python 解释器 +set "PYTHON=" +for %%p in (python3.12 python3.11 python3.10 python3 python py) do ( + where %%p >nul 2>&1 + if !errorlevel! equ 0 ( + REM 检查版本 + for /f "delims=" %%v in ('%%p -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"') do ( + set "version=%%v" + ) + + REM 解析版本号 + for /f "tokens=1,2 delims=." %%a in ("!version!") do ( + set "major=%%a" + set "minor=%%b" + ) + + REM 检查是否满足要求(3.10+) + if !major! equ 3 if !minor! geq 10 ( + set "PYTHON=%%p" + goto :found + ) + ) +) + +:found +if "%PYTHON%"=="" ( + echo ✗ Python 3.10+ 未找到 >&2 + echo 建议: 安装 Python 3.10 或更高版本 >&2 + echo 文档: https://www.python.org/downloads/ >&2 + exit /b 3 +) + +REM 检查 main.py 是否存在 +if not exist "%MAIN_PY%" ( + echo ✗ 找不到 main.py >&2 + echo 位置: %MAIN_PY% >&2 + exit /b 1 +) + +REM 执行 Python 程序 +"%PYTHON%" "%MAIN_PY%" %* +exit /b %errorlevel% +``` + +### 3.3 安装方式 + +```batch +REM 方式1:添加到 PATH +set PATH=C:\path\to\aide;%PATH% + +REM 方式2:创建批处理文件到系统目录 +copy aide.bat C:\Windows\System32\aide.bat +``` + +--- + +## 四、main.py 设计 + +### 4.1 功能要求 + +1. **命令分发**:根据第一个参数分发到对应的命令处理函数 +2. **参数解析**:解析命令行参数 +3. **错误处理**:统一的错误处理和退出码 +4. **帮助信息**:提供 `--help` 和 `--version` 支持 + +### 4.2 实现框架 + +```python +#!/usr/bin/env python3 +""" +Aide 命令行工具主程序 +版本: 1.0 +""" + +import sys +from pathlib import Path + +# 添加 src 目录到 Python 路径 +SCRIPT_DIR = Path(__file__).parent +sys.path.insert(0, str(SCRIPT_DIR)) + +from core.output import ok, warn, err, info +from commands.init import cmd_init +from commands.env import cmd_env +from commands.config_cmd import cmd_config + + +VERSION = "1.0.0" + + +def show_help(): + """显示帮助信息""" + help_text = """Aide - AI 辅助开发工作流工具 + +用法: + aide [options] + +命令: + init 初始化 .aide 目录和配置文件 + env ensure 检测并修复项目开发环境 + env ensure --runtime 检测 aide 运行时环境 + config get 获取配置值 + config set 设置配置值 + +选项: + -h, --help 显示帮助信息 + -v, --version 显示版本信息 + +示例: + aide init + aide env ensure + aide config get task.source + aide config set task.source "new-task.md" + +文档: https://github.com/your-org/aide +""" + print(help_text) + + +def show_version(): + """显示版本信息""" + print(f"Aide v{VERSION}") + + +def main(): + """主函数""" + # 解析参数 + args = sys.argv[1:] + + # 无参数或帮助 + if not args or args[0] in ["-h", "--help", "help"]: + show_help() + return 0 + + # 版本信息 + if args[0] in ["-v", "--version", "version"]: + show_version() + return 0 + + # 获取命令 + command = args[0] + + try: + # 命令分发 + if command == "init": + return cmd_init(args[1:]) + elif command == "env": + return cmd_env(args[1:]) + elif command == "config": + return cmd_config(args[1:]) + else: + err( + f"未知命令: {command}", + [ + "可用命令: init, env, config", + "使用 'aide --help' 查看帮助" + ] + ) + return 2 + + except KeyboardInterrupt: + print("\n") + info("操作已取消") + return 130 # 128 + SIGINT(2) + + except Exception as e: + err( + "命令执行失败", + [ + f"原因: {str(e)}", + "使用 'aide --help' 查看帮助" + ] + ) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) +``` + +### 4.3 命令处理函数接口 + +每个命令处理函数应该遵循以下接口: + +```python +def cmd_(args: list[str]) -> int: + """命令处理函数 + + Args: + args: 命令参数列表(不包含命令本身) + + Returns: + 退出码(0 表示成功) + """ + pass +``` + +--- + +## 五、参数解析 + +### 5.1 简单参数解析 + +对于简单的命令,可以手动解析参数: + +```python +def cmd_env(args: list[str]) -> int: + """env 命令处理""" + if not args: + err("缺少子命令", ["使用 'aide env ensure' 检测环境"]) + return 2 + + subcommand = args[0] + + if subcommand == "ensure": + # 检查 --runtime 参数 + runtime_only = "--runtime" in args + return env_ensure(runtime_only=runtime_only) + else: + err(f"未知子命令: {subcommand}", ["可用子命令: ensure"]) + return 2 +``` + +### 5.2 使用 argparse(可选) + +对于复杂的命令,可以使用 argparse: + +```python +import argparse + +def cmd_config(args: list[str]) -> int: + """config 命令处理""" + parser = argparse.ArgumentParser( + prog="aide config", + description="配置管理" + ) + + subparsers = parser.add_subparsers(dest="subcommand") + + # get 子命令 + parser_get = subparsers.add_parser("get", help="获取配置值") + parser_get.add_argument("key", help="配置键") + + # set 子命令 + parser_set = subparsers.add_parser("set", help="设置配置值") + parser_set.add_argument("key", help="配置键") + parser_set.add_argument("value", help="配置值") + + try: + parsed = parser.parse_args(args) + except SystemExit: + return 2 + + if parsed.subcommand == "get": + return config_get(parsed.key) + elif parsed.subcommand == "set": + return config_set(parsed.key, parsed.value) + else: + parser.print_help() + return 2 +``` + +--- + +## 六、错误处理 + +### 6.1 异常捕获 + +```python +def main(): + """主函数""" + try: + # 命令执行 + return execute_command() + + except KeyboardInterrupt: + # Ctrl+C 中断 + print("\n") + info("操作已取消") + return 130 + + except FileNotFoundError as e: + # 文件不存在 + err("文件不存在", [f"路径: {e.filename}"]) + return 5 + + except PermissionError as e: + # 权限错误 + err("权限不足", [f"路径: {e.filename}"]) + return 1 + + except Exception as e: + # 其他错误 + err("命令执行失败", [f"原因: {str(e)}"]) + return 1 +``` + +### 6.2 退出码规范 + +| 退出码 | 含义 | +|-------|------| +| 0 | 成功 | +| 1 | 一般错误 | +| 2 | 参数错误 | +| 3 | 环境错误 | +| 4 | 配置错误 | +| 5 | 文件错误 | +| 130 | 用户中断(Ctrl+C) | + +--- + +## 七、测试 + +### 7.1 入口脚本测试 + +```bash +# 测试 aide.sh +./aide.sh --version +./aide.sh --help +./aide.sh init +./aide.sh env ensure + +# 测试 aide.bat(Windows) +aide.bat --version +aide.bat --help +aide.bat init +aide.bat env ensure +``` + +### 7.2 main.py 测试 + +```python +def test_main_help(capsys): + """测试帮助信息""" + sys.argv = ["aide", "--help"] + result = main() + + assert result == 0 + captured = capsys.readouterr() + assert "用法:" in captured.out + +def test_main_version(capsys): + """测试版本信息""" + sys.argv = ["aide", "--version"] + result = main() + + assert result == 0 + captured = capsys.readouterr() + assert "Aide v" in captured.out + +def test_main_unknown_command(capsys): + """测试未知命令""" + sys.argv = ["aide", "unknown"] + result = main() + + assert result == 2 + captured = capsys.readouterr() + assert "未知命令" in captured.out +``` + +--- + +## 八、安装和分发 + +### 8.1 目录结构 + +``` +aide/ +├── aide.sh # Linux/macOS 入口 +├── aide.bat # Windows 入口 +├── main.py # Python 主程序 +├── core/ # 核心模块 +├── commands/ # 命令实现 +└── utils/ # 工具函数 +``` + +### 8.2 安装脚本(可选) + +```bash +#!/usr/bin/env bash +# install.sh - Aide 安装脚本 + +set -e + +INSTALL_DIR="${HOME}/.local/bin" + +# 创建安装目录 +mkdir -p "$INSTALL_DIR" + +# 复制文件 +cp -r aide "$INSTALL_DIR/" + +# 创建符号链接 +ln -sf "$INSTALL_DIR/aide/aide.sh" "$INSTALL_DIR/aide" + +# 添加到 PATH +if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then + echo "export PATH=\"$INSTALL_DIR:\$PATH\"" >> ~/.bashrc + echo "✓ 已添加到 PATH" + echo " 请运行: source ~/.bashrc" +fi + +echo "✓ Aide 安装完成" +echo " 使用 'aide --help' 查看帮助" +``` + +--- + +## 九、总结 + +### 9.1 核心要点 + +1. 提供跨平台的统一入口 +2. 自动查找合适的 Python 解释器 +3. 统一的命令分发和错误处理 +4. 清晰的帮助和版本信息 + +### 9.2 实现检查清单 + +- [ ] 实现 aide.sh(Linux/macOS) +- [ ] 实现 aide.bat(Windows) +- [ ] 实现 main.py 命令分发 +- [ ] 实现帮助和版本信息 +- [ ] 实现错误处理和退出码 +- [ ] 编写入口脚本测试 +- [ ] 验证跨平台兼容性 +- [ ] 编写安装脚本(可选) + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/aide-program/docs/02-aide-init设计.md b/aide-program/docs/02-aide-init设计.md new file mode 100644 index 0000000..a6886f6 --- /dev/null +++ b/aide-program/docs/02-aide-init设计.md @@ -0,0 +1,545 @@ +# aide init 设计 + +## 一、命令概述 + +### 1.1 功能定位 + +`aide init` 命令用于初始化项目的 aide 工作环境,创建必要的目录和配置文件。 + +### 1.2 执行时机 + +- 首次在项目中使用 aide 时 +- 配置文件丢失需要重新创建时 +- 作为 `/aide:init` 命令的一部分 + +### 1.3 命令格式 + +```bash +aide init [options] +``` + +**选项**: +- 无(当前版本不需要选项) + +--- + +## 二、功能需求 + +### 2.1 核心功能 + +1. **创建 .aide 目录** + - 检查目录是否存在 + - 不存在则创建 + - 已存在则跳过(幂等性) + +2. **生成配置文件** + - 创建 `config.toml` + - 包含详细注释 + - 使用合理的默认值 + +3. **更新 .gitignore** + - 检查 `.gitignore` 是否存在 + - 添加 `.aide/` 到忽略列表 + - 避免重复添加 + +### 2.2 输出要求 + +**首次初始化**: +``` +✓ 已创建 .aide/ 目录 +✓ 已生成默认配置 +✓ 已添加 .aide/ 到 .gitignore +``` + +**重复初始化**: +``` +⚠ .aide/ 目录已存在 +⚠ 配置文件已存在,跳过生成 +``` + +**部分已存在**: +``` +⚠ .aide/ 目录已存在 +✓ 已生成默认配置 +✓ 已添加 .aide/ 到 .gitignore +``` + +--- + +## 三、实现设计 + +### 3.1 函数接口 + +```python +def cmd_init(args: list[str]) -> int: + """aide init 命令处理 + + Args: + args: 命令参数(当前版本为空) + + Returns: + 退出码(0 表示成功) + """ + pass +``` + +### 3.2 实现流程 + +```python +from pathlib import Path +from core.output import ok, warn, err +from core.config import generate_default_config + +def cmd_init(args: list[str]) -> int: + """aide init 命令实现""" + + # 1. 获取项目根目录(当前工作目录) + project_root = Path.cwd() + aide_dir = project_root / ".aide" + config_path = aide_dir / "config.toml" + gitignore_path = project_root / ".gitignore" + + # 2. 创建 .aide 目录 + if aide_dir.exists(): + warn(".aide/ 目录已存在") + else: + aide_dir.mkdir(parents=True, exist_ok=True) + ok("已创建 .aide/ 目录") + + # 3. 生成配置文件 + if config_path.exists(): + warn("配置文件已存在,跳过生成") + else: + config_content = generate_default_config() + config_path.write_text(config_content, encoding="utf-8") + ok("已生成默认配置") + + # 4. 更新 .gitignore + gitignore_updated = update_gitignore(gitignore_path) + if gitignore_updated: + ok("已添加 .aide/ 到 .gitignore") + + return 0 +``` + +### 3.3 辅助函数 + +#### 3.3.1 更新 .gitignore + +```python +def update_gitignore(gitignore_path: Path) -> bool: + """更新 .gitignore 文件 + + Args: + gitignore_path: .gitignore 文件路径 + + Returns: + 是否进行了更新 + """ + aide_ignore = ".aide/" + + # 读取现有内容 + if gitignore_path.exists(): + content = gitignore_path.read_text(encoding="utf-8") + lines = content.splitlines() + else: + lines = [] + + # 检查是否已存在 + if aide_ignore in lines or ".aide" in lines: + return False + + # 添加到末尾 + if lines and not lines[-1].strip(): + # 最后一行是空行,直接添加 + lines.append(aide_ignore) + else: + # 添加空行和 .aide/ + lines.extend(["", aide_ignore]) + + # 写回文件 + gitignore_path.write_text("\n".join(lines) + "\n", encoding="utf-8") + return True +``` + +#### 3.3.2 生成默认配置 + +```python +def generate_default_config() -> str: + """生成默认配置文件内容 + + Returns: + TOML 格式的配置内容 + """ + return '''# Aide 项目配置文件 +# 由 aide init 自动生成 +# 文档: https://github.com/your-org/aide + +[task] +# 任务原文档路径(prep 阶段使用) +# 可通过 /aide:prep [路径] 覆盖 +source = "task-now.md" + +# 任务细则文档路径(exec 阶段使用) +# 可通过 /aide:exec [路径] 覆盖 +spec = "task-spec.md" + +[env] +# 环境配置 + +[env.python] +# Python 版本要求(语义化版本) +# 格式: ">=3.10" 或 ">=3.10,<4.0" +version = ">=3.10" + +# 虚拟环境路径(相对于项目根目录) +venv = ".venv" + +[env.tools] +# 可选工具配置 + +# 是否需要 uv(Python 包管理器) +uv = false + +# 是否需要 git +git = true + +[flow] +# 流程追踪配置(aide flow 命令使用) + +# 环节列表(不建议修改) +phases = ["flow-design", "impl", "verify", "docs", "finish"] + +# PlantUML 流程图目录 +flowchart_dir = "program_flowchart" + +[decide] +# 待定项确认配置(aide decide 命令使用) + +# Web 服务端口 +port = 3721 + +# 决策记录保存目录 +decisions_dir = ".aide/decisions" + +[output] +# 输出配置 + +# 是否启用颜色输出 +# 可通过环境变量 NO_COLOR 禁用 +color = true + +# 输出语言(当前仅支持 zh-CN) +language = "zh-CN" +''' +``` + +--- + +## 四、错误处理 + +### 4.1 可能的错误 + +| 错误类型 | 处理方式 | 退出码 | +|---------|---------|--------| +| 无写入权限 | 显示错误信息和建议 | 1 | +| 磁盘空间不足 | 显示错误信息 | 1 | +| 配置文件格式错误 | 不应该发生(生成的内容固定) | - | + +### 4.2 错误处理示例 + +```python +def cmd_init(args: list[str]) -> int: + """aide init 命令实现(带错误处理)""" + + try: + project_root = Path.cwd() + aide_dir = project_root / ".aide" + + # 创建目录 + try: + aide_dir.mkdir(parents=True, exist_ok=True) + except PermissionError: + err( + "无法创建 .aide/ 目录", + [ + "原因: 权限不足", + f"位置: {aide_dir}", + "建议: 检查当前目录的写入权限" + ] + ) + return 1 + except OSError as e: + err( + "无法创建 .aide/ 目录", + [ + f"原因: {str(e)}", + f"位置: {aide_dir}" + ] + ) + return 1 + + # 生成配置文件 + config_path = aide_dir / "config.toml" + if not config_path.exists(): + try: + config_content = generate_default_config() + config_path.write_text(config_content, encoding="utf-8") + ok("已生成默认配置") + except OSError as e: + err( + "无法创建配置文件", + [ + f"原因: {str(e)}", + f"位置: {config_path}" + ] + ) + return 1 + else: + warn("配置文件已存在,跳过生成") + + # 更新 .gitignore + gitignore_path = project_root / ".gitignore" + try: + if update_gitignore(gitignore_path): + ok("已添加 .aide/ 到 .gitignore") + except OSError as e: + # .gitignore 更新失败不是致命错误 + warn(f"无法更新 .gitignore: {str(e)}") + + return 0 + + except Exception as e: + err( + "初始化失败", + [ + f"原因: {str(e)}", + "建议: 检查目录权限和磁盘空间" + ] + ) + return 1 +``` + +--- + +## 五、幂等性设计 + +### 5.1 幂等性要求 + +多次执行 `aide init` 应该: +1. 不破坏已有的配置 +2. 不重复添加 .gitignore 条目 +3. 给出清晰的提示信息 + +### 5.2 幂等性测试 + +```python +def test_init_idempotent(): + """测试 aide init 的幂等性""" + + # 第一次执行 + result1 = cmd_init([]) + assert result1 == 0 + + # 验证文件已创建 + assert Path(".aide").exists() + assert Path(".aide/config.toml").exists() + + # 第二次执行 + result2 = cmd_init([]) + assert result2 == 0 + + # 验证配置文件内容未改变 + config1 = Path(".aide/config.toml").read_text() + + # 第三次执行 + result3 = cmd_init([]) + assert result3 == 0 + + config2 = Path(".aide/config.toml").read_text() + assert config1 == config2 +``` + +--- + +## 六、测试用例 + +### 6.1 正常场景 + +```python +def test_init_success(tmp_path, monkeypatch): + """测试正常初始化""" + # 切换到临时目录 + monkeypatch.chdir(tmp_path) + + # 执行初始化 + result = cmd_init([]) + + # 验证退出码 + assert result == 0 + + # 验证目录创建 + assert (tmp_path / ".aide").exists() + assert (tmp_path / ".aide").is_dir() + + # 验证配置文件创建 + config_path = tmp_path / ".aide" / "config.toml" + assert config_path.exists() + assert config_path.is_file() + + # 验证配置文件内容 + content = config_path.read_text() + assert "[task]" in content + assert "[env]" in content + assert "source = " in content + + # 验证 .gitignore 更新 + gitignore_path = tmp_path / ".gitignore" + assert gitignore_path.exists() + gitignore_content = gitignore_path.read_text() + assert ".aide/" in gitignore_content +``` + +### 6.2 重复初始化 + +```python +def test_init_already_exists(tmp_path, monkeypatch, capsys): + """测试重复初始化""" + monkeypatch.chdir(tmp_path) + + # 第一次初始化 + cmd_init([]) + + # 第二次初始化 + result = cmd_init([]) + + # 验证退出码 + assert result == 0 + + # 验证输出包含警告 + captured = capsys.readouterr() + assert "已存在" in captured.out +``` + +### 6.3 权限错误 + +```python +def test_init_permission_error(tmp_path, monkeypatch, capsys): + """测试权限错误""" + monkeypatch.chdir(tmp_path) + + # 创建只读目录 + tmp_path.chmod(0o444) + + # 执行初始化 + result = cmd_init([]) + + # 恢复权限 + tmp_path.chmod(0o755) + + # 验证退出码 + assert result == 1 + + # 验证错误信息 + captured = capsys.readouterr() + assert "权限" in captured.out +``` + +### 6.4 .gitignore 已存在 + +```python +def test_init_gitignore_exists(tmp_path, monkeypatch): + """测试 .gitignore 已存在的情况""" + monkeypatch.chdir(tmp_path) + + # 创建已有的 .gitignore + gitignore_path = tmp_path / ".gitignore" + gitignore_path.write_text("*.pyc\n__pycache__/\n") + + # 执行初始化 + result = cmd_init([]) + + # 验证退出码 + assert result == 0 + + # 验证 .gitignore 内容 + content = gitignore_path.read_text() + assert "*.pyc" in content + assert ".aide/" in content + + # 验证不重复添加 + lines = content.splitlines() + aide_count = sum(1 for line in lines if ".aide" in line) + assert aide_count == 1 +``` + +--- + +## 七、集成测试 + +### 7.1 完整工作流测试 + +```python +def test_init_workflow(tmp_path, monkeypatch): + """测试完整的初始化工作流""" + monkeypatch.chdir(tmp_path) + + # 1. 执行初始化 + result = cmd_init([]) + assert result == 0 + + # 2. 验证可以读取配置 + from core.config import Config + config = Config(tmp_path) + config.load() + + # 3. 验证配置值 + assert config.get("task.source") == "task-now.md" + assert config.get("task.spec") == "task-spec.md" + assert config.get("env.python.version") == ">=3.10" + + # 4. 验证可以修改配置 + config.set("task.source", "new-task.md") + assert config.get("task.source") == "new-task.md" +``` + +--- + +## 八、性能要求 + +### 8.1 执行时间 + +- 正常情况:< 100ms +- 包含 .gitignore 更新:< 200ms + +### 8.2 资源占用 + +- 内存:< 10MB +- 磁盘空间:< 10KB(配置文件) + +--- + +## 九、总结 + +### 9.1 核心要点 + +1. 创建 .aide 目录和配置文件 +2. 自动更新 .gitignore +3. 幂等性设计,可重复执行 +4. 完善的错误处理 + +### 9.2 实现检查清单 + +- [ ] 实现 cmd_init 函数 +- [ ] 实现 generate_default_config 函数 +- [ ] 实现 update_gitignore 函数 +- [ ] 实现错误处理 +- [ ] 编写单元测试 +- [ ] 编写集成测试 +- [ ] 验证幂等性 +- [ ] 性能测试 + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/aide-program/docs/03-aide-env设计.md b/aide-program/docs/03-aide-env设计.md new file mode 100644 index 0000000..1fefd97 --- /dev/null +++ b/aide-program/docs/03-aide-env设计.md @@ -0,0 +1,599 @@ +# aide env 设计 + +## 一、命令概述 + +### 1.1 功能定位 + +`aide env` 命令用于检测和修复项目开发环境,确保所有必需的工具和依赖都已正确安装。 + +### 1.2 执行时机 + +- `/aide:init` 命令中调用(两次) +- 用户手动检查环境时 +- 环境配置变更后 + +### 1.3 命令格式 + +```bash +aide env ensure [--runtime] +``` + +**选项**: +- `--runtime`:仅检查 aide 运行时环境(不依赖配置文件) + +--- + +## 二、功能需求 + +### 2.1 aide env ensure --runtime + +**用途**:检查 aide 程序自身运行所需的环境 + +**检查项**: +1. Python 版本(>= 3.10) +2. 必需的 Python 库(tomli/tomllib, tomli-w) + +**特点**: +- 不读取项目配置文件 +- 在 `aide init` 之前执行 +- 失败时给出明确的安装建议 + +**输出示例**: + +成功: +``` +✓ 环境就绪 (python:3.12) +``` + +失败: +``` +✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) + 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 + 文档: https://www.python.org/downloads/ +``` + +### 2.2 aide env ensure + +**用途**:检查项目开发环境 + +**检查项**: +1. Python 版本(根据配置文件) +2. 虚拟环境(根据配置文件) +3. 可选工具(git, uv 等,根据配置文件) + +**特点**: +- 读取 `.aide/config.toml` +- 在 `aide init` 之后执行 +- 可以自动修复部分问题 + +**输出示例**: + +成功(无问题): +``` +✓ 环境就绪 (python:3.12, git:2.40.0) +``` + +成功(自动修复): +``` +⚠ 已修复: 创建虚拟环境 .venv +✓ 环境就绪 (python:3.12) +``` + +失败(无法修复): +``` +✗ Python 版本不满足要求 (需要 >=3.11, 当前 3.10) + 建议: 升级 Python 到 3.11+ 或修改配置文件 + 配置: .aide/config.toml (env.python.version) +``` + +--- + +## 三、实现设计 + +### 3.1 函数接口 + +```python +def cmd_env(args: list[str]) -> int: + """aide env 命令处理 + + Args: + args: 命令参数 + + Returns: + 退出码(0 表示成功) + """ + pass + +def env_ensure(runtime_only: bool = False) -> int: + """环境检测和修复 + + Args: + runtime_only: 是否仅检查运行时环境 + + Returns: + 退出码(0 表示成功) + """ + pass +``` + +### 3.2 实现流程 + +#### 3.2.1 aide env ensure --runtime + +```python +import sys +import subprocess +from pathlib import Path +from core.output import ok, err + +def env_ensure_runtime() -> int: + """检查 aide 运行时环境""" + + # 1. 检查 Python 版本 + python_version = sys.version_info + if python_version.major < 3 or (python_version.major == 3 and python_version.minor < 10): + err( + f"Python 版本不满足要求 (需要 >=3.10, 当前 {python_version.major}.{python_version.minor})", + [ + "建议: 安装 Python 3.10+ 或使用 pyenv 管理版本", + "文档: https://www.python.org/downloads/" + ] + ) + return 3 + + # 2. 检查必需的库 + try: + # Python 3.11+ 内置 tomllib + if python_version.minor >= 11: + import tomllib + else: + import tomli as tomllib + + import tomli_w + except ImportError as e: + err( + f"缺少必需的 Python 库: {e.name}", + [ + "建议: 运行 'pip install tomli tomli-w'", + "或使用 uv: 'uv pip install tomli tomli-w'" + ] + ) + return 3 + + # 3. 输出成功信息 + version_str = f"{python_version.major}.{python_version.minor}.{python_version.micro}" + ok(f"环境就绪 (python:{version_str})") + + return 0 +``` + +#### 3.2.2 aide env ensure + +```python +from core.config import Config +from core.output import ok, warn, err + +def env_ensure_project() -> int: + """检查项目开发环境""" + + # 1. 加载配置 + try: + config = Config(Path.cwd()) + config.load() + except FileNotFoundError: + err( + "配置文件不存在", + [ + "位置: .aide/config.toml", + "建议: 运行 'aide init' 创建配置文件" + ] + ) + return 4 + except Exception as e: + err( + "配置文件读取失败", + [ + f"原因: {str(e)}", + "建议: 检查配置文件格式" + ] + ) + return 4 + + # 2. 检查 Python 版本 + required_version = config.get("env.python.version", ">=3.10") + if not check_python_version(required_version): + err( + f"Python 版本不满足要求 (需要 {required_version}, 当前 {get_python_version()})", + [ + "建议: 升级 Python 或修改配置文件", + "配置: .aide/config.toml (env.python.version)" + ] + ) + return 3 + + # 3. 检查虚拟环境 + venv_path = config.get("env.python.venv", ".venv") + venv_result = check_venv(venv_path) + + if venv_result == "missing": + # 尝试创建虚拟环境 + if create_venv(venv_path): + warn(f"已修复: 创建虚拟环境 {venv_path}") + else: + err( + f"虚拟环境不存在且无法创建 ({venv_path})", + [ + "建议: 手动创建虚拟环境", + f"命令: python3 -m venv {venv_path}" + ] + ) + return 3 + + # 4. 检查可选工具 + tools_info = [] + + # 检查 git + if config.get("env.tools.git", True): + git_version = get_tool_version("git") + if git_version: + tools_info.append(f"git:{git_version}") + else: + warn("git 未安装(可选)") + + # 检查 uv + if config.get("env.tools.uv", False): + uv_version = get_tool_version("uv") + if uv_version: + tools_info.append(f"uv:{uv_version}") + else: + warn("uv 未安装(可选)") + + # 5. 输出成功信息 + python_version = get_python_version() + info_parts = [f"python:{python_version}"] + tools_info + ok(f"环境就绪 ({', '.join(info_parts)})") + + return 0 +``` + +### 3.3 辅助函数 + +#### 3.3.1 版本检查 + +```python +import re +from packaging import version + +def check_python_version(requirement: str) -> bool: + """检查 Python 版本是否满足要求 + + Args: + requirement: 版本要求(如 ">=3.10") + + Returns: + 是否满足要求 + """ + current = get_python_version() + + # 解析要求 + match = re.match(r'(>=|<=|>|<|==)?(\d+\.\d+(?:\.\d+)?)', requirement) + if not match: + return True # 无法解析,假设满足 + + operator, required = match.groups() + operator = operator or "==" + + # 比较版本 + try: + current_ver = version.parse(current) + required_ver = version.parse(required) + + if operator == ">=": + return current_ver >= required_ver + elif operator == "<=": + return current_ver <= required_ver + elif operator == ">": + return current_ver > required_ver + elif operator == "<": + return current_ver < required_ver + elif operator == "==": + return current_ver == required_ver + except Exception: + return True + + return True + +def get_python_version() -> str: + """获取当前 Python 版本 + + Returns: + 版本字符串(如 "3.12.0") + """ + v = sys.version_info + return f"{v.major}.{v.minor}.{v.micro}" +``` + +#### 3.3.2 虚拟环境检查 + +```python +import subprocess + +def check_venv(venv_path: str) -> str: + """检查虚拟环境状态 + + Args: + venv_path: 虚拟环境路径 + + Returns: + 状态:'ok', 'missing', 'invalid' + """ + venv_dir = Path(venv_path) + + if not venv_dir.exists(): + return "missing" + + # 检查是否是有效的虚拟环境 + if sys.platform == "win32": + python_exe = venv_dir / "Scripts" / "python.exe" + else: + python_exe = venv_dir / "bin" / "python" + + if python_exe.exists(): + return "ok" + else: + return "invalid" + +def create_venv(venv_path: str) -> bool: + """创建虚拟环境 + + Args: + venv_path: 虚拟环境路径 + + Returns: + 是否创建成功 + """ + try: + subprocess.run( + [sys.executable, "-m", "venv", venv_path], + check=True, + capture_output=True, + text=True + ) + return True + except subprocess.CalledProcessError: + return False + except Exception: + return False +``` + +#### 3.3.3 工具版本检查 + +```python +def get_tool_version(tool: str) -> str | None: + """获取工具版本 + + Args: + tool: 工具名称(如 "git", "uv") + + Returns: + 版本字符串,未安装则返回 None + """ + try: + result = subprocess.run( + [tool, "--version"], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode == 0: + # 解析版本号 + output = result.stdout.strip() + match = re.search(r'(\d+\.\d+\.\d+)', output) + if match: + return match.group(1) + return "unknown" + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + + return None +``` + +--- + +## 四、错误处理 + +### 4.1 错误分类 + +| 错误类型 | 退出码 | 处理方式 | +|---------|--------|---------| +| Python 版本不满足 | 3 | 显示当前版本和要求,给出升级建议 | +| 配置文件不存在 | 4 | 提示运行 aide init | +| 配置文件格式错误 | 4 | 显示错误位置和原因 | +| 虚拟环境无法创建 | 3 | 显示创建命令 | +| 必需工具未安装 | 3 | 显示安装建议 | + +### 4.2 错误恢复 + +```python +def env_ensure_with_retry(runtime_only: bool = False, max_retries: int = 3) -> int: + """带重试的环境检测 + + Args: + runtime_only: 是否仅检查运行时 + max_retries: 最大重试次数 + + Returns: + 退出码 + """ + for attempt in range(max_retries): + result = env_ensure(runtime_only) + + if result == 0: + return 0 + + if attempt < max_retries - 1: + info(f"重试 ({attempt + 1}/{max_retries})...") + + return result +``` + +--- + +## 五、测试用例 + +### 5.1 runtime 模式测试 + +```python +def test_env_ensure_runtime_success(): + """测试运行时环境检测成功""" + result = env_ensure_runtime() + assert result == 0 + +def test_env_ensure_runtime_old_python(monkeypatch): + """测试 Python 版本过低""" + # 模拟 Python 3.8 + monkeypatch.setattr(sys, "version_info", (3, 8, 0, "final", 0)) + + result = env_ensure_runtime() + assert result == 3 + +def test_env_ensure_runtime_missing_lib(monkeypatch): + """测试缺少必需库""" + # 模拟 import 失败 + def mock_import(name, *args, **kwargs): + if name in ["tomli", "tomllib"]: + raise ImportError(f"No module named '{name}'") + return __import__(name, *args, **kwargs) + + monkeypatch.setattr("builtins.__import__", mock_import) + + result = env_ensure_runtime() + assert result == 3 +``` + +### 5.2 项目环境测试 + +```python +def test_env_ensure_project_success(tmp_path, monkeypatch): + """测试项目环境检测成功""" + monkeypatch.chdir(tmp_path) + + # 创建配置文件 + cmd_init([]) + + # 检测环境 + result = env_ensure_project() + assert result == 0 + +def test_env_ensure_project_no_config(tmp_path, monkeypatch): + """测试配置文件不存在""" + monkeypatch.chdir(tmp_path) + + result = env_ensure_project() + assert result == 4 + +def test_env_ensure_project_create_venv(tmp_path, monkeypatch): + """测试自动创建虚拟环境""" + monkeypatch.chdir(tmp_path) + + # 创建配置文件 + cmd_init([]) + + # 确保虚拟环境不存在 + venv_path = tmp_path / ".venv" + if venv_path.exists(): + shutil.rmtree(venv_path) + + # 检测环境(应该自动创建虚拟环境) + result = env_ensure_project() + assert result == 0 + assert venv_path.exists() +``` + +### 5.3 版本检查测试 + +```python +def test_check_python_version(): + """测试 Python 版本检查""" + # 测试各种版本要求 + assert check_python_version(">=3.10") == True # 假设当前是 3.12 + assert check_python_version(">=3.15") == False + assert check_python_version("==3.12") == True + assert check_python_version("<4.0") == True + +def test_get_python_version(): + """测试获取 Python 版本""" + version = get_python_version() + assert re.match(r'\d+\.\d+\.\d+', version) +``` + +--- + +## 六、性能要求 + +### 6.1 执行时间 + +- `--runtime` 模式:< 100ms +- 项目环境检测(无修复):< 500ms +- 项目环境检测(创建虚拟环境):< 10s + +### 6.2 资源占用 + +- 内存:< 50MB +- CPU:低(主要是 I/O 操作) + +--- + +## 七、集成测试 + +### 7.1 完整工作流 + +```python +def test_env_workflow(tmp_path, monkeypatch): + """测试完整的环境检测工作流""" + monkeypatch.chdir(tmp_path) + + # 1. 检查运行时环境 + result = env_ensure_runtime() + assert result == 0 + + # 2. 初始化项目 + result = cmd_init([]) + assert result == 0 + + # 3. 检查项目环境 + result = env_ensure_project() + assert result == 0 + + # 4. 验证虚拟环境已创建 + assert (tmp_path / ".venv").exists() +``` + +--- + +## 八、总结 + +### 8.1 核心要点 + +1. 两种模式:runtime 和 project +2. 自动修复部分问题(如创建虚拟环境) +3. 清晰的错误信息和建议 +4. 完善的版本检查逻辑 + +### 8.2 实现检查清单 + +- [ ] 实现 cmd_env 函数 +- [ ] 实现 env_ensure_runtime 函数 +- [ ] 实现 env_ensure_project 函数 +- [ ] 实现版本检查函数 +- [ ] 实现虚拟环境检查和创建 +- [ ] 实现工具版本检查 +- [ ] 编写单元测试 +- [ ] 编写集成测试 +- [ ] 性能测试 + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/aide-program/docs/04-aide-config设计.md b/aide-program/docs/04-aide-config设计.md new file mode 100644 index 0000000..3620ac4 --- /dev/null +++ b/aide-program/docs/04-aide-config设计.md @@ -0,0 +1,745 @@ +# aide config 设计 + +## 一、命令概述 + +### 1.1 功能定位 + +`aide config` 命令用于读取和修改项目配置文件,提供命令行方式访问配置。 + +### 1.2 执行时机 + +- 用户需要查看配置值时 +- 用户需要修改配置值时 +- Commands 中需要获取配置时(如 prep/exec 的默认文档路径) + +### 1.3 命令格式 + +```bash +aide config get +aide config set +``` + +**参数**: +- ``:配置键,支持点号分隔(如 `task.source`) +- ``:配置值(仅 set 命令需要) + +--- + +## 二、功能需求 + +### 2.1 aide config get + +**功能**:获取配置值 + +**输出格式**: + +单个值: +``` +task.source = "task-now.md" +``` + +数组值: +``` +flow.phases = ["flow-design", "impl", "verify", "docs", "finish"] +``` + +布尔值: +``` +env.tools.git = true +``` + +**错误处理**: + +配置键不存在: +``` +✗ 配置键不存在: invalid.key + 可用的配置键: task.source, task.spec, env.python.version +``` + +配置文件不存在: +``` +✗ 配置文件不存在 + 位置: .aide/config.toml + 建议: 运行 'aide init' 创建配置文件 +``` + +### 2.2 aide config set + +**功能**:设置配置值 + +**输出**: + +成功时无输出(静默原则) + +**错误处理**: + +配置键不存在: +``` +✗ 配置键不存在: invalid.key + 可用的配置键: task.source, task.spec, env.python.version +``` + +配置值类型错误: +``` +✗ 配置值类型错误 + 键: env.tools.git + 期望类型: boolean + 实际值: "yes" + 建议: 使用 true 或 false +``` + +--- + +## 三、实现设计 + +### 3.1 函数接口 + +```python +def cmd_config(args: list[str]) -> int: + """aide config 命令处理 + + Args: + args: 命令参数 + + Returns: + 退出码(0 表示成功) + """ + pass + +def config_get(key: str) -> int: + """获取配置值 + + Args: + key: 配置键 + + Returns: + 退出码 + """ + pass + +def config_set(key: str, value: str) -> int: + """设置配置值 + + Args: + key: 配置键 + value: 配置值(字符串形式) + + Returns: + 退出码 + """ + pass +``` + +### 3.2 实现流程 + +#### 3.2.1 cmd_config + +```python +from core.output import err + +def cmd_config(args: list[str]) -> int: + """aide config 命令处理""" + + if not args: + err( + "缺少子命令", + [ + "用法: aide config get ", + " aide config set " + ] + ) + return 2 + + subcommand = args[0] + + if subcommand == "get": + if len(args) < 2: + err("缺少参数: key") + return 2 + return config_get(args[1]) + + elif subcommand == "set": + if len(args) < 3: + err("缺少参数: key 或 value") + return 2 + return config_set(args[1], args[2]) + + else: + err( + f"未知子命令: {subcommand}", + ["可用子命令: get, set"] + ) + return 2 +``` + +#### 3.2.2 config_get + +```python +from pathlib import Path +from core.config import Config +from core.output import err + +def config_get(key: str) -> int: + """获取配置值""" + + try: + # 加载配置 + config = Config(Path.cwd()) + config.load() + + # 获取值 + value = config.get(key) + + if value is None: + # 配置键不存在 + available_keys = get_available_keys(config._config) + err( + f"配置键不存在: {key}", + [f"可用的配置键: {', '.join(available_keys)}"] + ) + return 4 + + # 格式化输出 + formatted_value = format_config_value(value) + print(f"{key} = {formatted_value}") + + return 0 + + except FileNotFoundError: + err( + "配置文件不存在", + [ + "位置: .aide/config.toml", + "建议: 运行 'aide init' 创建配置文件" + ] + ) + return 4 + + except Exception as e: + err( + "配置读取失败", + [f"原因: {str(e)}"] + ) + return 4 +``` + +#### 3.2.3 config_set + +```python +def config_set(key: str, value_str: str) -> int: + """设置配置值""" + + try: + # 加载配置 + config = Config(Path.cwd()) + config.load() + + # 验证配置键是否存在 + if not is_valid_config_key(key): + available_keys = get_available_keys(config._config) + err( + f"配置键不存在: {key}", + [f"可用的配置键: {', '.join(available_keys)}"] + ) + return 4 + + # 解析值 + try: + value = parse_config_value(key, value_str, config._config) + except ValueError as e: + err( + "配置值类型错误", + [ + f"键: {key}", + f"原因: {str(e)}" + ] + ) + return 4 + + # 设置值 + config.set(key, value) + + # 成功时无输出(静默原则) + return 0 + + except FileNotFoundError: + err( + "配置文件不存在", + [ + "位置: .aide/config.toml", + "建议: 运行 'aide init' 创建配置文件" + ] + ) + return 4 + + except Exception as e: + err( + "配置写入失败", + [f"原因: {str(e)}"] + ) + return 4 +``` + +### 3.3 辅助函数 + +#### 3.3.1 格式化配置值 + +```python +import json + +def format_config_value(value) -> str: + """格式化配置值用于输出 + + Args: + value: 配置值 + + Returns: + 格式化后的字符串 + """ + if isinstance(value, str): + return f'"{value}"' + elif isinstance(value, bool): + return "true" if value else "false" + elif isinstance(value, (list, dict)): + return json.dumps(value, ensure_ascii=False) + else: + return str(value) +``` + +#### 3.3.2 解析配置值 + +```python +def parse_config_value(key: str, value_str: str, config: dict): + """解析配置值 + + Args: + key: 配置键 + value_str: 值字符串 + config: 当前配置(用于推断类型) + + Returns: + 解析后的值 + + Raises: + ValueError: 值类型错误 + """ + # 获取当前值以推断类型 + current_value = get_config_value(config, key) + + if current_value is None: + # 新键,尝试自动推断类型 + return auto_parse_value(value_str) + + # 根据当前值的类型解析 + if isinstance(current_value, bool): + return parse_bool(value_str) + elif isinstance(current_value, int): + return parse_int(value_str) + elif isinstance(current_value, float): + return parse_float(value_str) + elif isinstance(current_value, list): + return parse_list(value_str) + elif isinstance(current_value, dict): + return parse_dict(value_str) + else: + # 字符串 + return value_str + +def parse_bool(value_str: str) -> bool: + """解析布尔值""" + lower = value_str.lower() + if lower in ["true", "yes", "1"]: + return True + elif lower in ["false", "no", "0"]: + return False + else: + raise ValueError(f"无效的布尔值: {value_str},使用 true 或 false") + +def parse_int(value_str: str) -> int: + """解析整数""" + try: + return int(value_str) + except ValueError: + raise ValueError(f"无效的整数: {value_str}") + +def parse_float(value_str: str) -> float: + """解析浮点数""" + try: + return float(value_str) + except ValueError: + raise ValueError(f"无效的浮点数: {value_str}") + +def parse_list(value_str: str) -> list: + """解析列表(JSON 格式)""" + try: + value = json.loads(value_str) + if not isinstance(value, list): + raise ValueError("不是列表") + return value + except json.JSONDecodeError: + raise ValueError(f"无效的列表格式: {value_str},使用 JSON 格式") + +def parse_dict(value_str: str) -> dict: + """解析字典(JSON 格式)""" + try: + value = json.loads(value_str) + if not isinstance(value, dict): + raise ValueError("不是字典") + return value + except json.JSONDecodeError: + raise ValueError(f"无效的字典格式: {value_str},使用 JSON 格式") + +def auto_parse_value(value_str: str): + """自动推断并解析值""" + # 尝试 JSON + try: + return json.loads(value_str) + except json.JSONDecodeError: + pass + + # 尝试布尔值 + if value_str.lower() in ["true", "false", "yes", "no"]: + return parse_bool(value_str) + + # 尝试数字 + try: + if "." in value_str: + return float(value_str) + else: + return int(value_str) + except ValueError: + pass + + # 默认为字符串 + return value_str +``` + +#### 3.3.3 获取可用配置键 + +```python +def get_available_keys(config: dict, prefix: str = "") -> list[str]: + """获取所有可用的配置键 + + Args: + config: 配置字典 + prefix: 键前缀 + + Returns: + 配置键列表 + """ + keys = [] + + for key, value in config.items(): + full_key = f"{prefix}{key}" if prefix else key + + if isinstance(value, dict): + # 递归获取嵌套键 + keys.extend(get_available_keys(value, f"{full_key}.")) + else: + keys.append(full_key) + + return sorted(keys) + +def is_valid_config_key(key: str) -> bool: + """验证配置键是否有效 + + Args: + key: 配置键 + + Returns: + 是否有效 + """ + # 定义允许的配置键 + valid_keys = [ + "task.source", + "task.spec", + "env.python.version", + "env.python.venv", + "env.tools.uv", + "env.tools.git", + "flow.phases", + "flow.flowchart_dir", + "decide.port", + "decide.decisions_dir", + "output.color", + "output.language" + ] + + return key in valid_keys +``` + +--- + +## 四、错误处理 + +### 4.1 错误分类 + +| 错误类型 | 退出码 | 处理方式 | +|---------|--------|---------| +| 缺少参数 | 2 | 显示用法 | +| 配置文件不存在 | 4 | 提示运行 aide init | +| 配置键不存在 | 4 | 显示可用的键 | +| 配置值类型错误 | 4 | 显示期望类型和建议 | +| 配置文件格式错误 | 4 | 显示错误位置 | + +### 4.2 类型验证 + +```python +def validate_config_value(key: str, value) -> list[str]: + """验证配置值 + + Args: + key: 配置键 + value: 配置值 + + Returns: + 错误列表(空列表表示无错误) + """ + errors = [] + + # 验证特定键的值 + if key == "env.python.version": + if not isinstance(value, str): + errors.append("env.python.version 必须是字符串") + elif not is_valid_version_spec(value): + errors.append(f"无效的版本格式: {value}") + + elif key == "env.python.venv": + if not isinstance(value, str): + errors.append("env.python.venv 必须是字符串") + + elif key in ["env.tools.uv", "env.tools.git", "output.color"]: + if not isinstance(value, bool): + errors.append(f"{key} 必须是布尔值") + + elif key == "flow.phases": + if not isinstance(value, list): + errors.append("flow.phases 必须是数组") + elif not all(isinstance(p, str) for p in value): + errors.append("flow.phases 的元素必须是字符串") + + elif key == "decide.port": + if not isinstance(value, int): + errors.append("decide.port 必须是整数") + elif not (1024 <= value <= 65535): + errors.append("decide.port 必须在 1024-65535 之间") + + return errors +``` + +--- + +## 五、测试用例 + +### 5.1 config get 测试 + +```python +def test_config_get_success(tmp_path, monkeypatch, capsys): + """测试获取配置成功""" + monkeypatch.chdir(tmp_path) + + # 初始化配置 + cmd_init([]) + + # 获取配置 + result = config_get("task.source") + + # 验证退出码 + assert result == 0 + + # 验证输出 + captured = capsys.readouterr() + assert 'task.source = "task-now.md"' in captured.out + +def test_config_get_not_found(tmp_path, monkeypatch, capsys): + """测试配置键不存在""" + monkeypatch.chdir(tmp_path) + + # 初始化配置 + cmd_init([]) + + # 获取不存在的配置 + result = config_get("invalid.key") + + # 验证退出码 + assert result == 4 + + # 验证输出 + captured = capsys.readouterr() + assert "配置键不存在" in captured.out + +def test_config_get_no_config(tmp_path, monkeypatch, capsys): + """测试配置文件不存在""" + monkeypatch.chdir(tmp_path) + + # 获取配置(未初始化) + result = config_get("task.source") + + # 验证退出码 + assert result == 4 + + # 验证输出 + captured = capsys.readouterr() + assert "配置文件不存在" in captured.out +``` + +### 5.2 config set 测试 + +```python +def test_config_set_success(tmp_path, monkeypatch): + """测试设置配置成功""" + monkeypatch.chdir(tmp_path) + + # 初始化配置 + cmd_init([]) + + # 设置配置 + result = config_set("task.source", "new-task.md") + + # 验证退出码 + assert result == 0 + + # 验证配置已更新 + config = Config(tmp_path) + config.load() + assert config.get("task.source") == "new-task.md" + +def test_config_set_bool(tmp_path, monkeypatch): + """测试设置布尔值""" + monkeypatch.chdir(tmp_path) + + # 初始化配置 + cmd_init([]) + + # 设置布尔值 + result = config_set("env.tools.uv", "true") + assert result == 0 + + # 验证 + config = Config(tmp_path) + config.load() + assert config.get("env.tools.uv") == True + +def test_config_set_invalid_type(tmp_path, monkeypatch, capsys): + """测试设置错误类型""" + monkeypatch.chdir(tmp_path) + + # 初始化配置 + cmd_init([]) + + # 设置错误类型 + result = config_set("env.tools.git", "yes") + + # 验证退出码 + assert result == 4 + + # 验证输出 + captured = capsys.readouterr() + assert "类型错误" in captured.out +``` + +### 5.3 值解析测试 + +```python +def test_parse_bool(): + """测试布尔值解析""" + assert parse_bool("true") == True + assert parse_bool("false") == False + assert parse_bool("yes") == True + assert parse_bool("no") == False + + with pytest.raises(ValueError): + parse_bool("invalid") + +def test_parse_list(): + """测试列表解析""" + result = parse_list('["a", "b", "c"]') + assert result == ["a", "b", "c"] + + with pytest.raises(ValueError): + parse_list("not a list") + +def test_auto_parse_value(): + """测试自动解析""" + assert auto_parse_value("true") == True + assert auto_parse_value("123") == 123 + assert auto_parse_value("3.14") == 3.14 + assert auto_parse_value('["a"]') == ["a"] + assert auto_parse_value("text") == "text" +``` + +--- + +## 六、使用示例 + +### 6.1 查看配置 + +```bash +# 查看任务文档路径 +aide config get task.source + +# 查看 Python 版本要求 +aide config get env.python.version + +# 查看流程环节列表 +aide config get flow.phases +``` + +### 6.2 修改配置 + +```bash +# 修改任务文档路径 +aide config set task.source "my-task.md" + +# 修改 Python 版本要求 +aide config set env.python.version ">=3.11" + +# 启用 uv +aide config set env.tools.uv true + +# 修改端口 +aide config set decide.port 8080 +``` + +--- + +## 七、性能要求 + +### 7.1 执行时间 + +- config get:< 100ms +- config set:< 200ms + +### 7.2 资源占用 + +- 内存:< 20MB +- 磁盘 I/O:最小化 + +--- + +## 八、总结 + +### 8.1 核心要点 + +1. 支持 get 和 set 两个子命令 +2. 点号分隔的键访问 +3. 自动类型推断和验证 +4. 静默原则(set 成功时无输出) +5. 清晰的错误信息 + +### 8.2 实现检查清单 + +- [ ] 实现 cmd_config 函数 +- [ ] 实现 config_get 函数 +- [ ] 实现 config_set 函数 +- [ ] 实现值格式化函数 +- [ ] 实现值解析函数 +- [ ] 实现类型验证 +- [ ] 编写单元测试 +- [ ] 编写集成测试 +- [ ] 性能测试 + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/aide-program/docs/05-配置文件规范.md b/aide-program/docs/05-配置文件规范.md new file mode 100644 index 0000000..01ad8c5 --- /dev/null +++ b/aide-program/docs/05-配置文件规范.md @@ -0,0 +1,698 @@ +# 配置文件规范 + +## 一、概述 + +### 1.1 配置文件定位 + +- **文件路径**:`.aide/config.toml` +- **格式**:TOML(Tom's Obvious, Minimal Language) +- **特点**:自文档化,包含详细注释 +- **创建时机**:`aide init` 命令执行时 + +### 1.2 设计原则 + +1. **自文档化**:每个配置项都有注释说明 +2. **合理默认值**:开箱即用,无需修改 +3. **类型安全**:明确的数据类型 +4. **向后兼容**:新版本保持旧配置可用 + +--- + +## 二、配置文件结构 + +### 2.1 完整示例 + +```toml +# Aide 项目配置文件 +# 由 aide init 自动生成 +# 版本: 1.0 + +[task] +# 任务原文档路径(prep 阶段使用) +source = "task-now.md" + +# 任务细则文档路径(exec 阶段使用) +spec = "task-spec.md" + +[env] +# 环境配置 + +[env.python] +# Python 版本要求(语义化版本) +version = ">=3.10" + +# 虚拟环境路径(相对于项目根目录) +venv = ".venv" + +[env.tools] +# 可选工具配置 + +# 是否需要 uv(Python 包管理器) +uv = false + +# 是否需要 git +git = true + +[flow] +# 流程追踪配置(后续实现) + +# 环节列表 +phases = ["flow-design", "impl", "verify", "docs", "finish"] + +# PlantUML 流程图目录 +flowchart_dir = "program_flowchart" + +[decide] +# 待定项确认配置(后续实现) + +# Web 服务端口 +port = 3721 + +# 决策记录保存目录 +decisions_dir = ".aide/decisions" + +[output] +# 输出配置 + +# 是否启用颜色输出 +color = true + +# 输出语言(当前仅支持 zh-CN) +language = "zh-CN" +``` + +### 2.2 配置项说明 + +#### 2.2.1 [task] 任务配置 + +| 键 | 类型 | 默认值 | 说明 | +|---|------|--------|------| +| `source` | string | `"task-now.md"` | 任务原文档路径 | +| `spec` | string | `"task-spec.md"` | 任务细则文档路径 | + +**使用场景**: +- `prep` 命令未传入参数时,使用 `task.source` +- `exec` 命令未传入参数时,使用 `task.spec` + +#### 2.2.2 [env.python] Python 环境配置 + +| 键 | 类型 | 默认值 | 说明 | +|---|------|--------|------| +| `version` | string | `">=3.10"` | Python 版本要求 | +| `venv` | string | `".venv"` | 虚拟环境路径 | + +**版本格式**: +- `">=3.10"` - 大于等于 3.10 +- `">=3.10,<4.0"` - 3.10 到 4.0 之间 +- `"3.12"` - 精确匹配 3.12 + +#### 2.2.3 [env.tools] 工具配置 + +| 键 | 类型 | 默认值 | 说明 | +|---|------|--------|------| +| `uv` | boolean | `false` | 是否需要 uv | +| `git` | boolean | `true` | 是否需要 git | + +#### 2.2.4 [flow] 流程配置(后续实现) + +| 键 | 类型 | 默认值 | 说明 | +|---|------|--------|------| +| `phases` | array | `["flow-design", "impl", "verify", "docs", "finish"]` | 环节列表 | +| `flowchart_dir` | string | `"program_flowchart"` | 流程图目录 | + +#### 2.2.5 [decide] 待定项配置(后续实现) + +| 键 | 类型 | 默认值 | 说明 | +|---|------|--------|------| +| `port` | integer | `3721` | Web 服务端口 | +| `decisions_dir` | string | `".aide/decisions"` | 决策记录目录 | + +#### 2.2.6 [output] 输出配置 + +| 键 | 类型 | 默认值 | 说明 | +|---|------|--------|------| +| `color` | boolean | `true` | 是否启用颜色 | +| `language` | string | `"zh-CN"` | 输出语言 | + +--- + +## 三、配置文件操作 + +### 3.1 读取配置 + +**Python 实现示例**: + +```python +import tomllib # Python 3.11+ +# 或 +import tomli # Python 3.10 + +from pathlib import Path + +def load_config(project_root: Path) -> dict: + """加载配置文件 + + Args: + project_root: 项目根目录 + + Returns: + 配置字典 + + Raises: + FileNotFoundError: 配置文件不存在 + tomllib.TOMLDecodeError: 配置文件格式错误 + """ + config_path = project_root / ".aide" / "config.toml" + + if not config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {config_path}") + + with open(config_path, "rb") as f: + return tomllib.load(f) + +def get_config_value(config: dict, key: str, default=None): + """获取配置值(支持点号分隔的键) + + Args: + config: 配置字典 + key: 配置键(如 "task.source") + default: 默认值 + + Returns: + 配置值 + + Example: + >>> config = {"task": {"source": "task-now.md"}} + >>> get_config_value(config, "task.source") + 'task-now.md' + """ + keys = key.split(".") + value = config + + for k in keys: + if isinstance(value, dict) and k in value: + value = value[k] + else: + return default + + return value +``` + +### 3.2 写入配置 + +**Python 实现示例**: + +```python +import tomli_w +from pathlib import Path + +def save_config(project_root: Path, config: dict) -> None: + """保存配置文件 + + Args: + project_root: 项目根目录 + config: 配置字典 + """ + config_path = project_root / ".aide" / "config.toml" + + with open(config_path, "wb") as f: + tomli_w.dump(config, f) + +def set_config_value(config: dict, key: str, value) -> dict: + """设置配置值(支持点号分隔的键) + + Args: + config: 配置字典 + key: 配置键(如 "task.source") + value: 配置值 + + Returns: + 更新后的配置字典 + + Example: + >>> config = {"task": {"source": "task-now.md"}} + >>> set_config_value(config, "task.source", "new-task.md") + {'task': {'source': 'new-task.md'}} + """ + keys = key.split(".") + current = config + + # 导航到倒数第二层 + for k in keys[:-1]: + if k not in current: + current[k] = {} + current = current[k] + + # 设置最后一层的值 + current[keys[-1]] = value + + return config +``` + +### 3.3 验证配置 + +**验证规则**: + +```python +from typing import Any + +def validate_config(config: dict) -> list[str]: + """验证配置文件 + + Args: + config: 配置字典 + + Returns: + 错误列表(空列表表示无错误) + """ + errors = [] + + # 验证必需的顶层键 + required_sections = ["task", "env", "output"] + for section in required_sections: + if section not in config: + errors.append(f"缺少必需的配置节: [{section}]") + + # 验证 task 配置 + if "task" in config: + if "source" not in config["task"]: + errors.append("缺少配置项: task.source") + if "spec" not in config["task"]: + errors.append("缺少配置项: task.spec") + + # 验证 env.python 配置 + if "env" in config and "python" in config["env"]: + python_config = config["env"]["python"] + if "version" not in python_config: + errors.append("缺少配置项: env.python.version") + else: + # 验证版本格式 + version = python_config["version"] + if not is_valid_version_spec(version): + errors.append(f"无效的版本格式: {version}") + + # 验证 output 配置 + if "output" in config: + output_config = config["output"] + if "language" in output_config: + lang = output_config["language"] + if lang not in ["zh-CN", "en-US"]: + errors.append(f"不支持的语言: {lang}") + + return errors + +def is_valid_version_spec(spec: str) -> bool: + """验证版本规格字符串 + + Args: + spec: 版本规格(如 ">=3.10") + + Returns: + 是否有效 + """ + import re + # 简化的版本规格验证 + pattern = r'^(>=|<=|>|<|==)?\d+\.\d+(\.\d+)?$' + return bool(re.match(pattern, spec)) +``` + +--- + +## 四、默认配置生成 + +### 4.1 生成逻辑 + +`aide init` 命令应该生成包含注释的默认配置: + +```python +def generate_default_config() -> str: + """生成默认配置文件内容(带注释) + + Returns: + TOML 格式的配置文件内容 + """ + return '''# Aide 项目配置文件 +# 由 aide init 自动生成 +# 文档: https://github.com/your-org/aide + +[task] +# 任务原文档路径(prep 阶段使用) +# 可通过 /aide:prep [路径] 覆盖 +source = "task-now.md" + +# 任务细则文档路径(exec 阶段使用) +# 可通过 /aide:exec [路径] 覆盖 +spec = "task-spec.md" + +[env] +# 环境配置 + +[env.python] +# Python 版本要求(语义化版本) +# 格式: ">=3.10" 或 ">=3.10,<4.0" +version = ">=3.10" + +# 虚拟环境路径(相对于项目根目录) +venv = ".venv" + +[env.tools] +# 可选工具配置 + +# 是否需要 uv(Python 包管理器) +uv = false + +# 是否需要 git +git = true + +[flow] +# 流程追踪配置(aide flow 命令使用) + +# 环节列表(不建议修改) +phases = ["flow-design", "impl", "verify", "docs", "finish"] + +# PlantUML 流程图目录 +flowchart_dir = "program_flowchart" + +[decide] +# 待定项确认配置(aide decide 命令使用) + +# Web 服务端口 +port = 3721 + +# 决策记录保存目录 +decisions_dir = ".aide/decisions" + +[output] +# 输出配置 + +# 是否启用颜色输出 +# 可通过环境变量 NO_COLOR 禁用 +color = true + +# 输出语言(当前仅支持 zh-CN) +language = "zh-CN" +''' +``` + +### 4.2 配置文件创建流程 + +```python +from pathlib import Path + +def create_config_file(project_root: Path) -> bool: + """创建配置文件 + + Args: + project_root: 项目根目录 + + Returns: + 是否创建成功(False 表示文件已存在) + """ + aide_dir = project_root / ".aide" + config_path = aide_dir / "config.toml" + + # 检查是否已存在 + if config_path.exists(): + return False + + # 确保 .aide 目录存在 + aide_dir.mkdir(exist_ok=True) + + # 写入默认配置 + config_content = generate_default_config() + config_path.write_text(config_content, encoding="utf-8") + + return True +``` + +--- + +## 五、配置访问接口 + +### 5.1 Config 类设计 + +```python +from pathlib import Path +from typing import Any, Optional + +class Config: + """配置管理类""" + + def __init__(self, project_root: Path): + """初始化配置 + + Args: + project_root: 项目根目录 + """ + self.project_root = project_root + self.config_path = project_root / ".aide" / "config.toml" + self._config: Optional[dict] = None + + def load(self) -> None: + """加载配置文件""" + if not self.config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {self.config_path}") + + with open(self.config_path, "rb") as f: + self._config = tomllib.load(f) + + def get(self, key: str, default: Any = None) -> Any: + """获取配置值 + + Args: + key: 配置键(支持点号分隔) + default: 默认值 + + Returns: + 配置值 + """ + if self._config is None: + self.load() + + return get_config_value(self._config, key, default) + + def set(self, key: str, value: Any) -> None: + """设置配置值 + + Args: + key: 配置键(支持点号分隔) + value: 配置值 + """ + if self._config is None: + self.load() + + set_config_value(self._config, key, value) + self.save() + + def save(self) -> None: + """保存配置文件""" + if self._config is None: + return + + with open(self.config_path, "wb") as f: + tomli_w.dump(self._config, f) + + def validate(self) -> list[str]: + """验证配置 + + Returns: + 错误列表 + """ + if self._config is None: + self.load() + + return validate_config(self._config) +``` + +### 5.2 使用示例 + +```python +# 初始化配置 +config = Config(Path.cwd()) + +# 获取配置值 +task_source = config.get("task.source", "task-now.md") +python_version = config.get("env.python.version", ">=3.10") + +# 设置配置值 +config.set("task.source", "new-task.md") +config.set("env.python.version", ">=3.11") + +# 验证配置 +errors = config.validate() +if errors: + for error in errors: + print(f"✗ 配置错误: {error}") +``` + +--- + +## 六、配置迁移 + +### 6.1 版本兼容性 + +当配置文件格式升级时,需要提供迁移机制: + +```python +def migrate_config(config: dict, from_version: str, to_version: str) -> dict: + """迁移配置文件 + + Args: + config: 旧配置 + from_version: 源版本 + to_version: 目标版本 + + Returns: + 新配置 + """ + if from_version == "1.0" and to_version == "1.1": + # 示例:添加新的配置项 + if "output" not in config: + config["output"] = { + "color": True, + "language": "zh-CN" + } + + return config +``` + +### 6.2 配置备份 + +修改配置前应该备份: + +```python +import shutil +from datetime import datetime + +def backup_config(config_path: Path) -> Path: + """备份配置文件 + + Args: + config_path: 配置文件路径 + + Returns: + 备份文件路径 + """ + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_path = config_path.with_suffix(f".toml.backup.{timestamp}") + shutil.copy2(config_path, backup_path) + return backup_path +``` + +--- + +## 七、错误处理 + +### 7.1 常见错误 + +| 错误类型 | 处理方式 | +|---------|---------| +| 配置文件不存在 | 提示使用 `aide init` 创建 | +| 配置文件格式错误 | 显示具体错误位置和原因 | +| 配置项缺失 | 使用默认值并警告 | +| 配置值类型错误 | 显示期望类型和实际类型 | + +### 7.2 错误信息示例 + +```python +def handle_config_error(error: Exception, config_path: Path) -> None: + """处理配置错误 + + Args: + error: 异常对象 + config_path: 配置文件路径 + """ + if isinstance(error, FileNotFoundError): + err( + "配置文件不存在", + [ + f"位置: {config_path}", + "建议: 运行 'aide init' 创建配置文件" + ] + ) + elif isinstance(error, tomllib.TOMLDecodeError): + err( + f"配置文件格式错误 (第{error.lineno}行)", + [ + f"位置: {config_path}:{error.lineno}", + f"原因: {error.msg}", + "建议: 检查 TOML 语法,确保格式正确" + ] + ) + else: + err( + "配置文件读取失败", + [ + f"位置: {config_path}", + f"原因: {str(error)}" + ] + ) +``` + +--- + +## 八、测试要求 + +### 8.1 测试用例 + +```python +def test_load_config(): + """测试加载配置""" + # 创建测试配置 + # 加载配置 + # 验证配置内容 + +def test_get_config_value(): + """测试获取配置值""" + # 测试简单键 + # 测试嵌套键 + # 测试不存在的键 + # 测试默认值 + +def test_set_config_value(): + """测试设置配置值""" + # 测试设置简单键 + # 测试设置嵌套键 + # 测试创建新键 + +def test_validate_config(): + """测试配置验证""" + # 测试有效配置 + # 测试缺少必需项 + # 测试无效值类型 + # 测试无效版本格式 + +def test_config_migration(): + """测试配置迁移""" + # 测试版本升级 + # 测试向后兼容 +``` + +--- + +## 九、总结 + +### 9.1 核心要点 + +1. 使用 TOML 格式,自文档化 +2. 提供合理的默认值 +3. 支持点号分隔的键访问 +4. 完善的错误处理和验证 +5. 配置迁移和备份机制 + +### 9.2 实现检查清单 + +- [ ] 实现配置文件读取(load_config) +- [ ] 实现配置文件写入(save_config) +- [ ] 实现配置值获取(get_config_value) +- [ ] 实现配置值设置(set_config_value) +- [ ] 实现配置验证(validate_config) +- [ ] 实现默认配置生成(generate_default_config) +- [ ] 实现 Config 类 +- [ ] 编写配置测试用例 +- [ ] 验证 TOML 格式正确性 + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/aide-program/docs/06-输出格式规范.md b/aide-program/docs/06-输出格式规范.md new file mode 100644 index 0000000..d9299c3 --- /dev/null +++ b/aide-program/docs/06-输出格式规范.md @@ -0,0 +1,428 @@ +# 输出格式规范 + +## 一、设计原则 + +### 1.1 核心原则 + +1. **精简优先**:成功时输出极简,失败时输出详细 +2. **一致性**:所有命令使用统一的输出格式 +3. **可解析性**:输出格式便于程序解析(如需要) +4. **用户友好**:错误信息包含解决建议 + +### 1.2 静默原则 + +**无输出 = 正常完成** + +适用于幂等操作,例如: +- 配置项已经是目标值时,`aide config set` 不输出 +- 目录已存在时,创建目录操作不输出 + +--- + +## 二、输出前缀 + +### 2.1 前缀定义 + +所有输出行必须以以下前缀之一开头: + +| 前缀 | Unicode | 含义 | 颜色(可选) | 退出码 | +|------|---------|------|-------------|--------| +| `✓` | U+2713 | 成功 | 绿色 | 0 | +| `⚠` | U+26A0 | 警告(可继续) | 黄色 | 0 | +| `✗` | U+2717 | 错误(失败) | 红色 | 非0 | +| `→` | U+2192 | 信息/进行中 | 蓝色 | - | + +### 2.2 使用场景 + +**✓ 成功** +``` +✓ 环境就绪 (python:3.12) +✓ 已创建 .aide/ 目录 +✓ 已生成默认配置 +``` + +**⚠ 警告** +``` +⚠ 已修复: 创建虚拟环境 .venv +⚠ 配置文件不存在,使用默认配置 +⚠ Python 版本较旧 (3.10),建议升级到 3.11+ +``` + +**✗ 错误** +``` +✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) + 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 +✗ 配置文件格式错误: 第5行缺少引号 + 位置: .aide/config.toml:5 +``` + +**→ 信息** +``` +→ 正在检测环境... +→ 正在创建配置文件... +→ 配置值: task.source = "task-now.md" +``` + +--- + +## 三、输出格式 + +### 3.1 单行输出 + +**格式**:`<前缀> <消息>` + +**示例**: +``` +✓ 环境就绪 (python:3.12) +⚠ 已修复: 创建虚拟环境 .venv +✗ Python 未安装 +``` + +### 3.2 多行输出 + +**格式**: +``` +<前缀> <主消息> + <详细信息行1> + <详细信息行2> +``` + +**缩进规则**: +- 主消息行:前缀 + 空格 + 消息 +- 详细信息行:2个空格缩进 + +**示例**: +``` +✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) + 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 + 文档: https://docs.python.org/3/using/index.html +``` + +### 3.3 列表输出 + +**格式**: +``` +<前缀> <标题> + - <项目1> + - <项目2> +``` + +**示例**: +``` +✓ 环境就绪 + - python: 3.12.0 + - uv: 0.4.0 + - git: 2.40.0 +``` + +--- + +## 四、特殊场景 + +### 4.1 配置输出 + +**aide config get** + +单个值: +``` +task.source = "task-now.md" +``` + +多个值(如果支持): +``` +task.source = "task-now.md" +task.spec = "task-spec.md" +``` + +数组值: +``` +flow.phases = ["flow-design", "impl", "verify", "docs", "finish"] +``` + +**aide config set** + +成功时无输出(静默原则) + +失败时: +``` +✗ 配置键不存在: invalid.key + 可用的配置键: task.source, task.spec, env.python.version +``` + +### 4.2 环境检测输出 + +**成功(无问题)**: +``` +✓ 环境就绪 (python:3.12) +``` + +**成功(自动修复)**: +``` +⚠ 已修复: 创建虚拟环境 .venv +✓ 环境就绪 (python:3.12) +``` + +**失败(无法修复)**: +``` +✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) + 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 +``` + +### 4.3 初始化输出 + +**首次初始化**: +``` +✓ 已创建 .aide/ 目录 +✓ 已生成默认配置 +✓ 已添加 .aide/ 到 .gitignore +``` + +**重复初始化**: +``` +⚠ .aide/ 目录已存在 +⚠ 配置文件已存在,跳过生成 +``` + +--- + +## 五、实现规范 + +### 5.1 输出函数接口 + +建议在 `core/output.py` 中实现以下函数: + +```python +def ok(message: str, details: list[str] | None = None) -> None: + """输出成功信息 + + Args: + message: 主消息 + details: 详细信息列表(可选) + """ + pass + +def warn(message: str, details: list[str] | None = None) -> None: + """输出警告信息 + + Args: + message: 主消息 + details: 详细信息列表(可选) + """ + pass + +def err(message: str, details: list[str] | None = None) -> None: + """输出错误信息 + + Args: + message: 主消息 + details: 详细信息列表(可选) + """ + pass + +def info(message: str, details: list[str] | None = None) -> None: + """输出信息 + + Args: + message: 主消息 + details: 详细信息列表(可选) + """ + pass +``` + +### 5.2 使用示例 + +```python +from core.output import ok, warn, err, info + +# 简单成功 +ok("环境就绪 (python:3.12)") + +# 带详细信息的警告 +warn("已修复: 创建虚拟环境 .venv") + +# 带建议的错误 +err( + "Python 版本不满足要求 (需要 >=3.10, 当前 3.8)", + [ + "建议: 安装 Python 3.10+ 或使用 pyenv 管理版本", + "文档: https://docs.python.org/3/using/index.html" + ] +) + +# 进行中信息 +info("正在检测环境...") +``` + +### 5.3 颜色支持(可选) + +如果实现颜色输出,必须: + +1. **检测终端支持**:使用 `sys.stdout.isatty()` 检测 +2. **提供禁用选项**:通过环境变量 `NO_COLOR` 或 `--no-color` 参数 +3. **跨平台兼容**:Windows 需要特殊处理(colorama 或 ANSI 转义) + +**颜色映射**: +- ✓ 成功:绿色(`\033[32m`) +- ⚠ 警告:黄色(`\033[33m`) +- ✗ 错误:红色(`\033[31m`) +- → 信息:蓝色(`\033[34m`) + +--- + +## 六、错误信息规范 + +### 6.1 错误信息结构 + +``` +✗ <问题描述> (<具体原因>) + <解决建议> + <相关信息> +``` + +### 6.2 错误信息要素 + +1. **问题描述**:清晰说明出了什么问题 +2. **具体原因**:括号内说明具体原因(如版本号、路径等) +3. **解决建议**:以"建议:"开头,提供可行的解决方案 +4. **相关信息**:文档链接、配置位置等 + +### 6.3 错误信息示例 + +**环境错误**: +``` +✗ Python 版本不满足要求 (需要 >=3.10, 当前 3.8) + 建议: 安装 Python 3.10+ 或使用 pyenv 管理版本 +``` + +**配置错误**: +``` +✗ 配置文件格式错误 (第5行: 缺少引号) + 位置: .aide/config.toml:5 + 建议: 检查 TOML 语法,确保字符串值使用引号 +``` + +**文件错误**: +``` +✗ 任务文档不存在 (task-now.md) + 建议: 创建任务文档或使用 --file 参数指定路径 +``` + +**权限错误**: +``` +✗ 无法创建目录 (.aide/) + 原因: 权限不足 + 建议: 检查当前目录的写入权限 +``` + +--- + +## 七、退出码规范 + +### 7.1 退出码定义 + +| 退出码 | 含义 | 使用场景 | +|-------|------|---------| +| 0 | 成功 | 操作成功完成 | +| 1 | 一般错误 | 操作失败,原因不明确 | +| 2 | 参数错误 | 命令参数错误或缺失 | +| 3 | 环境错误 | 环境不满足要求 | +| 4 | 配置错误 | 配置文件错误 | +| 5 | 文件错误 | 文件不存在或无法访问 | + +### 7.2 退出码使用 + +```python +import sys + +# 成功 +sys.exit(0) + +# 参数错误 +sys.exit(2) + +# 环境错误 +sys.exit(3) +``` + +--- + +## 八、测试要求 + +### 8.1 输出测试 + +每个命令必须测试: + +1. **成功场景**:验证输出格式正确 +2. **警告场景**:验证警告信息正确 +3. **错误场景**:验证错误信息和退出码 +4. **静默场景**:验证幂等操作无输出 + +### 8.2 测试示例 + +```python +def test_env_ensure_success(capsys): + """测试环境检测成功输出""" + # 执行命令 + result = env_ensure() + + # 验证退出码 + assert result == 0 + + # 验证输出 + captured = capsys.readouterr() + assert "✓ 环境就绪" in captured.out + assert "python:" in captured.out + +def test_env_ensure_failure(capsys): + """测试环境检测失败输出""" + # 模拟 Python 版本过低 + with mock_python_version("3.8"): + result = env_ensure() + + # 验证退出码 + assert result == 3 + + # 验证输出 + captured = capsys.readouterr() + assert "✗ Python 版本不满足要求" in captured.out + assert "建议:" in captured.out +``` + +--- + +## 九、国际化考虑(可选) + +如果需要支持多语言: + +1. **消息模板**:将所有消息定义为模板 +2. **语言检测**:通过环境变量 `LANG` 或 `--lang` 参数 +3. **回退机制**:不支持的语言回退到英文 + +**当前阶段**:仅支持简体中文,国际化可在后续版本实现。 + +--- + +## 十、总结 + +### 10.1 核心要点 + +1. 使用统一的前缀(✓ ⚠ ✗ →) +2. 成功时输出极简,失败时输出详细 +3. 错误信息包含解决建议 +4. 遵循静默原则(幂等操作) +5. 使用明确的退出码 + +### 10.2 实现检查清单 + +- [ ] 实现 `core/output.py` 模块 +- [ ] 定义 ok/warn/err/info 函数 +- [ ] 支持单行和多行输出 +- [ ] 实现颜色支持(可选) +- [ ] 定义退出码常量 +- [ ] 编写输出测试用例 +- [ ] 验证跨平台兼容性 + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/aide-program/docs/07-测试规范.md b/aide-program/docs/07-测试规范.md new file mode 100644 index 0000000..85a0df3 --- /dev/null +++ b/aide-program/docs/07-测试规范.md @@ -0,0 +1,708 @@ +# 测试规范 + +## 一、测试概述 + +### 1.1 测试目标 + +确保 aide 程序的所有功能正确、稳定、可靠,满足设计要求。 + +### 1.2 测试范围 + +- 单元测试:核心模块和函数 +- 集成测试:命令完整流程 +- 跨平台测试:Linux、macOS、Windows +- 性能测试:执行时间和资源占用 + +### 1.3 测试工具 + +- **pytest**:测试框架 +- **pytest-cov**:代码覆盖率 +- **pytest-mock**:模拟和打桩 +- **pytest-timeout**:超时控制 + +--- + +## 二、测试结构 + +### 2.1 目录结构 + +``` +tests/ +├── __init__.py +├── conftest.py # pytest 配置和 fixtures +├── unit/ # 单元测试 +│ ├── __init__.py +│ ├── test_output.py # 输出模块测试 +│ ├── test_config.py # 配置模块测试 +│ └── test_validators.py # 验证函数测试 +├── integration/ # 集成测试 +│ ├── __init__.py +│ ├── test_init.py # aide init 测试 +│ ├── test_env.py # aide env 测试 +│ └── test_config_cmd.py # aide config 测试 +└── fixtures/ # 测试数据 + ├── sample_config.toml + └── sample_gitignore +``` + +### 2.2 命名规范 + +- 测试文件:`test_.py` +- 测试类:`Test` +- 测试函数:`test_` + +**示例**: +```python +# test_config.py +class TestConfig: + def test_load_success(self): + """测试成功加载配置""" + pass + + def test_load_file_not_found(self): + """测试配置文件不存在""" + pass +``` + +--- + +## 三、单元测试 + +### 3.1 core/output.py 测试 + +```python +# tests/unit/test_output.py +import pytest +from core.output import ok, warn, err, info + +def test_ok_simple(capsys): + """测试简单成功输出""" + ok("操作成功") + + captured = capsys.readouterr() + assert "✓ 操作成功" in captured.out + +def test_ok_with_details(capsys): + """测试带详细信息的成功输出""" + ok("操作成功", ["详细信息1", "详细信息2"]) + + captured = capsys.readouterr() + assert "✓ 操作成功" in captured.out + assert " 详细信息1" in captured.out + assert " 详细信息2" in captured.out + +def test_warn(capsys): + """测试警告输出""" + warn("这是警告") + + captured = capsys.readouterr() + assert "⚠ 这是警告" in captured.out + +def test_err(capsys): + """测试错误输出""" + err("这是错误", ["建议: 检查配置"]) + + captured = capsys.readouterr() + assert "✗ 这是错误" in captured.out + assert " 建议: 检查配置" in captured.out + +def test_info(capsys): + """测试信息输出""" + info("正在处理...") + + captured = capsys.readouterr() + assert "→ 正在处理..." in captured.out +``` + +### 3.2 core/config.py 测试 + +```python +# tests/unit/test_config.py +import pytest +from pathlib import Path +from core.config import Config, get_config_value, set_config_value + +def test_get_config_value(): + """测试获取配置值""" + config = { + "task": { + "source": "task-now.md" + } + } + + assert get_config_value(config, "task.source") == "task-now.md" + assert get_config_value(config, "task.spec", "default") == "default" + +def test_set_config_value(): + """测试设置配置值""" + config = {"task": {}} + + set_config_value(config, "task.source", "new-task.md") + assert config["task"]["source"] == "new-task.md" + +def test_config_load(tmp_path): + """测试加载配置""" + # 创建测试配置文件 + config_dir = tmp_path / ".aide" + config_dir.mkdir() + config_path = config_dir / "config.toml" + config_path.write_text('[task]\nsource = "test.md"\n') + + # 加载配置 + config = Config(tmp_path) + config.load() + + assert config.get("task.source") == "test.md" + +def test_config_load_not_found(tmp_path): + """测试配置文件不存在""" + config = Config(tmp_path) + + with pytest.raises(FileNotFoundError): + config.load() + +def test_config_save(tmp_path): + """测试保存配置""" + # 创建配置 + config_dir = tmp_path / ".aide" + config_dir.mkdir() + + config = Config(tmp_path) + config._config = {"task": {"source": "test.md"}} + config.save() + + # 验证文件已创建 + config_path = tmp_path / ".aide" / "config.toml" + assert config_path.exists() + + # 验证内容 + content = config_path.read_text() + assert "task" in content + assert "test.md" in content +``` + +### 3.3 utils/validators.py 测试 + +```python +# tests/unit/test_validators.py +import pytest +from utils.validators import is_valid_version_spec, validate_config + +def test_is_valid_version_spec(): + """测试版本规格验证""" + assert is_valid_version_spec(">=3.10") == True + assert is_valid_version_spec("3.12") == True + assert is_valid_version_spec(">=3.10,<4.0") == True + assert is_valid_version_spec("invalid") == False + +def test_validate_config(): + """测试配置验证""" + # 有效配置 + valid_config = { + "task": {"source": "task.md", "spec": "spec.md"}, + "env": {"python": {"version": ">=3.10"}}, + "output": {"language": "zh-CN"} + } + errors = validate_config(valid_config) + assert len(errors) == 0 + + # 缺少必需项 + invalid_config = {"task": {}} + errors = validate_config(invalid_config) + assert len(errors) > 0 + assert any("task.source" in e for e in errors) +``` + +--- + +## 四、集成测试 + +### 4.1 aide init 测试 + +```python +# tests/integration/test_init.py +import pytest +from pathlib import Path +from commands.init import cmd_init + +def test_init_success(tmp_path, monkeypatch): + """测试初始化成功""" + monkeypatch.chdir(tmp_path) + + result = cmd_init([]) + + assert result == 0 + assert (tmp_path / ".aide").exists() + assert (tmp_path / ".aide" / "config.toml").exists() + assert (tmp_path / ".gitignore").exists() + +def test_init_idempotent(tmp_path, monkeypatch): + """测试幂等性""" + monkeypatch.chdir(tmp_path) + + # 第一次初始化 + result1 = cmd_init([]) + assert result1 == 0 + + # 读取配置内容 + config_path = tmp_path / ".aide" / "config.toml" + content1 = config_path.read_text() + + # 第二次初始化 + result2 = cmd_init([]) + assert result2 == 0 + + # 验证配置未改变 + content2 = config_path.read_text() + assert content1 == content2 + +def test_init_permission_error(tmp_path, monkeypatch): + """测试权限错误""" + monkeypatch.chdir(tmp_path) + + # 设置只读权限 + tmp_path.chmod(0o444) + + result = cmd_init([]) + + # 恢复权限 + tmp_path.chmod(0o755) + + assert result != 0 +``` + +### 4.2 aide env 测试 + +```python +# tests/integration/test_env.py +import pytest +from commands.env import cmd_env, env_ensure_runtime, env_ensure_project + +def test_env_ensure_runtime_success(): + """测试运行时环境检测成功""" + result = env_ensure_runtime() + assert result == 0 + +def test_env_ensure_project_success(tmp_path, monkeypatch): + """测试项目环境检测成功""" + monkeypatch.chdir(tmp_path) + + # 初始化项目 + from commands.init import cmd_init + cmd_init([]) + + # 检测环境 + result = env_ensure_project() + assert result == 0 + +def test_env_ensure_project_no_config(tmp_path, monkeypatch): + """测试配置文件不存在""" + monkeypatch.chdir(tmp_path) + + result = env_ensure_project() + assert result == 4 + +def test_env_ensure_create_venv(tmp_path, monkeypatch): + """测试自动创建虚拟环境""" + monkeypatch.chdir(tmp_path) + + # 初始化项目 + from commands.init import cmd_init + cmd_init([]) + + # 检测环境(应该自动创建虚拟环境) + result = env_ensure_project() + assert result == 0 + + # 验证虚拟环境已创建 + assert (tmp_path / ".venv").exists() +``` + +### 4.3 aide config 测试 + +```python +# tests/integration/test_config_cmd.py +import pytest +from commands.config_cmd import cmd_config, config_get, config_set + +def test_config_get_success(tmp_path, monkeypatch, capsys): + """测试获取配置成功""" + monkeypatch.chdir(tmp_path) + + # 初始化 + from commands.init import cmd_init + cmd_init([]) + + # 获取配置 + result = config_get("task.source") + assert result == 0 + + captured = capsys.readouterr() + assert "task.source" in captured.out + assert "task-now.md" in captured.out + +def test_config_set_success(tmp_path, monkeypatch): + """测试设置配置成功""" + monkeypatch.chdir(tmp_path) + + # 初始化 + from commands.init import cmd_init + cmd_init([]) + + # 设置配置 + result = config_set("task.source", "new-task.md") + assert result == 0 + + # 验证配置已更新 + result = config_get("task.source") + assert result == 0 + +def test_config_cmd_no_args(capsys): + """测试无参数""" + result = cmd_config([]) + assert result == 2 + + captured = capsys.readouterr() + assert "缺少子命令" in captured.out +``` + +--- + +## 五、跨平台测试 + +### 5.1 路径处理测试 + +```python +def test_path_handling_cross_platform(tmp_path): + """测试跨平台路径处理""" + from pathlib import Path + + # 使用 pathlib 确保跨平台兼容 + aide_dir = tmp_path / ".aide" + config_path = aide_dir / "config.toml" + + aide_dir.mkdir() + config_path.write_text("test") + + assert config_path.exists() + assert config_path.is_file() +``` + +### 5.2 命令执行测试 + +```python +@pytest.mark.skipif(sys.platform == "win32", reason="Unix only") +def test_unix_specific(): + """Unix 特定测试""" + pass + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows only") +def test_windows_specific(): + """Windows 特定测试""" + pass +``` + +--- + +## 六、性能测试 + +### 6.1 执行时间测试 + +```python +import time + +def test_init_performance(tmp_path, monkeypatch): + """测试初始化性能""" + monkeypatch.chdir(tmp_path) + + start = time.time() + result = cmd_init([]) + elapsed = time.time() - start + + assert result == 0 + assert elapsed < 0.2 # 应该在 200ms 内完成 + +def test_config_get_performance(tmp_path, monkeypatch): + """测试配置读取性能""" + monkeypatch.chdir(tmp_path) + + # 初始化 + cmd_init([]) + + # 测试性能 + start = time.time() + result = config_get("task.source") + elapsed = time.time() - start + + assert result == 0 + assert elapsed < 0.1 # 应该在 100ms 内完成 +``` + +### 6.2 内存占用测试 + +```python +import psutil +import os + +def test_memory_usage(): + """测试内存占用""" + process = psutil.Process(os.getpid()) + mem_before = process.memory_info().rss / 1024 / 1024 # MB + + # 执行操作 + cmd_init([]) + + mem_after = process.memory_info().rss / 1024 / 1024 # MB + mem_used = mem_after - mem_before + + assert mem_used < 50 # 应该小于 50MB +``` + +--- + +## 七、测试覆盖率 + +### 7.1 覆盖率要求 + +- **核心模块**:≥ 80% +- **命令模块**:≥ 80% +- **工具模块**:≥ 70% +- **整体**:≥ 75% + +### 7.2 运行覆盖率测试 + +```bash +# 运行测试并生成覆盖率报告 +pytest --cov=src --cov-report=html --cov-report=term + +# 查看覆盖率报告 +open htmlcov/index.html +``` + +### 7.3 覆盖率配置 + +```ini +# .coveragerc +[run] +source = src +omit = + */tests/* + */venv/* + */__pycache__/* + +[report] +exclude_lines = + pragma: no cover + def __repr__ + raise AssertionError + raise NotImplementedError + if __name__ == .__main__.: +``` + +--- + +## 八、测试 Fixtures + +### 8.1 conftest.py + +```python +# tests/conftest.py +import pytest +from pathlib import Path +import shutil + +@pytest.fixture +def temp_project(tmp_path): + """创建临时项目目录""" + project_dir = tmp_path / "test_project" + project_dir.mkdir() + return project_dir + +@pytest.fixture +def initialized_project(temp_project, monkeypatch): + """创建已初始化的项目""" + monkeypatch.chdir(temp_project) + from commands.init import cmd_init + cmd_init([]) + return temp_project + +@pytest.fixture +def sample_config(): + """示例配置""" + return { + "task": { + "source": "task-now.md", + "spec": "task-spec.md" + }, + "env": { + "python": { + "version": ">=3.10", + "venv": ".venv" + } + } + } +``` + +--- + +## 九、持续集成 + +### 9.1 GitHub Actions 配置 + +```yaml +# .github/workflows/test.yml +name: Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pytest-mock + pip install tomli tomli-w + + - name: Run tests + run: | + pytest --cov=src --cov-report=xml + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml +``` + +--- + +## 十、测试最佳实践 + +### 10.1 测试原则 + +1. **独立性**:每个测试独立运行,不依赖其他测试 +2. **可重复性**:多次运行结果一致 +3. **清晰性**:测试意图明确,易于理解 +4. **快速性**:测试执行快速,及时反馈 + +### 10.2 测试模式 + +**AAA 模式**(Arrange-Act-Assert): + +```python +def test_example(): + # Arrange:准备测试数据 + config = {"task": {"source": "test.md"}} + + # Act:执行操作 + result = get_config_value(config, "task.source") + + # Assert:验证结果 + assert result == "test.md" +``` + +### 10.3 Mock 使用 + +```python +from unittest.mock import Mock, patch + +def test_with_mock(monkeypatch): + """使用 mock 测试""" + # Mock 函数 + mock_func = Mock(return_value=True) + monkeypatch.setattr("module.function", mock_func) + + # 执行测试 + result = some_function() + + # 验证 mock 被调用 + mock_func.assert_called_once() +``` + +--- + +## 十一、测试检查清单 + +### 11.1 单元测试 + +- [ ] core/output.py 所有函数 +- [ ] core/config.py 所有函数 +- [ ] utils/validators.py 所有函数 +- [ ] 边界条件测试 +- [ ] 错误路径测试 + +### 11.2 集成测试 + +- [ ] aide init 完整流程 +- [ ] aide env ensure --runtime +- [ ] aide env ensure +- [ ] aide config get +- [ ] aide config set +- [ ] 命令组合测试 + +### 11.3 跨平台测试 + +- [ ] Linux 测试通过 +- [ ] macOS 测试通过 +- [ ] Windows 测试通过 +- [ ] 路径处理正确 + +### 11.4 性能测试 + +- [ ] 执行时间符合要求 +- [ ] 内存占用符合要求 +- [ ] 无内存泄漏 + +### 11.5 覆盖率 + +- [ ] 核心模块 ≥ 80% +- [ ] 命令模块 ≥ 80% +- [ ] 整体 ≥ 75% + +--- + +## 十二、总结 + +### 12.1 核心要点 + +1. 完整的测试覆盖(单元、集成、跨平台) +2. 明确的覆盖率要求 +3. 持续集成自动化 +4. 遵循测试最佳实践 + +### 12.2 运行测试 + +```bash +# 运行所有测试 +pytest + +# 运行特定测试 +pytest tests/unit/test_config.py + +# 运行带覆盖率的测试 +pytest --cov=src + +# 运行性能测试 +pytest -m performance + +# 运行跨平台测试 +pytest -m cross_platform +``` + +--- + +**版本**:v1.0 +**更新日期**:2025-12-13 diff --git a/reply/re-04.md b/reply/re-04.md new file mode 100644 index 0000000..639e3a9 --- /dev/null +++ b/reply/re-04.md @@ -0,0 +1,5 @@ +在aide-program/目录下,编写一套详细准确的,可用于开始开发aide体系程序的文档(但不包含aide flow和aide decide), + +注意:这些文档必须足够详细足够准确,但是不允许直接给出具体的代码片段(可以定义细节、逻辑、接口、数据结构,但不允许在设计这一步给定固定死的代码实现) + +然后我将会把aide-program/目录单独打包交给开发人员,完成好程序的开发(封装脚本+python程序)和测试后,再回来我们继续谈flow和decide的具体细节 \ No newline at end of file