Files
agent-aide/aide-program/docs/04-aide-config设计.md

746 lines
16 KiB
Markdown
Raw Normal View History

2025-12-13 04:37:41 +08:00
# 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