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
moai_adk/__init__.py
ADDED
moai_adk/__main__.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_cli_commands.py
|
|
2
|
+
"""MoAI-ADK CLI Entry Point
|
|
3
|
+
|
|
4
|
+
Implements the CLI entry point:
|
|
5
|
+
- Click-based CLI framework
|
|
6
|
+
- Rich console terminal output
|
|
7
|
+
- ASCII logo rendering
|
|
8
|
+
- --version and --help options
|
|
9
|
+
- Six core commands: init, doctor, status, backup, restore, update
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
import pyfiglet
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
from moai_adk import __version__
|
|
19
|
+
from moai_adk.cli.commands.backup import backup
|
|
20
|
+
from moai_adk.cli.commands.doctor import doctor
|
|
21
|
+
from moai_adk.cli.commands.init import init
|
|
22
|
+
from moai_adk.cli.commands.restore import restore
|
|
23
|
+
from moai_adk.cli.commands.status import status
|
|
24
|
+
from moai_adk.cli.commands.update import update
|
|
25
|
+
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def show_logo() -> None:
|
|
30
|
+
"""Render the MoAI-ADK ASCII logo with Pyfiglet"""
|
|
31
|
+
# Generate the "MoAI-ADK" banner using the ansi_shadow font
|
|
32
|
+
logo = pyfiglet.figlet_format("MoAI-ADK", font="ansi_shadow")
|
|
33
|
+
|
|
34
|
+
# Print with Rich styling
|
|
35
|
+
console.print(logo, style="cyan bold", highlight=False)
|
|
36
|
+
console.print(" Modu-AI's Agentic Development Kit w/ SuperAgent š© Alfred", style="yellow bold")
|
|
37
|
+
console.print()
|
|
38
|
+
console.print(" Version: ", style="green", end="")
|
|
39
|
+
console.print(__version__, style="cyan bold")
|
|
40
|
+
console.print()
|
|
41
|
+
console.print(" Tip: Run ", style="yellow", end="")
|
|
42
|
+
console.print("python -m moai_adk --help", style="cyan", end="")
|
|
43
|
+
console.print(" to see available commands", style="yellow")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@click.group(invoke_without_command=True)
|
|
47
|
+
@click.version_option(version=__version__, prog_name="MoAI-ADK")
|
|
48
|
+
@click.pass_context
|
|
49
|
+
def cli(ctx: click.Context) -> None:
|
|
50
|
+
"""MoAI Agentic Development Kit
|
|
51
|
+
|
|
52
|
+
SPEC-First TDD Framework with Alfred SuperAgent
|
|
53
|
+
"""
|
|
54
|
+
# Display the logo when no subcommand is invoked
|
|
55
|
+
if ctx.invoked_subcommand is None:
|
|
56
|
+
show_logo()
|
|
57
|
+
|
|
58
|
+
cli.add_command(init)
|
|
59
|
+
cli.add_command(doctor)
|
|
60
|
+
cli.add_command(status)
|
|
61
|
+
cli.add_command(restore)
|
|
62
|
+
cli.add_command(backup)
|
|
63
|
+
cli.add_command(update)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main() -> int:
|
|
67
|
+
"""CLI entry point"""
|
|
68
|
+
try:
|
|
69
|
+
cli(standalone_mode=False)
|
|
70
|
+
return 0
|
|
71
|
+
except click.Abort:
|
|
72
|
+
# User cancelled with Ctrl+C
|
|
73
|
+
return 130
|
|
74
|
+
except click.ClickException as e:
|
|
75
|
+
e.show()
|
|
76
|
+
return e.exit_code
|
|
77
|
+
except Exception as e:
|
|
78
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
79
|
+
return 1
|
|
80
|
+
finally:
|
|
81
|
+
# Flush the output buffer explicitly
|
|
82
|
+
console.file.flush()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
sys.exit(main())
|
moai_adk/cli/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_cli_commands.py
|
|
2
|
+
"""CLI command module
|
|
3
|
+
|
|
4
|
+
Four core commands:
|
|
5
|
+
- init: initialize the project
|
|
6
|
+
- doctor: run system diagnostics
|
|
7
|
+
- status: show project status
|
|
8
|
+
- restore: restore backups
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from moai_adk.cli.commands.doctor import doctor
|
|
12
|
+
from moai_adk.cli.commands.init import init
|
|
13
|
+
from moai_adk.cli.commands.restore import restore
|
|
14
|
+
from moai_adk.cli.commands.status import status
|
|
15
|
+
|
|
16
|
+
__all__ = ["init", "doctor", "status", "restore"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Backup command"""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
from moai_adk.core.template.processor import TemplateProcessor
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
@click.option(
|
|
14
|
+
"--path",
|
|
15
|
+
type=click.Path(exists=True),
|
|
16
|
+
default=".",
|
|
17
|
+
help="Project path (default: current directory)"
|
|
18
|
+
)
|
|
19
|
+
def backup(path: str) -> None:
|
|
20
|
+
"""Create a backup of the current project.
|
|
21
|
+
|
|
22
|
+
Includes:
|
|
23
|
+
- .claude/ (entire directory)
|
|
24
|
+
- .moai/ (excluding specs and reports)
|
|
25
|
+
- CLAUDE.md
|
|
26
|
+
|
|
27
|
+
Backup location: .moai-backup/YYYYMMDD-HHMMSS/
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
project_path = Path(path).resolve()
|
|
31
|
+
|
|
32
|
+
# Verify the project has been initialized
|
|
33
|
+
if not (project_path / ".moai").exists():
|
|
34
|
+
console.print("[yellow]ā Project not initialized[/yellow]")
|
|
35
|
+
raise click.Abort()
|
|
36
|
+
|
|
37
|
+
# Create the backup
|
|
38
|
+
console.print("[cyan]š¾ Creating backup...[/cyan]")
|
|
39
|
+
processor = TemplateProcessor(project_path)
|
|
40
|
+
backup_path = processor.create_backup()
|
|
41
|
+
|
|
42
|
+
# Success message
|
|
43
|
+
console.print(f"[green]ā Backup completed: {backup_path.relative_to(project_path)}[/green]")
|
|
44
|
+
|
|
45
|
+
# Show backup contents
|
|
46
|
+
backup_items = list(backup_path.iterdir())
|
|
47
|
+
for item in backup_items:
|
|
48
|
+
if item.is_dir():
|
|
49
|
+
file_count = len(list(item.rglob("*")))
|
|
50
|
+
console.print(f" āā {item.name}/ ({file_count} files)")
|
|
51
|
+
else:
|
|
52
|
+
console.print(f" āā {item.name}")
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
console.print(f"[red]ā Backup failed: {e}[/red]")
|
|
56
|
+
raise click.ClickException(str(e)) from e
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_doctor.py
|
|
2
|
+
"""MoAI-ADK doctor command
|
|
3
|
+
|
|
4
|
+
System diagnostics command:
|
|
5
|
+
- Check the Python version
|
|
6
|
+
- Verify Git installation
|
|
7
|
+
- Validate project structure
|
|
8
|
+
- Inspect language-specific tool chains
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
import questionary
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from moai_adk.core.project.checker import SystemChecker, check_environment
|
|
20
|
+
from moai_adk.core.project.detector import detect_project_language
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.option("--verbose", "-v", is_flag=True, help="Show detailed tool versions and language detection")
|
|
27
|
+
@click.option("--fix", is_flag=True, help="Suggest fixes for missing tools")
|
|
28
|
+
@click.option("--export", type=click.Path(), help="Export diagnostics to JSON file")
|
|
29
|
+
@click.option("--check", type=str, help="Check specific tool only")
|
|
30
|
+
def doctor(verbose: bool, fix: bool, export: str | None, check: str | None) -> None:
|
|
31
|
+
"""Check system requirements and project health
|
|
32
|
+
|
|
33
|
+
Verifies:
|
|
34
|
+
- Python version (>= 3.13)
|
|
35
|
+
- Git installation
|
|
36
|
+
- Project structure (.moai directory)
|
|
37
|
+
- Language-specific tool chains (20+ languages)
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
console.print("[cyan]Running system diagnostics...[/cyan]\n")
|
|
41
|
+
|
|
42
|
+
# Run basic environment checks
|
|
43
|
+
results = check_environment()
|
|
44
|
+
diagnostics_data: dict = {"basic_checks": results}
|
|
45
|
+
|
|
46
|
+
# In verbose mode, verify language-specific toolchains
|
|
47
|
+
if verbose or fix:
|
|
48
|
+
language = detect_project_language()
|
|
49
|
+
diagnostics_data["detected_language"] = language
|
|
50
|
+
|
|
51
|
+
if verbose:
|
|
52
|
+
console.print(f"[dim]Detected language: {language or 'Unknown'}[/dim]\n")
|
|
53
|
+
|
|
54
|
+
if language:
|
|
55
|
+
checker = SystemChecker()
|
|
56
|
+
language_tools = checker.check_language_tools(language)
|
|
57
|
+
diagnostics_data["language_tools"] = language_tools
|
|
58
|
+
|
|
59
|
+
if verbose:
|
|
60
|
+
_display_language_tools(language, language_tools, checker)
|
|
61
|
+
|
|
62
|
+
# Specific tool check
|
|
63
|
+
if check:
|
|
64
|
+
_check_specific_tool(check)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Build the base results table
|
|
68
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
69
|
+
table.add_column("Check", style="dim", width=40)
|
|
70
|
+
table.add_column("Status", justify="center")
|
|
71
|
+
|
|
72
|
+
for check_name, status in results.items():
|
|
73
|
+
icon = "ā" if status else "ā"
|
|
74
|
+
color = "green" if status else "red"
|
|
75
|
+
table.add_row(check_name, f"[{color}]{icon}[/{color}]")
|
|
76
|
+
|
|
77
|
+
console.print(table)
|
|
78
|
+
|
|
79
|
+
# In fix mode, suggest installation commands for missing tools
|
|
80
|
+
if fix and "language_tools" in diagnostics_data:
|
|
81
|
+
_suggest_fixes(diagnostics_data["language_tools"], diagnostics_data.get("detected_language"))
|
|
82
|
+
|
|
83
|
+
# When exporting, write diagnostics to JSON
|
|
84
|
+
if export:
|
|
85
|
+
_export_diagnostics(export, diagnostics_data)
|
|
86
|
+
|
|
87
|
+
# Summarize the overall result
|
|
88
|
+
all_passed = all(results.values())
|
|
89
|
+
if all_passed:
|
|
90
|
+
console.print("\n[green]ā All checks passed[/green]")
|
|
91
|
+
else:
|
|
92
|
+
console.print("\n[yellow]ā Some checks failed[/yellow]")
|
|
93
|
+
console.print("[dim]Run [cyan]python -m moai_adk doctor --verbose[/cyan] for detailed diagnostics[/dim]")
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
console.print(f"[red]ā Diagnostic failed: {e}[/red]")
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _display_language_tools(language: str, tools: dict[str, bool], checker: SystemChecker) -> None:
|
|
101
|
+
"""Display a table of language-specific tools (helper)"""
|
|
102
|
+
table = Table(show_header=True, header_style="bold cyan", title=f"{language.title()} Tools")
|
|
103
|
+
table.add_column("Tool", style="dim")
|
|
104
|
+
table.add_column("Status", justify="center")
|
|
105
|
+
table.add_column("Version", style="blue")
|
|
106
|
+
|
|
107
|
+
for tool, available in tools.items():
|
|
108
|
+
icon = "ā" if available else "ā"
|
|
109
|
+
color = "green" if available else "red"
|
|
110
|
+
version = checker.get_tool_version(tool) if available else "not installed"
|
|
111
|
+
|
|
112
|
+
table.add_row(tool, f"[{color}]{icon}[/{color}]", version or "")
|
|
113
|
+
|
|
114
|
+
console.print(table)
|
|
115
|
+
console.print()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _check_specific_tool(tool: str) -> None:
|
|
119
|
+
"""Check only a specific tool (helper)"""
|
|
120
|
+
checker = SystemChecker()
|
|
121
|
+
available = checker._is_tool_available(tool)
|
|
122
|
+
version = checker.get_tool_version(tool) if available else None
|
|
123
|
+
|
|
124
|
+
if available:
|
|
125
|
+
console.print(f"[green]ā {tool} is installed[/green]")
|
|
126
|
+
if version:
|
|
127
|
+
console.print(f" Version: {version}")
|
|
128
|
+
else:
|
|
129
|
+
console.print(f"[red]ā {tool} is not installed[/red]")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _suggest_fixes(tools: dict[str, bool], language: str | None) -> None:
|
|
133
|
+
"""Suggest installation commands for missing tools (helper)"""
|
|
134
|
+
missing_tools = [tool for tool, available in tools.items() if not available]
|
|
135
|
+
|
|
136
|
+
if not missing_tools:
|
|
137
|
+
console.print("\n[green]ā All tools are installed[/green]")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
console.print(f"\n[yellow]ā Missing {len(missing_tools)} tool(s)[/yellow]")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
proceed = questionary.confirm(
|
|
144
|
+
"Would you like to see install suggestions for missing tools?",
|
|
145
|
+
default=True,
|
|
146
|
+
).ask()
|
|
147
|
+
except Exception:
|
|
148
|
+
proceed = True
|
|
149
|
+
|
|
150
|
+
if not proceed:
|
|
151
|
+
console.print("[yellow]User skipped install suggestions[/yellow]")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
for tool in missing_tools:
|
|
155
|
+
install_cmd = _get_install_command(tool, language)
|
|
156
|
+
console.print(f" [red]ā[/red] {tool}")
|
|
157
|
+
if install_cmd:
|
|
158
|
+
console.print(f" Install: [cyan]{install_cmd}[/cyan]")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _get_install_command(tool: str, language: str | None) -> str:
|
|
162
|
+
"""Return the install command for a given tool (helper)"""
|
|
163
|
+
# Common tools
|
|
164
|
+
install_commands = {
|
|
165
|
+
"pytest": "pip install pytest",
|
|
166
|
+
"mypy": "pip install mypy",
|
|
167
|
+
"ruff": "pip install ruff",
|
|
168
|
+
"vitest": "npm install -D vitest",
|
|
169
|
+
"biome": "npm install -D @biomejs/biome",
|
|
170
|
+
"eslint": "npm install -D eslint",
|
|
171
|
+
"jest": "npm install -D jest",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return install_commands.get(tool, f"# Install {tool} for {language}")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _export_diagnostics(export_path: str, data: dict) -> None:
|
|
178
|
+
"""Export diagnostic results to a JSON file (helper)"""
|
|
179
|
+
try:
|
|
180
|
+
output = Path(export_path)
|
|
181
|
+
output.write_text(json.dumps(data, indent=2))
|
|
182
|
+
console.print(f"\n[green]ā Diagnostics exported to {export_path}[/green]")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
console.print(f"\n[red]ā Failed to export diagnostics: {e}[/red]")
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# @CODE:CLI-001 | @CODE:INIT-003:CLI
|
|
2
|
+
# SPEC: SPEC-CLI-001.md, SPEC-INIT-003.md
|
|
3
|
+
# TEST: tests/unit/test_cli_commands.py, tests/unit/test_init_reinit.py
|
|
4
|
+
"""MoAI-ADK init command
|
|
5
|
+
|
|
6
|
+
Project initialization command (interactive/non-interactive):
|
|
7
|
+
- Interactive Mode: Ask user for project settings
|
|
8
|
+
- Non-Interactive Mode: Use defaults or CLI options
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Sequence
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskID, TextColumn
|
|
18
|
+
from rich.prompt import Confirm
|
|
19
|
+
|
|
20
|
+
from moai_adk import __version__
|
|
21
|
+
from moai_adk.cli.prompts import prompt_project_setup
|
|
22
|
+
from moai_adk.core.project.initializer import ProjectInitializer
|
|
23
|
+
from moai_adk.utils.banner import print_banner, print_welcome_message
|
|
24
|
+
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_progress_callback(progress: Progress, task_ids: Sequence[TaskID]):
|
|
29
|
+
"""Create progress callback
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
progress: Rich Progress object
|
|
33
|
+
task_ids: List of task IDs (one per phase)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Progress callback function
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def callback(message: str, current: int, total: int) -> None:
|
|
40
|
+
"""Update progress
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
message: Progress message
|
|
44
|
+
current: Current phase (1-based)
|
|
45
|
+
total: Total phases
|
|
46
|
+
"""
|
|
47
|
+
# Complete current phase (1-based index ā 0-based)
|
|
48
|
+
if 1 <= current <= len(task_ids):
|
|
49
|
+
progress.update(task_ids[current - 1], completed=1, description=message)
|
|
50
|
+
|
|
51
|
+
return callback
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.command()
|
|
55
|
+
@click.argument("path", type=click.Path(), default=".")
|
|
56
|
+
@click.option(
|
|
57
|
+
"--non-interactive",
|
|
58
|
+
"-y",
|
|
59
|
+
is_flag=True,
|
|
60
|
+
help="Non-interactive mode (use defaults)",
|
|
61
|
+
)
|
|
62
|
+
@click.option(
|
|
63
|
+
"--mode",
|
|
64
|
+
type=click.Choice(["personal", "team"]),
|
|
65
|
+
default="personal",
|
|
66
|
+
help="Project mode",
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
"--locale",
|
|
70
|
+
type=click.Choice(["ko", "en", "ja", "zh"]),
|
|
71
|
+
default="ko",
|
|
72
|
+
help="Preferred language",
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--language",
|
|
76
|
+
type=str,
|
|
77
|
+
default=None,
|
|
78
|
+
help="Programming language (auto-detect if not specified)",
|
|
79
|
+
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--force",
|
|
82
|
+
is_flag=True,
|
|
83
|
+
help="Force reinitialize without confirmation",
|
|
84
|
+
)
|
|
85
|
+
def init(
|
|
86
|
+
path: str,
|
|
87
|
+
non_interactive: bool,
|
|
88
|
+
mode: str,
|
|
89
|
+
locale: str,
|
|
90
|
+
language: str | None,
|
|
91
|
+
force: bool,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Initialize a new MoAI-ADK project
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
path: Project directory path (default: current directory)
|
|
97
|
+
non_interactive: Skip prompts and use defaults
|
|
98
|
+
mode: Project mode (personal/team)
|
|
99
|
+
locale: Preferred language (ko/en/ja/zh)
|
|
100
|
+
language: Programming language
|
|
101
|
+
force: Force reinitialize without confirmation
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
# 1. Print banner
|
|
105
|
+
print_banner()
|
|
106
|
+
|
|
107
|
+
# 2. Check current directory mode
|
|
108
|
+
is_current_dir = path == "."
|
|
109
|
+
project_path = Path(path).resolve()
|
|
110
|
+
|
|
111
|
+
# 3. Interactive vs Non-Interactive
|
|
112
|
+
if non_interactive:
|
|
113
|
+
# Non-Interactive Mode
|
|
114
|
+
console.print(
|
|
115
|
+
f"\n[cyan]š Initializing project at {project_path}...[/cyan]\n"
|
|
116
|
+
)
|
|
117
|
+
project_name = project_path.name if is_current_dir else path
|
|
118
|
+
else:
|
|
119
|
+
# Interactive Mode
|
|
120
|
+
print_welcome_message()
|
|
121
|
+
|
|
122
|
+
# Interactive prompt
|
|
123
|
+
answers = prompt_project_setup(
|
|
124
|
+
project_name=None if is_current_dir else path,
|
|
125
|
+
is_current_dir=is_current_dir,
|
|
126
|
+
project_path=project_path,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Override with prompt answers
|
|
130
|
+
mode = answers["mode"]
|
|
131
|
+
locale = answers["locale"]
|
|
132
|
+
language = answers["language"]
|
|
133
|
+
project_name = answers["project_name"]
|
|
134
|
+
|
|
135
|
+
console.print("\n[cyan]š Starting installation...[/cyan]\n")
|
|
136
|
+
|
|
137
|
+
# 4. Check for reinitialization (SPEC-INIT-003 v0.3.0)
|
|
138
|
+
initializer = ProjectInitializer(project_path)
|
|
139
|
+
|
|
140
|
+
if initializer.is_initialized():
|
|
141
|
+
if non_interactive and not force:
|
|
142
|
+
# Non-interactive mode (without force): Reject reinitialization
|
|
143
|
+
console.print("\n[yellow]ā Project already initialized[/yellow]")
|
|
144
|
+
console.print(f"[dim] Location: {project_path}/.moai/[/dim]")
|
|
145
|
+
console.print("[dim] Use --force to reinitialize or interactive mode[/dim]\n")
|
|
146
|
+
raise click.Abort()
|
|
147
|
+
|
|
148
|
+
if force:
|
|
149
|
+
# Force mode: Reinitialize without confirmation
|
|
150
|
+
console.print("\n[green]š Force reinitializing project...[/green]\n")
|
|
151
|
+
else:
|
|
152
|
+
# Interactive mode: Reinitialization prompt
|
|
153
|
+
console.print("\nā ļø [yellow]Project already initialized[/yellow]")
|
|
154
|
+
console.print(f" Location: {project_path}/.moai/\n")
|
|
155
|
+
|
|
156
|
+
console.print("[cyan]This will:[/cyan]")
|
|
157
|
+
console.print(" ā Backup existing files to .moai-backups/{timestamp}/")
|
|
158
|
+
console.print(" ⢠CLAUDE.md")
|
|
159
|
+
console.print(" ⢠.claude/ (settings, commands, hooks)")
|
|
160
|
+
console.print(" ⢠.moai/ (all configurations and specs)")
|
|
161
|
+
console.print(" ā Update template files from moai-adk v0.3.0")
|
|
162
|
+
console.print(" ⢠.claude/ ā Latest Alfred commands")
|
|
163
|
+
console.print(" ⢠.moai/memory/ ā Latest development guides")
|
|
164
|
+
console.print(" ⢠CLAUDE.md ā Latest project documentation")
|
|
165
|
+
console.print(" ā Preserve your content")
|
|
166
|
+
console.print(" ⢠.moai/project/ (product/structure/tech.md)")
|
|
167
|
+
console.print(" ⢠.moai/specs/ (all SPEC documents)\n")
|
|
168
|
+
|
|
169
|
+
if not Confirm.ask("Would you like to update the project templates?", default=True):
|
|
170
|
+
console.print("\n[yellow]Reinit cancelled.[/yellow]\n")
|
|
171
|
+
raise click.Abort()
|
|
172
|
+
|
|
173
|
+
console.print("\n[green]š Starting reinit process...[/green]\n")
|
|
174
|
+
|
|
175
|
+
# 5. Initialize project (Progress Bar with 5 phases)
|
|
176
|
+
is_reinit = initializer.is_initialized()
|
|
177
|
+
|
|
178
|
+
# Reinit mode: set config.json optimized to false (v0.3.1+)
|
|
179
|
+
if is_reinit:
|
|
180
|
+
config_path = project_path / ".moai" / "config.json"
|
|
181
|
+
if config_path.exists():
|
|
182
|
+
try:
|
|
183
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
184
|
+
config_data = json.load(f)
|
|
185
|
+
|
|
186
|
+
# Update version and optimization flags
|
|
187
|
+
if "project" not in config_data:
|
|
188
|
+
config_data["project"] = {}
|
|
189
|
+
|
|
190
|
+
config_data["project"]["moai_adk_version"] = __version__
|
|
191
|
+
config_data["project"]["optimized"] = False
|
|
192
|
+
|
|
193
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
194
|
+
json.dump(config_data, f, indent=2, ensure_ascii=False)
|
|
195
|
+
except Exception:
|
|
196
|
+
# Ignore read/write failures; config.json is regenerated during initialization
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
with Progress(
|
|
200
|
+
SpinnerColumn(),
|
|
201
|
+
TextColumn("[progress.description]{task.description}"),
|
|
202
|
+
BarColumn(),
|
|
203
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
204
|
+
console=console,
|
|
205
|
+
) as progress:
|
|
206
|
+
# Create 5 phase tasks
|
|
207
|
+
phase_names = [
|
|
208
|
+
"Phase 1: Preparation and backup...",
|
|
209
|
+
"Phase 2: Creating directory structure...",
|
|
210
|
+
"Phase 3: Installing resources...",
|
|
211
|
+
"Phase 4: Generating configurations...",
|
|
212
|
+
"Phase 5: Validation and finalization...",
|
|
213
|
+
]
|
|
214
|
+
task_ids = [progress.add_task(name, total=1) for name in phase_names]
|
|
215
|
+
callback = create_progress_callback(progress, task_ids)
|
|
216
|
+
|
|
217
|
+
result = initializer.initialize(
|
|
218
|
+
mode=mode,
|
|
219
|
+
locale=locale,
|
|
220
|
+
language=language,
|
|
221
|
+
backup_enabled=True,
|
|
222
|
+
progress_callback=callback,
|
|
223
|
+
reinit=is_reinit, # SPEC-INIT-003 v0.3.0
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# 6. Output results
|
|
227
|
+
if result.success:
|
|
228
|
+
separator = "[dim]" + ("ā" * 60) + "[/dim]"
|
|
229
|
+
console.print(
|
|
230
|
+
"\n[green bold]ā
Initialization Completed Successfully![/green bold]"
|
|
231
|
+
)
|
|
232
|
+
console.print(separator)
|
|
233
|
+
console.print("\n[cyan]š Summary:[/cyan]")
|
|
234
|
+
console.print(f" [dim]š Location:[/dim] {result.project_path}")
|
|
235
|
+
console.print(f" [dim]š Language:[/dim] {result.language}")
|
|
236
|
+
console.print(f" [dim]š§ Mode:[/dim] {result.mode}")
|
|
237
|
+
console.print(
|
|
238
|
+
f" [dim]š Locale:[/dim] {result.locale}"
|
|
239
|
+
)
|
|
240
|
+
console.print(
|
|
241
|
+
f" [dim]š Files:[/dim] {len(result.created_files)} created"
|
|
242
|
+
)
|
|
243
|
+
console.print(f" [dim]ā±ļø Duration:[/dim] {result.duration}ms")
|
|
244
|
+
console.print(f"\n{separator}")
|
|
245
|
+
console.print("\n[cyan]š Next Steps:[/cyan]")
|
|
246
|
+
if not is_current_dir:
|
|
247
|
+
console.print(
|
|
248
|
+
f" [blue]1.[/blue] Run [bold]cd {project_name}[/bold] to enter the project"
|
|
249
|
+
)
|
|
250
|
+
console.print(
|
|
251
|
+
" [blue]2.[/blue] Check [bold].moai/config.json[/bold] for configuration"
|
|
252
|
+
)
|
|
253
|
+
console.print(
|
|
254
|
+
" [blue]3.[/blue] Read [bold]CLAUDE.md[/bold] for development guide\n"
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
console.print(
|
|
258
|
+
" [blue]1.[/blue] Check [bold].moai/config.json[/bold] for configuration"
|
|
259
|
+
)
|
|
260
|
+
console.print(
|
|
261
|
+
" [blue]2.[/blue] Read [bold]CLAUDE.md[/bold] for development guide\n"
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
console.print("\n[red bold]ā Initialization Failed![/red bold]")
|
|
265
|
+
if result.errors:
|
|
266
|
+
console.print("\n[red]Errors:[/red]")
|
|
267
|
+
for error in result.errors:
|
|
268
|
+
console.print(f" [red]ā¢[/red] {error}")
|
|
269
|
+
console.print()
|
|
270
|
+
raise click.ClickException("Installation failed")
|
|
271
|
+
|
|
272
|
+
except KeyboardInterrupt:
|
|
273
|
+
console.print("\n\n[yellow]ā Initialization cancelled by user[/yellow]\n")
|
|
274
|
+
raise click.Abort()
|
|
275
|
+
except FileExistsError as e:
|
|
276
|
+
console.print("\n[yellow]ā Project already initialized[/yellow]")
|
|
277
|
+
console.print("[dim] Use 'python -m moai_adk status' to check configuration[/dim]\n")
|
|
278
|
+
raise click.Abort() from e
|
|
279
|
+
except Exception as e:
|
|
280
|
+
console.print(f"\n[red]ā Initialization failed: {e}[/red]\n")
|
|
281
|
+
raise click.ClickException(str(e)) from e
|
|
282
|
+
finally:
|
|
283
|
+
# Explicitly flush output buffer
|
|
284
|
+
console.file.flush()
|