moai-adk 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of moai-adk might be problematic. Click here for more details.

Files changed (87) hide show
  1. moai_adk/__init__.py +8 -0
  2. moai_adk/__main__.py +86 -0
  3. moai_adk/cli/__init__.py +2 -0
  4. moai_adk/cli/commands/__init__.py +16 -0
  5. moai_adk/cli/commands/backup.py +56 -0
  6. moai_adk/cli/commands/doctor.py +184 -0
  7. moai_adk/cli/commands/init.py +284 -0
  8. moai_adk/cli/commands/restore.py +77 -0
  9. moai_adk/cli/commands/status.py +79 -0
  10. moai_adk/cli/commands/update.py +133 -0
  11. moai_adk/cli/main.py +12 -0
  12. moai_adk/cli/prompts/__init__.py +5 -0
  13. moai_adk/cli/prompts/init_prompts.py +159 -0
  14. moai_adk/core/__init__.py +2 -0
  15. moai_adk/core/git/__init__.py +24 -0
  16. moai_adk/core/git/branch.py +26 -0
  17. moai_adk/core/git/branch_manager.py +137 -0
  18. moai_adk/core/git/checkpoint.py +140 -0
  19. moai_adk/core/git/commit.py +68 -0
  20. moai_adk/core/git/event_detector.py +81 -0
  21. moai_adk/core/git/manager.py +127 -0
  22. moai_adk/core/project/__init__.py +2 -0
  23. moai_adk/core/project/backup_utils.py +84 -0
  24. moai_adk/core/project/checker.py +302 -0
  25. moai_adk/core/project/detector.py +105 -0
  26. moai_adk/core/project/initializer.py +174 -0
  27. moai_adk/core/project/phase_executor.py +297 -0
  28. moai_adk/core/project/validator.py +118 -0
  29. moai_adk/core/quality/__init__.py +6 -0
  30. moai_adk/core/quality/trust_checker.py +441 -0
  31. moai_adk/core/quality/validators/__init__.py +6 -0
  32. moai_adk/core/quality/validators/base_validator.py +19 -0
  33. moai_adk/core/template/__init__.py +8 -0
  34. moai_adk/core/template/backup.py +95 -0
  35. moai_adk/core/template/config.py +95 -0
  36. moai_adk/core/template/languages.py +44 -0
  37. moai_adk/core/template/merger.py +117 -0
  38. moai_adk/core/template/processor.py +310 -0
  39. moai_adk/templates/.claude/agents/alfred/cc-manager.md +474 -0
  40. moai_adk/templates/.claude/agents/alfred/code-builder.md +534 -0
  41. moai_adk/templates/.claude/agents/alfred/debug-helper.md +302 -0
  42. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +175 -0
  43. moai_adk/templates/.claude/agents/alfred/git-manager.md +200 -0
  44. moai_adk/templates/.claude/agents/alfred/project-manager.md +152 -0
  45. moai_adk/templates/.claude/agents/alfred/spec-builder.md +256 -0
  46. moai_adk/templates/.claude/agents/alfred/tag-agent.md +247 -0
  47. moai_adk/templates/.claude/agents/alfred/trust-checker.md +332 -0
  48. moai_adk/templates/.claude/commands/alfred/0-project.md +523 -0
  49. moai_adk/templates/.claude/commands/alfred/1-spec.md +531 -0
  50. moai_adk/templates/.claude/commands/alfred/2-build.md +413 -0
  51. moai_adk/templates/.claude/commands/alfred/3-sync.md +552 -0
  52. moai_adk/templates/.claude/hooks/alfred/README.md +238 -0
  53. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +165 -0
  54. moai_adk/templates/.claude/hooks/alfred/core/__init__.py +79 -0
  55. moai_adk/templates/.claude/hooks/alfred/core/checkpoint.py +271 -0
  56. moai_adk/templates/.claude/hooks/alfred/core/context.py +110 -0
  57. moai_adk/templates/.claude/hooks/alfred/core/project.py +284 -0
  58. moai_adk/templates/.claude/hooks/alfred/core/tags.py +244 -0
  59. moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +23 -0
  60. moai_adk/templates/.claude/hooks/alfred/handlers/compact.py +51 -0
  61. moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +25 -0
  62. moai_adk/templates/.claude/hooks/alfred/handlers/session.py +80 -0
  63. moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +71 -0
  64. moai_adk/templates/.claude/hooks/alfred/handlers/user.py +41 -0
  65. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +635 -0
  66. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +691 -0
  67. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +469 -0
  68. moai_adk/templates/.claude/settings.json +135 -0
  69. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +68 -0
  70. moai_adk/templates/.github/workflows/moai-gitflow.yml +255 -0
  71. moai_adk/templates/.gitignore +41 -0
  72. moai_adk/templates/.moai/config.json +89 -0
  73. moai_adk/templates/.moai/memory/development-guide.md +367 -0
  74. moai_adk/templates/.moai/memory/spec-metadata.md +277 -0
  75. moai_adk/templates/.moai/project/product.md +121 -0
  76. moai_adk/templates/.moai/project/structure.md +150 -0
  77. moai_adk/templates/.moai/project/tech.md +221 -0
  78. moai_adk/templates/CLAUDE.md +733 -0
  79. moai_adk/templates/__init__.py +2 -0
  80. moai_adk/utils/__init__.py +8 -0
  81. moai_adk/utils/banner.py +42 -0
  82. moai_adk/utils/logger.py +152 -0
  83. moai_adk-0.3.0.dist-info/METADATA +20 -0
  84. moai_adk-0.3.0.dist-info/RECORD +87 -0
  85. moai_adk-0.3.0.dist-info/WHEEL +4 -0
  86. moai_adk-0.3.0.dist-info/entry_points.txt +2 -0
  87. moai_adk-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,302 @@
