Coverage for src / moai_adk / core / git / checkpoint.py: 29.27%
41 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1"""
2Checkpoint Manager - Event-driven checkpoint system.
4SPEC: .moai/specs/SPEC-CHECKPOINT-EVENT-001/spec.md
5"""
7from datetime import datetime
8from pathlib import Path
9from typing import Optional
11import git
13from moai_adk.core.git.branch_manager import BranchManager
14from moai_adk.core.git.event_detector import EventDetector
17class CheckpointManager:
18 """Manage creation and restoration of event-driven checkpoints."""
20 def __init__(self, repo: git.Repo, project_root: Path):
21 """
22 Initialize the CheckpointManager.
24 Args:
25 repo: GitPython Repo instance.
26 project_root: Project root directory.
27 """
28 self.repo = repo
29 self.project_root = project_root
30 self.event_detector = EventDetector()
31 self.branch_manager = BranchManager(repo)
32 self.log_file = project_root / ".moai" / "checkpoints.log"
34 # Ensure the log directory exists
35 self.log_file.parent.mkdir(parents=True, exist_ok=True)
37 def create_checkpoint_if_risky(
38 self,
39 operation: str,
40 deleted_files: Optional[list[str]] = None,
41 renamed_files: Optional[list[tuple[str, str]]] = None,
42 modified_files: Optional[list[Path]] = None,
43 ) -> Optional[str]:
44 """
45 Create a checkpoint when a risky operation is detected.
47 SPEC requirement: automatically create a checkpoint for risky actions.
49 Args:
50 operation: Operation type.
51 deleted_files: Files scheduled for deletion.
52 renamed_files: Files that will be renamed.
53 modified_files: Files that will be modified.
55 Returns:
56 Created checkpoint ID (branch name) or None when the operation is safe.
57 """
58 is_risky = False
60 # Identify large deletion operations
61 if deleted_files and self.event_detector.is_risky_deletion(deleted_files):
62 is_risky = True
64 # Identify large-scale refactoring
65 if renamed_files and self.event_detector.is_risky_refactoring(renamed_files):
66 is_risky = True
68 # Check for critical file modifications
69 if modified_files:
70 for file_path in modified_files:
71 if self.event_detector.is_critical_file(file_path):
72 is_risky = True
73 break
75 if not is_risky:
76 return None
78 # Create a checkpoint
79 checkpoint_id = self.branch_manager.create_checkpoint_branch(operation)
81 # Record checkpoint metadata
82 self._log_checkpoint(checkpoint_id, operation)
84 return checkpoint_id
86 def restore_checkpoint(self, checkpoint_id: str) -> None:
87 """
88 Restore the repository to the specified checkpoint.
90 SPEC requirement: capture the current state as a new checkpoint before restoring.
92 Args:
93 checkpoint_id: Target checkpoint ID (branch name).
94 """
95 # Save current state as a safety checkpoint before restoring
96 safety_checkpoint = self.branch_manager.create_checkpoint_branch("restore")
97 self._log_checkpoint(safety_checkpoint, "restore", is_safety=True)
99 # Check out the checkpoint branch
100 self.repo.git.checkout(checkpoint_id)
102 def list_checkpoints(self) -> list[str]:
103 """
104 List all checkpoints.
106 Returns:
107 List of checkpoint IDs.
108 """
109 return self.branch_manager.list_checkpoint_branches()
111 def _log_checkpoint(
112 self, checkpoint_id: str, operation: str, is_safety: bool = False
113 ) -> None:
114 """
115 Append checkpoint metadata to the log file.
117 SPEC requirement: write metadata to .moai/checkpoints.log.
119 Args:
120 checkpoint_id: Checkpoint identifier.
121 operation: Operation type.
122 is_safety: Whether the checkpoint was created for safety.
123 """
124 timestamp = datetime.now().isoformat()
126 log_entry = f"""---
127checkpoint_id: {checkpoint_id}
128operation: {operation}
129timestamp: {timestamp}
130is_safety: {is_safety}
131---
132"""
134 # Append the entry to the log
135 with open(self.log_file, "a", encoding="utf-8") as f:
136 f.write(log_entry)