""" 项目引导模块 — 确保 AirPlan 目录结构存在。 V2 保持与 V1 相同的不变量:制品驱动通信、上下文隔离。 """ from __future__ import annotations import subprocess from pathlib import Path def ensure_git_initialized(project_root: Path) -> dict: """3.2.5: Arc 规划前 git 初始化检测。 检测项目是否为 git 仓库,至少有 1 个 commit。 如果没有则自动初始化并创建空提交,确保 worktree 操作可行。 """ git_dir = project_root / ".git" result = {"git": False, "commits": False, "initialized": False} if git_dir.exists(): result["git"] = True try: r = subprocess.run( ["git", "rev-parse", "HEAD"], cwd=project_root, capture_output=True, text=True, timeout=10, ) if r.returncode == 0: result["commits"] = True return result except Exception: pass # 初始化 git if not result["git"]: try: subprocess.run( ["git", "init"], cwd=project_root, capture_output=True, timeout=10, ) result["git"] = True except Exception as e: result["error"] = f"git init failed: {e}" return result # 创建初始提交 try: subprocess.run( ["git", "config", "user.email", "airplan@local"], cwd=project_root, capture_output=True, timeout=5, ) subprocess.run( ["git", "config", "user.name", "AirPlan"], cwd=project_root, capture_output=True, timeout=5, ) # 创建 .gitkeep 确保有文件可提交 gitkeep = project_root / ".gitkeep" if not gitkeep.exists(): gitkeep.touch() subprocess.run( ["git", "add", ".gitkeep"], cwd=project_root, capture_output=True, timeout=10, ) subprocess.run( ["git", "commit", "-m", "AirPlan: initial empty commit"], cwd=project_root, capture_output=True, timeout=10, ) result["commits"] = True result["initialized"] = True except Exception as e: result["error"] = f"initial commit failed: {e}" return result def ensure_project_bootstrap(project_root: Path) -> dict[str, bool]: """创建 AirPlan 必需目录结构。""" root = project_root / "AirPlan" docs = root / "docs" arch = docs / "architecture" adr_dir = arch / "adr" c4_dir = arch / "c4" debug_dir = docs / "debug" state = root / "state" dirs = [ root, docs, arch, adr_dir, c4_dir, debug_dir, state, state / "airarc" / "reviews", state / "aireng" / "dispatch", state / "aireng" / "archive", state / "aireng" / "plans", state / "airdo" / "tasks", state / "airdbg" / "sessions", state / "airdbg" / "snapshots", state / "airxdb" / "artifacts", state / "airsdb" / "reports", state / "airndb" / "captures", state / "aircontext", state / "airdep" / "sessions", state / "airtst" / "reports", state / "airsec", state / "airrvr" / "reviews", ] for d in dirs: d.mkdir(parents=True, exist_ok=True) # 创建必要文件 (root / "AGENTS.md").touch() (root / "plan.md").touch() (root / "todo.md").touch() (adr_dir / "placeholder.md").touch() (c4_dir / "module.md").touch() (debug_dir / "debug-log.md").touch() (debug_dir / "gui-debug-log.md").touch() (docs / "staticanalysis.md").touch() return {"bootstrap": True}