746 lines
16 KiB
Markdown
746 lines
16 KiB
Markdown
# 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
|