Files
agent-aide/aide-program/aide/core/config.py

110 lines
3.5 KiB
Python
Raw Normal View History

2025-12-13 05:01:28 +08:00
"""配置管理:生成默认配置、读取/写入配置、维护 .aide 目录与 .gitignore。"""
from __future__ import annotations
from pathlib import Path
from typing import Any
import tomllib
from tomli_w import dumps as toml_dumps
from aide.core import output
DEFAULT_CONFIG = """# Aide 默认配置(由 aide init 生成)
# runtime: aide 自身运行要求
# task: 任务文档路径
# env: 虚拟环境与依赖配置
# flow: 环节名称列表,供流程校验使用
[runtime]
python_min = "3.11"
use_uv = true
[task]
source = "task-now.md"
spec = "task-spec.md"
[env]
venv = ".venv"
requirements = "requirements.txt"
[flow]
phases = ["task-optimize", "flow-design", "impl", "verify", "docs", "finish"]
"""
class ConfigManager:
def __init__(self, root: Path):
self.root = root
self.aide_dir = self.root / ".aide"
self.config_path = self.aide_dir / "config.toml"
self.decisions_dir = self.aide_dir / "decisions"
self.logs_dir = self.aide_dir / "logs"
def ensure_base_dirs(self) -> None:
self.aide_dir.mkdir(parents=True, exist_ok=True)
self.decisions_dir.mkdir(parents=True, exist_ok=True)
self.logs_dir.mkdir(parents=True, exist_ok=True)
def ensure_gitignore(self) -> None:
gitignore_path = self.root / ".gitignore"
marker = ".aide/"
if gitignore_path.exists():
content = gitignore_path.read_text(encoding="utf-8").splitlines()
if any(line.strip() == marker for line in content):
return
content.append(marker)
gitignore_path.write_text("\n".join(content) + "\n", encoding="utf-8")
else:
gitignore_path.write_text(f"{marker}\n", encoding="utf-8")
def ensure_config(self) -> dict[str, Any]:
self.ensure_base_dirs()
if not self.config_path.exists():
self.config_path.write_text(DEFAULT_CONFIG, encoding="utf-8")
output.ok("已创建默认配置 .aide/config.toml")
return self.load_config()
def load_config(self) -> dict[str, Any]:
if not self.config_path.exists():
return {}
try:
with self.config_path.open("rb") as f:
return tomllib.load(f)
except Exception as exc: # pragma: no cover - 兼容性输出
output.err(f"读取配置失败: {exc}")
return {}
def get_value(self, key: str) -> Any:
data = self.load_config()
return self._walk_get(data, key)
def set_value(self, key: str, value: Any) -> None:
data = self.ensure_config()
self._walk_set(data, key, value)
self._write_config(data)
output.ok(f"已更新 {key} = {value!r}")
def _write_config(self, data: dict[str, Any]) -> None:
self.config_path.write_text(toml_dumps(data), encoding="utf-8")
@staticmethod
def _walk_get(data: dict[str, Any], dotted_key: str) -> Any:
current: Any = data
for part in dotted_key.split("."):
if not isinstance(current, dict):
return None
if part not in current:
return None
current = current[part]
return current
@staticmethod
def _walk_set(data: dict[str, Any], dotted_key: str, value: Any) -> None:
parts = dotted_key.split(".")
current = data
for part in parts[:-1]:
if part not in current or not isinstance(current[part], dict):
current[part] = {}
current = current[part]
current[parts[-1]] = value