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.
- moai_adk/__init__.py +8 -0
- moai_adk/__main__.py +86 -0
- moai_adk/cli/__init__.py +2 -0
- moai_adk/cli/commands/__init__.py +16 -0
- moai_adk/cli/commands/backup.py +56 -0
- moai_adk/cli/commands/doctor.py +184 -0
- moai_adk/cli/commands/init.py +284 -0
- moai_adk/cli/commands/restore.py +77 -0
- moai_adk/cli/commands/status.py +79 -0
- moai_adk/cli/commands/update.py +133 -0
- moai_adk/cli/main.py +12 -0
- moai_adk/cli/prompts/__init__.py +5 -0
- moai_adk/cli/prompts/init_prompts.py +159 -0
- moai_adk/core/__init__.py +2 -0
- moai_adk/core/git/__init__.py +24 -0
- moai_adk/core/git/branch.py +26 -0
- moai_adk/core/git/branch_manager.py +137 -0
- moai_adk/core/git/checkpoint.py +140 -0
- moai_adk/core/git/commit.py +68 -0
- moai_adk/core/git/event_detector.py +81 -0
- moai_adk/core/git/manager.py +127 -0
- moai_adk/core/project/__init__.py +2 -0
- moai_adk/core/project/backup_utils.py +84 -0
- moai_adk/core/project/checker.py +302 -0
- moai_adk/core/project/detector.py +105 -0
- moai_adk/core/project/initializer.py +174 -0
- moai_adk/core/project/phase_executor.py +297 -0
- moai_adk/core/project/validator.py +118 -0
- moai_adk/core/quality/__init__.py +6 -0
- moai_adk/core/quality/trust_checker.py +441 -0
- moai_adk/core/quality/validators/__init__.py +6 -0
- moai_adk/core/quality/validators/base_validator.py +19 -0
- moai_adk/core/template/__init__.py +8 -0
- moai_adk/core/template/backup.py +95 -0
- moai_adk/core/template/config.py +95 -0
- moai_adk/core/template/languages.py +44 -0
- moai_adk/core/template/merger.py +117 -0
- moai_adk/core/template/processor.py +310 -0
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +474 -0
- moai_adk/templates/.claude/agents/alfred/code-builder.md +534 -0
- moai_adk/templates/.claude/agents/alfred/debug-helper.md +302 -0
- moai_adk/templates/.claude/agents/alfred/doc-syncer.md +175 -0
- moai_adk/templates/.claude/agents/alfred/git-manager.md +200 -0
- moai_adk/templates/.claude/agents/alfred/project-manager.md +152 -0
- moai_adk/templates/.claude/agents/alfred/spec-builder.md +256 -0
- moai_adk/templates/.claude/agents/alfred/tag-agent.md +247 -0
- moai_adk/templates/.claude/agents/alfred/trust-checker.md +332 -0
- moai_adk/templates/.claude/commands/alfred/0-project.md +523 -0
- moai_adk/templates/.claude/commands/alfred/1-spec.md +531 -0
- moai_adk/templates/.claude/commands/alfred/2-build.md +413 -0
- moai_adk/templates/.claude/commands/alfred/3-sync.md +552 -0
- moai_adk/templates/.claude/hooks/alfred/README.md +238 -0
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +165 -0
- moai_adk/templates/.claude/hooks/alfred/core/__init__.py +79 -0
- moai_adk/templates/.claude/hooks/alfred/core/checkpoint.py +271 -0
- moai_adk/templates/.claude/hooks/alfred/core/context.py +110 -0
- moai_adk/templates/.claude/hooks/alfred/core/project.py +284 -0
- moai_adk/templates/.claude/hooks/alfred/core/tags.py +244 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +23 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/compact.py +51 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +25 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/session.py +80 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +71 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/user.py +41 -0
- moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +635 -0
- moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +691 -0
- moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +469 -0
- moai_adk/templates/.claude/settings.json +135 -0
- moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +68 -0
- moai_adk/templates/.github/workflows/moai-gitflow.yml +255 -0
- moai_adk/templates/.gitignore +41 -0
- moai_adk/templates/.moai/config.json +89 -0
- moai_adk/templates/.moai/memory/development-guide.md +367 -0
- moai_adk/templates/.moai/memory/spec-metadata.md +277 -0
- moai_adk/templates/.moai/project/product.md +121 -0
- moai_adk/templates/.moai/project/structure.md +150 -0
- moai_adk/templates/.moai/project/tech.md +221 -0
- moai_adk/templates/CLAUDE.md +733 -0
- moai_adk/templates/__init__.py +2 -0
- moai_adk/utils/__init__.py +8 -0
- moai_adk/utils/banner.py +42 -0
- moai_adk/utils/logger.py +152 -0
- moai_adk-0.3.0.dist-info/METADATA +20 -0
- moai_adk-0.3.0.dist-info/RECORD +87 -0
- moai_adk-0.3.0.dist-info/WHEEL +4 -0
- moai_adk-0.3.0.dist-info/entry_points.txt +2 -0
- 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)
|