1
+ # @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_cli_commands.py
2
+ """System requirements validation module.
3
+
4
+ Checks whether required and optional tools are installed.
5
+ """
6
+
7
+ import platform
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ class SystemChecker:
15
+ """Validate system requirements."""
16
+
17
+ REQUIRED_TOOLS: dict[str, str] = {
18
+ "git": "git --version",
19
+ "python": "python3 --version",
20
+ }
21
+
22
+ OPTIONAL_TOOLS: dict[str, str] = {
23
+ "gh": "gh --version",
24
+ "docker": "docker --version",
25
+ }
26
+
27
+ # @CODE:CLI-001:DATA - Language toolchain mapping (20 languages)
28
+ LANGUAGE_TOOLS: dict[str, dict[str, list[str]]] = {
29
+ "python": {
30
+ "required": ["python3", "pip"],
31
+ "recommended": ["pytest", "mypy", "ruff"],
32
+ "optional": ["black", "pylint"],
33
+ },
34
+ "typescript": {
35
+ "required": ["node", "npm"],
36
+ "recommended": ["vitest", "biome"],
37
+ "optional": ["typescript", "eslint"],
38
+ },
39
+ "javascript": {
40
+ "required": ["node", "npm"],
41
+ "recommended": ["jest", "eslint"],
42
+ "optional": ["prettier", "webpack"],
43
+ },
44
+ "java": {
45
+ "required": ["java", "javac"],
46
+ "recommended": ["maven", "gradle"],
47
+ "optional": ["junit", "checkstyle"],
48
+ },
49
+ "go": {
50
+ "required": ["go"],
51
+ "recommended": ["golangci-lint", "gofmt"],
52
+ "optional": ["delve", "gopls"],
53
+ },
54
+ "rust": {
55
+ "required": ["rustc", "cargo"],
56
+ "recommended": ["rustfmt", "clippy"],
57
+ "optional": ["rust-analyzer", "cargo-audit"],
58
+ },
59
+ "dart": {
60
+ "required": ["dart"],
61
+ "recommended": ["flutter", "dart_test"],
62
+ "optional": ["dartfmt", "dartanalyzer"],
63
+ },
64
+ "swift": {
65
+ "required": ["swift", "swiftc"],
66
+ "recommended": ["xcrun", "swift-format"],
67
+ "optional": ["swiftlint", "sourcekit-lsp"],
68
+ },
69
+ "kotlin": {
70
+ "required": ["kotlin", "kotlinc"],
71
+ "recommended": ["gradle", "ktlint"],
72
+ "optional": ["detekt", "kotlin-language-server"],
73
+ },
74
+ "csharp": {
75
+ "required": ["dotnet"],
76
+ "recommended": ["msbuild", "nuget"],
77
+ "optional": ["csharpier", "roslyn"],
78
+ },
79
+ "php": {
80
+ "required": ["php"],
81
+ "recommended": ["composer", "phpunit"],
82
+ "optional": ["psalm", "phpstan"],
83
+ },
84
+ "ruby": {
85
+ "required": ["ruby", "gem"],
86
+ "recommended": ["bundler", "rspec"],
87
+ "optional": ["rubocop", "solargraph"],
88
+ },
89
+ "elixir": {
90
+ "required": ["elixir", "mix"],
91
+ "recommended": ["hex", "dialyzer"],
92
+ "optional": ["credo", "ex_unit"],
93
+ },
94
+ "scala": {
95
+ "required": ["scala", "scalac"],
96
+ "recommended": ["sbt", "scalatest"],
97
+ "optional": ["scalafmt", "metals"],
98
+ },
99
+ "clojure": {
100
+ "required": ["clojure", "clj"],
101
+ "recommended": ["leiningen", "clojure.test"],
102
+ "optional": ["cider", "clj-kondo"],
103
+ },
104
+ "haskell": {
105
+ "required": ["ghc", "ghci"],
106
+ "recommended": ["cabal", "stack"],
107
+ "optional": ["hlint", "haskell-language-server"],
108
+ },
109
+ "c": {
110
+ "required": ["gcc", "make"],
111
+ "recommended": ["clang", "cmake"],
112
+ "optional": ["gdb", "valgrind"],
113
+ },
114
+ "cpp": {
115
+ "required": ["g++", "make"],
116
+ "recommended": ["clang++", "cmake"],
117
+ "optional": ["gdb", "cppcheck"],
118
+ },
119
+ "lua": {
120
+ "required": ["lua"],
121
+ "recommended": ["luarocks", "busted"],
122
+ "optional": ["luacheck", "lua-language-server"],
123
+ },
124
+ "ocaml": {
125
+ "required": ["ocaml", "opam"],
126
+ "recommended": ["dune", "ocamlformat"],
127
+ "optional": ["merlin", "ocp-indent"],
128
+ },
129
+ }
130
+
131
+ def check_all(self) -> dict[str, bool]:
132
+ """Validate every tool.
133
+
134
+ Returns:
135
+ Dictionary mapping tool names to availability.
136
+ """
137
+ result = {}
138
+
139
+ # Check required tools
140
+ for tool, command in self.REQUIRED_TOOLS.items():
141
+ result[tool] = self._check_tool(command)
142
+
143
+ # Check optional tools
144
+ for tool, command in self.OPTIONAL_TOOLS.items():
145
+ result[tool] = self._check_tool(command)
146
+
147
+ return result
148
+
149
+ def _check_tool(self, command: str) -> bool:
150
+ """Check an individual tool.
151
+
152
+ Args:
153
+ command: Command to run (e.g., "git --version").
154
+
155
+ Returns:
156
+ True when the tool is available.
157
+ """
158
+ if not command:
159
+ return False
160
+
161
+ try:
162
+ # Extract the tool name (first token)
163
+ tool_name = command.split()[0]
164
+ # Determine availability via shutil.which
165
+ return shutil.which(tool_name) is not None
166
+ except Exception:
167
+ return False
168
+
169
+ def check_language_tools(self, language: str | None) -> dict[str, bool]:
170
+ """Validate toolchains by language.
171
+
172
+ Args:
173
+ language: Programming language name (e.g., "python", "typescript").
174
+
175
+ Returns:
176
+ Dictionary mapping tool names to availability.
177
+ """
178
+ # Guard clause: no language specified
179
+ if not language:
180
+ return {}
181
+
182
+ language_lower = language.lower()
183
+
184
+ # Guard clause: unsupported language
185
+ if language_lower not in self.LANGUAGE_TOOLS:
186
+ return {}
187
+
188
+ # Retrieve tool configuration for the language
189
+ tools_config = self.LANGUAGE_TOOLS[language_lower]
190
+
191
+ # Evaluate tools by category and collect results
192
+ result: dict[str, bool] = {}
193
+ for category in ["required", "recommended", "optional"]:
194
+ tools = tools_config.get(category, [])
195
+ for tool in tools:
196
+ result[tool] = self._is_tool_available(tool)
197
+
198
+ return result
199
+
200
+ def _is_tool_available(self, tool: str) -> bool:
201
+ """Check whether a tool is available (helper).
202
+
203
+ Args:
204
+ tool: Tool name.
205
+
206
+ Returns:
207
+ True when the tool is available.
208
+ """
209
+ return shutil.which(tool) is not None
210
+
211
+ def get_tool_version(self, tool: str | None) -> str | None:
212
+ """Retrieve tool version information.
213
+
214
+ Args:
215
+ tool: Tool name (for example, "python3", "node").
216
+
217
+ Returns:
218
+ Version string or None when the tool is unavailable.
219
+ """
220
+ # Guard clause: unspecified or unavailable tool
221
+ if not tool or not self._is_tool_available(tool):
222
+ return None
223
+
224
+ try:
225
+ # Call the tool with --version to obtain the version string
226
+ result = subprocess.run(
227
+ [tool, "--version"],
228
+ capture_output=True,
229
+ text=True,
230
+ timeout=2, # 2-second timeout to respect performance constraints
231
+ check=False,
232
+ )
233
+
234
+ # Return the version when the command succeeds
235
+ if result.returncode == 0 and result.stdout:
236
+ return self._extract_version_line(result.stdout)
237
+
238
+ return None
239
+
240
+ except (subprocess.TimeoutExpired, OSError):
241
+ # Gracefully handle timeout and OS errors
242
+ return None
243
+
244
+ def _extract_version_line(self, version_output: str) -> str:
245
+ """Extract the first line from version output (helper).
246
+
247
+ Args:
248
+ version_output: Output captured from the --version command.
249
+
250
+ Returns:
251
+ First line containing version information.
252
+ """
253
+ return version_output.strip().split("\n")[0]
254
+
255
+
256
+ def check_environment() -> dict[str, bool]:
257
+ """Validate the overall environment (used by the CLI doctor command).
258
+
259
+ Returns:
260
+ Mapping from check description to boolean status.
261
+ """
262
+ return {
263
+ "Python >= 3.13": sys.version_info >= (3, 13),
264
+ "Git installed": shutil.which("git") is not None,
265
+ "Project structure (.moai/)": Path(".moai").exists(),
266
+ "Config file (.moai/config.json)": Path(".moai/config.json").exists(),
267
+ }
268
+
269
+
270
+ def get_platform_specific_message(unix_message: str, windows_message: str) -> str:
271
+ """Return platform-specific message.
272
+
273
+ Args:
274
+ unix_message: Message for Unix/Linux/macOS.
275
+ windows_message: Message for Windows.
276
+
277
+ Returns:
278
+ Platform-appropriate message.
279
+
280
+ Examples:
281
+ >>> get_platform_specific_message("chmod 755 .moai", "Check directory permissions")
282
+ 'chmod 755 .moai' # on Unix/Linux/macOS
283
+ >>> get_platform_specific_message("chmod 755 .moai", "Check directory permissions")
284
+ 'Check directory permissions' # on Windows
285
+ """
286
+ if platform.system() == "Windows":
287
+ return windows_message
288
+ return unix_message
289
+
290
+
291
+ def get_permission_fix_message(path: str) -> str:
292
+ """Get platform-specific permission fix message.
293
+
294
+ Args:
295
+ path: Path to fix permissions for.
296
+
297
+ Returns:
298
+ Platform-specific fix instructions.
299
+ """
300
+ if platform.system() == "Windows":
301
+ return f"관리자 권한으로 실행하거나 '{path}' 디렉토리 속성에서 권한을 확인하세요"
302
+ return f"chmod 755 {path} 실행 후 재시도하세요"
@@ -0,0 +1,105 @@
1
+ # @CODE:CORE-PROJECT-001 | SPEC: SPEC-CORE-PROJECT-001.md | TEST: tests/unit/test_language_detector.py
2
+ """Language detector module.
3
+
4
+ Automatically detects 20 programming languages.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+
10
+ class LanguageDetector:
11
+ """Automatically detect up to 20 programming languages."""
12
+
13
+ LANGUAGE_PATTERNS = {
14
+ "python": ["*.py", "pyproject.toml", "requirements.txt", "setup.py"],
15
+ "typescript": ["*.ts", "tsconfig.json"],
16
+ "javascript": ["*.js", "package.json"],
17
+ "java": ["*.java", "pom.xml", "build.gradle"],
18
+ "go": ["*.go", "go.mod"],
19
+ "rust": ["*.rs", "Cargo.toml"],
20
+ "dart": ["*.dart", "pubspec.yaml"],
21
+ "swift": ["*.swift", "Package.swift"],
22
+ "kotlin": ["*.kt", "build.gradle.kts"],
23
+ "csharp": ["*.cs", "*.csproj"],
24
+ "php": ["*.php", "composer.json"],
25
+ "ruby": ["*.rb", "Gemfile"],
26
+ "elixir": ["*.ex", "mix.exs"],
27
+ "scala": ["*.scala", "build.sbt"],
28
+ "clojure": ["*.clj", "project.clj"],
29
+ "haskell": ["*.hs", "*.cabal"],
30
+ "c": ["*.c", "Makefile"],
31
+ "cpp": ["*.cpp", "CMakeLists.txt"],
32
+ "shell": ["*.sh", "*.bash"],
33
+ "lua": ["*.lua"],
34
+ }
35
+
36
+ def detect(self, path: str | Path = ".") -> str | None:
37
+ """Detect a single language (in priority order).
38
+
39
+ Args:
40
+ path: Directory to inspect.
41
+
42
+ Returns:
43
+ Detected language name (lowercase) or None.
44
+ """
45
+ path = Path(path)
46
+
47
+ # Inspect each language in priority order
48
+ for language, patterns in self.LANGUAGE_PATTERNS.items():
49
+ if self._check_patterns(path, patterns):
50
+ return language
51
+
52
+ return None
53
+
54
+ def detect_multiple(self, path: str | Path = ".") -> list[str]:
55
+ """Detect multiple languages.
56
+
57
+ Args:
58
+ path: Directory to inspect.
59
+
60
+ Returns:
61
+ List of all detected language names.
62
+ """
63
+ path = Path(path)
64
+ detected = []
65
+
66
+ for language, patterns in self.LANGUAGE_PATTERNS.items():
67
+ if self._check_patterns(path, patterns):
68
+ detected.append(language)
69
+
70
+ return detected
71
+
72
+ def _check_patterns(self, path: Path, patterns: list[str]) -> bool:
73
+ """Check whether any pattern matches.
74
+
75
+ Args:
76
+ path: Directory to inspect.
77
+ patterns: List of glob patterns.
78
+
79
+ Returns:
80
+ True when any pattern matches.
81
+ """
82
+ for pattern in patterns:
83
+ # Extension pattern (e.g., *.py)
84
+ if pattern.startswith("*."):
85
+ if list(path.rglob(pattern)):
86
+ return True
87
+ # Specific file name (e.g., pyproject.toml)
88
+ else:
89
+ if (path / pattern).exists():
90
+ return True
91
+
92
+ return False
93
+
94
+
95
+ def detect_project_language(path: str | Path = ".") -> str | None:
96
+ """Detect the project language (helper).
97
+
98
+ Args:
99
+ path: Directory to inspect (default: current directory).
100
+
101
+ Returns:
102
+ Detected language name (lowercase) or None.
103
+ """
104
+ detector = LanguageDetector()
105
+ return detector.detect(path)
@@ -0,0 +1,174 @@
1
+ # @CODE:CORE-PROJECT-001 | @CODE:INIT-003:INIT
2
+ # SPEC: SPEC-CORE-PROJECT-001.md, SPEC-INIT-003.md
3
+ # TEST: tests/unit/test_project_initializer.py, tests/unit/test_init_reinit.py
4
+ """Project Initialization Module
5
+
6
+ Phase-based 5-step initialization process:
7
+ 1. Preparation: Backup and validation
8
+ 2. Directory: Create directory structure
9
+ 3. Resource: Copy template resources
10
+ 4. Configuration: Generate configuration files
11
+ 5. Validation: Verification and finalization
12
+ """
13
+
14
+ import time
15
+ from pathlib import Path
16
+
17
+ from moai_adk.core.project.detector import LanguageDetector
18
+ from moai_adk.core.project.phase_executor import PhaseExecutor, ProgressCallback
19
+ from moai_adk.core.project.validator import ProjectValidator
20
+
21
+
22
+ class InstallationResult:
23
+ """Installation result"""
24
+
25
+ def __init__(
26
+ self,
27
+ success: bool,
28
+ project_path: str,
29
+ language: str,
30
+ mode: str,
31
+ locale: str,
32
+ duration: int,
33
+ created_files: list[str],
34
+ errors: list[str] | None = None,
35
+ ) -> None:
36
+ self.success = success
37
+ self.project_path = project_path
38
+ self.language = language
39
+ self.mode = mode
40
+ self.locale = locale
41
+ self.duration = duration
42
+ self.created_files = created_files
43
+ self.errors = errors or []
44
+
45
+
46
+ class ProjectInitializer:
47
+ """Project initializer (Phase-based)"""
48
+
49
+ def __init__(self, path: str | Path = ".") -> None:
50
+ """Initialize
51
+
52
+ Args:
53
+ path: Project root directory
54
+ """
55
+ self.path = Path(path).resolve()
56
+ self.detector = LanguageDetector()
57
+ self.validator = ProjectValidator()
58
+ self.executor = PhaseExecutor(self.validator)
59
+
60
+ def initialize(
61
+ self,
62
+ mode: str = "personal",
63
+ locale: str = "ko",
64
+ language: str | None = None,
65
+ backup_enabled: bool = True,
66
+ progress_callback: ProgressCallback | None = None,
67
+ reinit: bool = False,
68
+ ) -> InstallationResult:
69
+ """Execute project initialization (5-phase process)
70
+
71
+ Args:
72
+ mode: Project mode (personal/team)
73
+ locale: Locale (ko/en/ja/zh)
74
+ language: Force language specification (auto-detect if None)
75
+ backup_enabled: Whether to enable backup
76
+ progress_callback: Progress callback
77
+ reinit: Reinitialization mode (v0.3.0, SPEC-INIT-003)
78
+
79
+ Returns:
80
+ InstallationResult object
81
+
82
+ Raises:
83
+ FileExistsError: If project is already initialized (when reinit=False)
84
+ """
85
+ start_time = time.time()
86
+
87
+ try:
88
+ # Prevent duplicate initialization (only when not in reinit mode)
89
+ if self.is_initialized() and not reinit:
90
+ raise FileExistsError(
91
+ f"Project already initialized at {self.path}/.moai/\n"
92
+ f"Use 'python -m moai_adk status' to check the current configuration."
93
+ )
94
+
95
+ # Detect language
96
+ detected_language = language or self.detector.detect(self.path) or "generic"
97
+
98
+ # Phase 1: Preparation (backup and validation)
99
+ self.executor.execute_preparation_phase(
100
+ self.path, backup_enabled, progress_callback
101
+ )
102
+
103
+ # Phase 2: Directory (create directories)
104
+ self.executor.execute_directory_phase(self.path, progress_callback)
105
+
106
+ # Phase 3: Resource (copy templates)
107
+ resource_files = self.executor.execute_resource_phase(
108
+ self.path, progress_callback
109
+ )
110
+
111
+ # Phase 4: Configuration (generate config)
112
+ config = {
113
+ "projectName": self.path.name,
114
+ "mode": mode,
115
+ "locale": locale,
116
+ "language": detected_language,
117
+ }
118
+ config_files = self.executor.execute_configuration_phase(
119
+ self.path, config, progress_callback
120
+ )
121
+
122
+ # Phase 5: Validation (verify and finalize)
123
+ self.executor.execute_validation_phase(
124
+ self.path, mode, progress_callback
125
+ )
126
+
127
+ # Generate result
128
+ duration = int((time.time() - start_time) * 1000) # ms
129
+ return InstallationResult(
130
+ success=True,
131
+ project_path=str(self.path),
132
+ language=detected_language,
133
+ mode=mode,
134
+ locale=locale,
135
+ duration=duration,
136
+ created_files=resource_files + config_files,
137
+ )
138
+
139
+ except Exception as e:
140
+ duration = int((time.time() - start_time) * 1000)
141
+ return InstallationResult(
142
+ success=False,
143
+ project_path=str(self.path),
144
+ language=language or "unknown",
145
+ mode=mode,
146
+ locale=locale,
147
+ duration=duration,
148
+ created_files=[],
149
+ errors=[str(e)],
150
+ )
151
+
152
+ def is_initialized(self) -> bool:
153
+ """Check if .moai/ directory exists
154
+
155
+ Returns:
156
+ Whether initialized
157
+ """
158
+ return (self.path / ".moai").exists()
159
+
160
+
161
+ def initialize_project(
162
+ project_path: Path, progress_callback: ProgressCallback | None = None
163
+ ) -> InstallationResult:
164
+ """Initialize project (for CLI command)
165
+
166
+ Args:
167
+ project_path: Project directory path
168
+ progress_callback: Progress callback
169
+
170
+ Returns:
171
+ InstallationResult object
172
+ """
173
+ initializer = ProjectInitializer(project_path)
174
+ return initializer.initialize(progress_callback=progress_callback)