546 lines
12 KiB
Markdown
546 lines
12 KiB
Markdown
|
|
# 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
|