Coverage for src / moai_adk / core / project / validator.py: 23.08%

52 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-20 20:52 +0900

1"""Project initialization validation module. 

2 

3Validates system requirements and installation results. 

4 

5SPEC-INIT-004 Enhancement: 

6- Alfred command files validation (Phase 5) 

7- Explicit missing files reporting 

8- Required files verification checklist 

9""" 

10 

11import shutil 

12from pathlib import Path 

13 

14 

15class ValidationError(Exception): 

16 """Raised when validation fails.""" 

17 

18 pass 

19 

20 

21class ProjectValidator: 

22 """Validate project initialization.""" 

23 

24 # Required directory structure 

25 REQUIRED_DIRECTORIES = [ 

26 ".moai/", 

27 ".moai/project/", 

28 ".moai/specs/", 

29 ".moai/memory/", 

30 ".claude/", 

31 ".github/", 

32 ] 

33 

34 # Required files 

35 REQUIRED_FILES = [ 

36 ".moai/config/config.json", 

37 "CLAUDE.md", 

38 ] 

39 

40 # Required Alfred command files (SPEC-INIT-004) 

41 REQUIRED_ALFRED_COMMANDS = [ 

42 "0-project.md", 

43 "1-plan.md", 

44 "2-run.md", 

45 "3-sync.md", 

46 ] 

47 

48 def validate_system_requirements(self) -> None: 

49 """Verify system requirements. 

50 

51 Raises: 

52 ValidationError: Raised when requirements are not satisfied. 

53 """ 

54 # Ensure Git is installed 

55 if not shutil.which("git"): 

56 raise ValidationError("Git is not installed") 

57 

58 # Check Python version (3.10+) 

59 import sys 

60 

61 if sys.version_info < (3, 10): 

62 raise ValidationError( 

63 f"Python 3.10+ required (current: {sys.version_info.major}.{sys.version_info.minor})" 

64 ) 

65 

66 def validate_project_path(self, project_path: Path) -> None: 

67 """Verify the project path. 

68 

69 Args: 

70 project_path: Project path. 

71 

72 Raises: 

73 ValidationError: Raised when the path is invalid. 

74 """ 

75 # Must be an absolute path 

76 if not project_path.is_absolute(): 

77 raise ValidationError(f"Project path must be absolute: {project_path}") 

78 

79 # Parent directory must exist 

80 if not project_path.parent.exists(): 

81 raise ValidationError( 

82 f"Parent directory does not exist: {project_path.parent}" 

83 ) 

84 

85 # Prevent initialization inside the MoAI-ADK package 

86 if self._is_inside_moai_package(project_path): 

87 raise ValidationError("Cannot initialize inside MoAI-ADK package directory") 

88 

89 def validate_installation(self, project_path: Path) -> None: 

90 """Validate installation results. 

91 

92 

93 Args: 

94 project_path: Project path. 

95 

96 Raises: 

97 ValidationError: Raised when installation was incomplete. 

98 """ 

99 # Verify required directories 

100 for directory in self.REQUIRED_DIRECTORIES: 

101 dir_path = project_path / directory 

102 if not dir_path.exists(): 

103 raise ValidationError(f"Required directory not found: {directory}") 

104 

105 # Verify required files 

106 for file in self.REQUIRED_FILES: 

107 file_path = project_path / file 

108 if not file_path.exists(): 

109 raise ValidationError(f"Required file not found: {file}") 

110 

111 moai_commands_dir = project_path / ".claude" / "commands" / "moai" 

112 missing_commands = [] 

113 for cmd in self.REQUIRED_ALFRED_COMMANDS: 

114 cmd_path = moai_commands_dir / cmd 

115 if not cmd_path.exists(): 

116 missing_commands.append(cmd) 

117 

118 if missing_commands: 

119 missing_list = ", ".join(missing_commands) 

120 raise ValidationError( 

121 f"Required Alfred command files not found: {missing_list}" 

122 ) 

123 

124 def _is_inside_moai_package(self, project_path: Path) -> bool: 

125 """Determine whether the path is inside the MoAI-ADK package. 

126 

127 Args: 

128 project_path: Path to check. 

129 

130 Returns: 

131 True when the path resides within the package. 

132 """ 

133 # The package root contains a pyproject.toml referencing moai-adk 

134 current = project_path.resolve() 

135 while current != current.parent: 

136 pyproject = current / "pyproject.toml" 

137 if pyproject.exists(): 

138 try: 

139 content = pyproject.read_text(encoding="utf-8") 

140 if 'name = "moai-adk"' in content or 'name = "moai-adk"' in content: 

141 return True 

142 except Exception: 

143 pass 

144 current = current.parent 

145 return False