Files
agent-aide/aide-program/docs/02-aide-init设计.md

546 lines
12 KiB
Markdown
Raw Normal View History

2025-12-13 04:37:41 +08:00
# 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]
# 可选工具配置
# 是否需要 uvPython 包管理器)
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