Files
agent-aide/aide-program/docs/04-aide-config设计.md
2025-12-13 04:37:41 +08:00

746 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# aide config 设计
## 一、命令概述
### 1.1 功能定位
`aide config` 命令用于读取和修改项目配置文件,提供命令行方式访问配置。
### 1.2 执行时机
- 用户需要查看配置值时
- 用户需要修改配置值时
- Commands 中需要获取配置时(如 prep/exec 的默认文档路径)
### 1.3 命令格式
```bash
aide config get <key>
aide config set <key> <value>
```
**参数**
- `<key>`:配置键,支持点号分隔(如 `task.source`
- `<value>`:配置值(仅 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 <key>",
" aide config set <key> <value>"
]
)
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