110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
"""配置管理:生成默认配置、读取/写入配置、维护 .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
|