moai-adk 0.3.12__py3-none-any.whl → 0.4.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 -1
- moai_adk/__main__.py +1 -1
- moai_adk/cli/commands/__init__.py +1 -1
- moai_adk/cli/commands/doctor.py +2 -2
- moai_adk/cli/commands/status.py +1 -1
- moai_adk/cli/commands/update.py +136 -90
- moai_adk/cli/prompts/init_prompts.py +1 -1
- moai_adk/core/__init__.py +1 -1
- moai_adk/core/git/branch.py +1 -1
- moai_adk/core/git/manager.py +1 -1
- moai_adk/core/project/detector.py +14 -2
- moai_adk/core/quality/__init__.py +1 -1
- moai_adk/core/quality/trust_checker.py +1 -1
- moai_adk/core/quality/validators/__init__.py +1 -1
- moai_adk/core/quality/validators/base_validator.py +1 -1
- moai_adk/core/template/__init__.py +1 -1
- moai_adk/core/template/backup.py +2 -1
- moai_adk/core/template/config.py +24 -0
- moai_adk/core/template/languages.py +1 -1
- moai_adk/core/template/merger.py +58 -1
- moai_adk/core/template/processor.py +41 -12
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +558 -3
- moai_adk/templates/.claude/commands/alfred/0-project.md +480 -12
- moai_adk/templates/.claude/commands/alfred/1-plan.md +563 -0
- moai_adk/templates/.claude/commands/alfred/1-spec.md +15 -516
- moai_adk/templates/.claude/commands/alfred/2-build.md +15 -417
- moai_adk/templates/.claude/commands/alfred/2-run.md +460 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/session.py +12 -0
- moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-claude-code/examples.md +513 -0
- moai_adk/templates/.claude/skills/moai-claude-code/reference.md +419 -0
- moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +332 -0
- moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +384 -0
- moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +349 -0
- moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +552 -0
- moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +499 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +64 -0
- moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +69 -0
- moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +69 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +62 -0
- moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +74 -0
- moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +59 -0
- moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +76 -0
- moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +61 -0
- moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +63 -0
- moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +64 -0
- moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +61 -0
- moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +54 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +46 -0
- moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +69 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +64 -0
- moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +65 -0
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +65 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +64 -0
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +66 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +68 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +67 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +64 -0
- moai_adk/templates/.claude/skills/scripts/standardize_skills.py +166 -0
- moai_adk/templates/.claude/skills/scripts/verify_standardization.sh +43 -0
- moai_adk/templates/CLAUDE.md +153 -0
- moai_adk/templates/__init__.py +1 -1
- moai_adk/utils/__init__.py +1 -1
- moai_adk/utils/banner.py +1 -1
- moai_adk/utils/logger.py +1 -1
- {moai_adk-0.3.12.dist-info → moai_adk-0.4.0.dist-info}/METADATA +244 -3
- moai_adk-0.4.0.dist-info/RECORD +145 -0
- moai_adk-0.3.12.dist-info/RECORD +0 -90
- {moai_adk-0.3.12.dist-info → moai_adk-0.4.0.dist-info}/WHEEL +0 -0
- {moai_adk-0.3.12.dist-info → moai_adk-0.4.0.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.3.12.dist-info → moai_adk-0.4.0.dist-info}/licenses/LICENSE +0 -0
moai_adk/__init__.py
CHANGED
|
@@ -4,5 +4,12 @@
|
|
|
4
4
|
SPEC-First TDD Framework with Alfred SuperAgent
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("moai-adk")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
# Development mode fallback
|
|
13
|
+
__version__ = "0.4.0-dev"
|
|
14
|
+
|
|
8
15
|
__all__ = ["__version__"]
|
moai_adk/__main__.py
CHANGED
moai_adk/cli/commands/doctor.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_doctor.py
|
|
2
|
-
# @CODE:CLAUDE-COMMANDS-001:CLI | SPEC: SPEC-CLAUDE-COMMANDS-001.md | TEST: tests/unit/test_slash_commands.py
|
|
1
|
+
# @CODE:CLI-001 | SPEC: SPEC-CLI-001/spec.md | TEST: tests/unit/test_doctor.py
|
|
2
|
+
# @CODE:CLAUDE-COMMANDS-001:CLI | SPEC: SPEC-CLAUDE-COMMANDS-001/spec.md | TEST: tests/unit/test_slash_commands.py
|
|
3
3
|
"""MoAI-ADK doctor command
|
|
4
4
|
|
|
5
5
|
System diagnostics command:
|
moai_adk/cli/commands/status.py
CHANGED
moai_adk/cli/commands/update.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"""Update command"""
|
|
1
|
+
"""Update command - Upgrade moai-adk package to the latest version"""
|
|
2
2
|
import json
|
|
3
|
-
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
4
5
|
|
|
5
6
|
import click
|
|
6
7
|
from packaging import version
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
|
|
9
10
|
from moai_adk import __version__
|
|
10
|
-
from moai_adk.core.template.processor import TemplateProcessor
|
|
11
11
|
|
|
12
12
|
console = Console()
|
|
13
13
|
|
|
@@ -21,56 +21,123 @@ def get_latest_version() -> str | None:
|
|
|
21
21
|
try:
|
|
22
22
|
import urllib.error
|
|
23
23
|
import urllib.request
|
|
24
|
+
from typing import cast
|
|
24
25
|
|
|
25
26
|
url = "https://pypi.org/pypi/moai-adk/json"
|
|
26
27
|
with urllib.request.urlopen(url, timeout=5) as response: # nosec B310 - URL is hardcoded HTTPS to PyPI API, no user input
|
|
27
28
|
data = json.loads(response.read().decode("utf-8"))
|
|
28
|
-
|
|
29
|
+
version_str: str = cast(str, data["info"]["version"])
|
|
30
|
+
return version_str
|
|
29
31
|
except (urllib.error.URLError, json.JSONDecodeError, KeyError, TimeoutError):
|
|
30
32
|
# Return None if PyPI check fails
|
|
31
33
|
return None
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def detect_install_method() -> str:
|
|
37
|
+
"""Detect how moai-adk was installed.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
'uv-tool', 'uv-pip', or 'pip'
|
|
41
|
+
"""
|
|
42
|
+
# Check if installed via uv tool
|
|
43
|
+
try:
|
|
44
|
+
result = subprocess.run(
|
|
45
|
+
["uv", "tool", "list"],
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
timeout=5,
|
|
49
|
+
check=False
|
|
50
|
+
)
|
|
51
|
+
if result.returncode == 0 and "moai-adk" in result.stdout:
|
|
52
|
+
return "uv-tool"
|
|
53
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
# Check if uv is available (for uv pip)
|
|
57
|
+
try:
|
|
58
|
+
subprocess.run(
|
|
59
|
+
["uv", "--version"],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
timeout=5,
|
|
62
|
+
check=False
|
|
63
|
+
)
|
|
64
|
+
return "uv-pip"
|
|
65
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Default to pip
|
|
69
|
+
return "pip"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def upgrade_package(install_method: str, target_version: str) -> bool:
|
|
73
|
+
"""Upgrade moai-adk package.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
install_method: 'uv-tool', 'uv-pip', or 'pip'
|
|
77
|
+
target_version: Target version to upgrade to
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if successful, False otherwise
|
|
81
|
+
"""
|
|
82
|
+
commands = {
|
|
83
|
+
"uv-tool": ["uv", "tool", "upgrade", "moai-adk"],
|
|
84
|
+
"uv-pip": ["uv", "pip", "install", "--upgrade", "moai-adk"],
|
|
85
|
+
"pip": [sys.executable, "-m", "pip", "install", "--upgrade", "moai-adk"],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
cmd = commands.get(install_method)
|
|
89
|
+
if not cmd:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
console.print(f"\n[cyan]📦 Upgrading via {install_method}...[/cyan]")
|
|
94
|
+
console.print(f"[dim] Command: {' '.join(cmd)}[/dim]")
|
|
95
|
+
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
cmd,
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
timeout=120,
|
|
101
|
+
check=False
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if result.returncode == 0:
|
|
105
|
+
console.print(f"[green]✓ Upgraded to version {target_version}[/green]")
|
|
106
|
+
return True
|
|
107
|
+
else:
|
|
108
|
+
console.print(f"[red]✗ Upgrade failed[/red]")
|
|
109
|
+
if result.stderr:
|
|
110
|
+
console.print(f"[dim]{result.stderr.strip()}[/dim]")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
except subprocess.TimeoutExpired:
|
|
114
|
+
console.print("[red]✗ Upgrade timeout[/red]")
|
|
115
|
+
return False
|
|
116
|
+
except Exception as e:
|
|
117
|
+
console.print(f"[red]✗ Upgrade error: {e}[/red]")
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
34
121
|
@click.command()
|
|
35
|
-
@click.option(
|
|
36
|
-
"--path",
|
|
37
|
-
type=click.Path(exists=True),
|
|
38
|
-
default=".",
|
|
39
|
-
help="Project path (default: current directory)"
|
|
40
|
-
)
|
|
41
|
-
@click.option(
|
|
42
|
-
"--force",
|
|
43
|
-
is_flag=True,
|
|
44
|
-
help="Skip backup and force the update"
|
|
45
|
-
)
|
|
46
122
|
@click.option(
|
|
47
123
|
"--check",
|
|
48
124
|
is_flag=True,
|
|
49
|
-
help="Only check version (do not
|
|
125
|
+
help="Only check version (do not upgrade)"
|
|
50
126
|
)
|
|
51
|
-
def update(
|
|
52
|
-
"""
|
|
127
|
+
def update(check: bool) -> None:
|
|
128
|
+
"""Upgrade moai-adk package to the latest version.
|
|
53
129
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
- config.json (smart merge)
|
|
130
|
+
This command automatically detects the installation method
|
|
131
|
+
(uv tool, uv pip, or pip) and upgrades the package accordingly.
|
|
132
|
+
|
|
133
|
+
For template updates, use 'moai-adk init .' instead.
|
|
59
134
|
|
|
60
135
|
Examples:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
python -m moai_adk update --check # check version only
|
|
136
|
+
moai-adk update # Upgrade to latest version
|
|
137
|
+
moai-adk update --check # Check version only
|
|
64
138
|
"""
|
|
65
139
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# Verify the project is initialized
|
|
69
|
-
if not (project_path / ".moai").exists():
|
|
70
|
-
console.print("[yellow]⚠ Project not initialized[/yellow]")
|
|
71
|
-
raise click.Abort()
|
|
72
|
-
|
|
73
|
-
# Phase 1: check versions
|
|
140
|
+
# Phase 1: Check versions
|
|
74
141
|
console.print("[cyan]🔍 Checking versions...[/cyan]")
|
|
75
142
|
current_version = __version__
|
|
76
143
|
latest_version = get_latest_version()
|
|
@@ -79,74 +146,53 @@ def update(path: str, force: bool, check: bool) -> None:
|
|
|
79
146
|
if latest_version is None:
|
|
80
147
|
console.print(f" Current version: {current_version}")
|
|
81
148
|
console.print(" Latest version: [yellow]Unable to fetch from PyPI[/yellow]")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.print(f" Latest version: {latest_version}")
|
|
149
|
+
console.print("[yellow]⚠ Cannot check for updates[/yellow]")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
console.print(f" Current version: {current_version}")
|
|
153
|
+
console.print(f" Latest version: {latest_version}")
|
|
88
154
|
|
|
155
|
+
# Parse versions
|
|
156
|
+
current_ver = version.parse(current_version)
|
|
157
|
+
latest_ver = version.parse(latest_version)
|
|
158
|
+
|
|
159
|
+
# Check mode
|
|
89
160
|
if check:
|
|
90
|
-
|
|
91
|
-
if latest_version is None:
|
|
92
|
-
console.print("[yellow]⚠ Unable to check for updates[/yellow]")
|
|
93
|
-
elif version.parse(current_version) < version.parse(latest_version):
|
|
161
|
+
if current_ver < latest_ver:
|
|
94
162
|
console.print("[yellow]⚠ Update available[/yellow]")
|
|
95
|
-
elif
|
|
163
|
+
elif current_ver > latest_ver:
|
|
96
164
|
console.print("[green]✓ Development version (newer than PyPI)[/green]")
|
|
97
165
|
else:
|
|
98
166
|
console.print("[green]✓ Already up to date[/green]")
|
|
99
167
|
return
|
|
100
168
|
|
|
101
|
-
# Check if
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# Don't update if current version is newer or equal
|
|
107
|
-
if current_ver >= latest_ver:
|
|
108
|
-
# Check optimized status in config.json
|
|
109
|
-
config_path = project_path / ".moai" / "config.json"
|
|
110
|
-
if config_path.exists():
|
|
111
|
-
try:
|
|
112
|
-
config_data = json.loads(config_path.read_text())
|
|
113
|
-
is_optimized = config_data.get("project", {}).get("optimized", False)
|
|
114
|
-
|
|
115
|
-
if is_optimized:
|
|
116
|
-
# Already up to date and optimized - exit silently
|
|
117
|
-
return
|
|
118
|
-
else:
|
|
119
|
-
console.print("[yellow]⚠ Optimization needed[/yellow]")
|
|
120
|
-
console.print("[dim]Use /alfred:0-project update for template optimization[/dim]")
|
|
121
|
-
return
|
|
122
|
-
except (json.JSONDecodeError, KeyError):
|
|
123
|
-
# If config.json is invalid, proceed with update
|
|
124
|
-
pass
|
|
125
|
-
else:
|
|
126
|
-
console.print("[green]✓ Already up to date[/green]")
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
# Phase 2: create a backup unless --force
|
|
130
|
-
if not force:
|
|
131
|
-
console.print("\n[cyan]💾 Creating backup...[/cyan]")
|
|
132
|
-
processor = TemplateProcessor(project_path)
|
|
133
|
-
backup_path = processor.create_backup()
|
|
134
|
-
console.print(f"[green]✓ Backup completed: {backup_path.relative_to(project_path)}[/green]")
|
|
135
|
-
else:
|
|
136
|
-
console.print("\n[yellow]⚠ Skipping backup (--force)[/yellow]")
|
|
169
|
+
# Check if upgrade needed
|
|
170
|
+
if current_ver >= latest_ver:
|
|
171
|
+
console.print("[green]✓ Already up to date[/green]")
|
|
172
|
+
return
|
|
137
173
|
|
|
138
|
-
# Phase
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
processor.copy_templates(backup=False, silent=True) # Backup already handled
|
|
174
|
+
# Phase 2: Detect install method
|
|
175
|
+
install_method = detect_install_method()
|
|
176
|
+
console.print(f"\n[cyan]🔎 Detected installation method: {install_method}[/cyan]")
|
|
142
177
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.print(" [green]🔄 CLAUDE.md merge complete[/green]")
|
|
146
|
-
console.print(" [green]🔄 config.json merge complete[/green]")
|
|
178
|
+
# Phase 3: Upgrade package
|
|
179
|
+
success = upgrade_package(install_method, latest_version)
|
|
147
180
|
|
|
148
|
-
|
|
181
|
+
if success:
|
|
182
|
+
console.print("\n[green]✓ Update complete![/green]")
|
|
183
|
+
console.print("\n[dim]💡 For template updates, run: moai-adk init .[/dim]")
|
|
184
|
+
else:
|
|
185
|
+
console.print("\n[yellow]⚠ Upgrade failed. Please try manually:[/yellow]")
|
|
186
|
+
if install_method == "uv-tool":
|
|
187
|
+
console.print(" uv tool upgrade moai-adk")
|
|
188
|
+
elif install_method == "uv-pip":
|
|
189
|
+
console.print(" uv pip install --upgrade moai-adk")
|
|
190
|
+
else:
|
|
191
|
+
console.print(" pip install --upgrade moai-adk")
|
|
192
|
+
raise click.Abort()
|
|
149
193
|
|
|
194
|
+
except click.Abort:
|
|
195
|
+
raise
|
|
150
196
|
except Exception as e:
|
|
151
197
|
console.print(f"[red]✗ Update failed: {e}[/red]")
|
|
152
198
|
raise click.ClickException(str(e)) from e
|
moai_adk/core/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
# @CODE:PY314-001 | SPEC: SPEC-PY314-001.md | TEST: tests/unit/test_foundation.py
|
|
1
|
+
# @CODE:PY314-001 | SPEC: SPEC-PY314-001/spec.md | TEST: tests/unit/test_foundation.py
|
|
2
2
|
"""Core module: primary business logic"""
|
moai_adk/core/git/branch.py
CHANGED
moai_adk/core/git/manager.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# @CODE:CORE-PROJECT-001 | SPEC: SPEC-CORE-PROJECT-001.md | TEST: tests/unit/test_language_detector.py
|
|
2
|
+
# @CODE:LANG-DETECT-001 | SPEC: SPEC-LANG-DETECT-001.md | TEST: tests/unit/test_detector.py
|
|
2
3
|
"""Language detector module.
|
|
3
4
|
|
|
4
5
|
Automatically detects 20 programming languages.
|
|
@@ -8,9 +9,21 @@ from pathlib import Path
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class LanguageDetector:
|
|
11
|
-
"""Automatically detect up to 20 programming languages.
|
|
12
|
+
"""Automatically detect up to 20 programming languages.
|
|
13
|
+
|
|
14
|
+
Prioritizes framework-specific files (e.g., Laravel, Django) over
|
|
15
|
+
generic language files to improve accuracy in mixed-language projects.
|
|
16
|
+
"""
|
|
12
17
|
|
|
13
18
|
LANGUAGE_PATTERNS = {
|
|
19
|
+
# PHP moved to top for priority (Laravel detection)
|
|
20
|
+
"php": [
|
|
21
|
+
"*.php",
|
|
22
|
+
"composer.json",
|
|
23
|
+
"artisan", # Laravel: CLI tool (unique identifier)
|
|
24
|
+
"app/", # Laravel: application directory
|
|
25
|
+
"bootstrap/laravel.php" # Laravel: bootstrap file
|
|
26
|
+
],
|
|
14
27
|
"python": ["*.py", "pyproject.toml", "requirements.txt", "setup.py"],
|
|
15
28
|
"typescript": ["*.ts", "tsconfig.json"],
|
|
16
29
|
"javascript": ["*.js", "package.json"],
|
|
@@ -21,7 +34,6 @@ class LanguageDetector:
|
|
|
21
34
|
"swift": ["*.swift", "Package.swift"],
|
|
22
35
|
"kotlin": ["*.kt", "build.gradle.kts"],
|
|
23
36
|
"csharp": ["*.cs", "*.csproj"],
|
|
24
|
-
"php": ["*.php", "composer.json"],
|
|
25
37
|
"ruby": ["*.rb", "Gemfile"],
|
|
26
38
|
"elixir": ["*.ex", "mix.exs"],
|
|
27
39
|
"scala": ["*.scala", "build.sbt"],
|
moai_adk/core/template/backup.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003.md | Chain: TEMPLATE-001
|
|
1
|
+
# @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003/spec.md | Chain: TEMPLATE-001
|
|
2
2
|
"""Template backup manager (SPEC-INIT-003 v0.3.0).
|
|
3
3
|
|
|
4
4
|
Creates and manages backups to protect user data during template updates.
|
|
@@ -27,6 +27,7 @@ class TemplateBackup:
|
|
|
27
27
|
target_path: Project path (absolute).
|
|
28
28
|
"""
|
|
29
29
|
self.target_path = target_path.resolve()
|
|
30
|
+
self.backup_dir = self.target_path / ".moai-backups"
|
|
30
31
|
|
|
31
32
|
def has_existing_files(self) -> bool:
|
|
32
33
|
"""Check whether backup-worthy files already exist.
|
moai_adk/core/template/config.py
CHANGED
|
@@ -93,3 +93,27 @@ class ConfigManager:
|
|
|
93
93
|
result[key] = value
|
|
94
94
|
|
|
95
95
|
return result
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def set_optimized(config_path: Path, value: bool) -> None:
|
|
99
|
+
"""Set the optimized field in config.json.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
config_path: Path to config.json.
|
|
103
|
+
value: Value to set (True or False).
|
|
104
|
+
"""
|
|
105
|
+
if not config_path.exists():
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
with open(config_path, encoding="utf-8") as f:
|
|
110
|
+
config = json.load(f)
|
|
111
|
+
|
|
112
|
+
config.setdefault("project", {})["optimized"] = value
|
|
113
|
+
|
|
114
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
115
|
+
json.dump(config, f, ensure_ascii=False, indent=2)
|
|
116
|
+
f.write("\n") # Add trailing newline
|
|
117
|
+
except (json.JSONDecodeError, KeyError, OSError):
|
|
118
|
+
# Ignore errors if config.json is invalid or inaccessible
|
|
119
|
+
pass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @CODE:CORE-PROJECT-001 | SPEC: SPEC-CORE-PROJECT-001.md | TEST: tests/unit/test_language_mapping.py
|
|
1
|
+
# @CODE:CORE-PROJECT-001 | SPEC: SPEC-CORE-PROJECT-001/spec.md | TEST: tests/unit/test_language_mapping.py
|
|
2
2
|
"""Template mapping by language.
|
|
3
3
|
|
|
4
4
|
Defines template paths for 20 programming languages.
|
moai_adk/core/template/merger.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003.md | Chain: TEMPLATE-001
|
|
1
|
+
# @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003/spec.md | Chain: TEMPLATE-001
|
|
2
2
|
"""Template file merger (SPEC-INIT-003 v0.3.0).
|
|
3
3
|
|
|
4
4
|
Intelligently merges existing user files with new templates.
|
|
@@ -115,3 +115,60 @@ class TemplateMerger:
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
return new_config
|
|
118
|
+
|
|
119
|
+
def merge_settings_json(self, template_path: Path, existing_path: Path, backup_path: Path | None = None) -> None:
|
|
120
|
+
"""Smart merge for .claude/settings.json.
|
|
121
|
+
|
|
122
|
+
Rules:
|
|
123
|
+
- env: shallow merge (user variables preserved)
|
|
124
|
+
- permissions.allow: array merge (deduplicated)
|
|
125
|
+
- permissions.deny: template priority (security)
|
|
126
|
+
- hooks: template priority
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
template_path: Template settings.json.
|
|
130
|
+
existing_path: Existing settings.json.
|
|
131
|
+
backup_path: Backup settings.json (optional, for user settings extraction).
|
|
132
|
+
"""
|
|
133
|
+
# Load template
|
|
134
|
+
template_data = json.loads(template_path.read_text(encoding="utf-8"))
|
|
135
|
+
|
|
136
|
+
# Load backup or existing for user settings
|
|
137
|
+
user_data: dict[str, Any] = {}
|
|
138
|
+
if backup_path and backup_path.exists():
|
|
139
|
+
user_data = json.loads(backup_path.read_text(encoding="utf-8"))
|
|
140
|
+
elif existing_path.exists():
|
|
141
|
+
user_data = json.loads(existing_path.read_text(encoding="utf-8"))
|
|
142
|
+
|
|
143
|
+
# Merge env (shallow merge, user variables preserved)
|
|
144
|
+
merged_env = {**template_data.get("env", {}), **user_data.get("env", {})}
|
|
145
|
+
|
|
146
|
+
# Merge permissions.allow (deduplicated array merge)
|
|
147
|
+
template_allow = set(template_data.get("permissions", {}).get("allow", []))
|
|
148
|
+
user_allow = set(user_data.get("permissions", {}).get("allow", []))
|
|
149
|
+
merged_allow = sorted(template_allow | user_allow)
|
|
150
|
+
|
|
151
|
+
# permissions.deny: template priority (security)
|
|
152
|
+
merged_deny = template_data.get("permissions", {}).get("deny", [])
|
|
153
|
+
|
|
154
|
+
# permissions.ask: template priority + user additions
|
|
155
|
+
template_ask = set(template_data.get("permissions", {}).get("ask", []))
|
|
156
|
+
user_ask = set(user_data.get("permissions", {}).get("ask", []))
|
|
157
|
+
merged_ask = sorted(template_ask | user_ask)
|
|
158
|
+
|
|
159
|
+
# Build final merged settings
|
|
160
|
+
merged = {
|
|
161
|
+
"env": merged_env,
|
|
162
|
+
"hooks": template_data.get("hooks", {}), # Template priority
|
|
163
|
+
"permissions": {
|
|
164
|
+
"defaultMode": template_data.get("permissions", {}).get("defaultMode", "default"),
|
|
165
|
+
"allow": merged_allow,
|
|
166
|
+
"ask": merged_ask,
|
|
167
|
+
"deny": merged_deny
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
existing_path.write_text(
|
|
172
|
+
json.dumps(merged, indent=2, ensure_ascii=False) + "\n",
|
|
173
|
+
encoding="utf-8"
|
|
174
|
+
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003.md | Chain: TEMPLATE-001
|
|
1
|
+
# @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003/spec.md | Chain: TEMPLATE-001
|
|
2
2
|
"""Template copy and backup processor (SPEC-INIT-003 v0.3.0: preserve user content)."""
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
@@ -267,9 +267,8 @@ class TemplateProcessor:
|
|
|
267
267
|
dst_folder = dst / folder
|
|
268
268
|
|
|
269
269
|
if src_folder.exists():
|
|
270
|
-
#
|
|
270
|
+
# Remove existing folder (backup is already handled by create_backup() in update.py)
|
|
271
271
|
if dst_folder.exists():
|
|
272
|
-
self._backup_alfred_folder(dst_folder, folder)
|
|
273
272
|
shutil.rmtree(dst_folder)
|
|
274
273
|
|
|
275
274
|
# Create parent directory if needed
|
|
@@ -278,7 +277,7 @@ class TemplateProcessor:
|
|
|
278
277
|
if not silent:
|
|
279
278
|
console.print(f" ✅ .claude/{folder}/ overwritten")
|
|
280
279
|
|
|
281
|
-
# 2. Copy other files/folders individually (
|
|
280
|
+
# 2. Copy other files/folders individually (smart merge for settings.json)
|
|
282
281
|
all_warnings = []
|
|
283
282
|
for item in src.iterdir():
|
|
284
283
|
rel_path = item.relative_to(src)
|
|
@@ -289,9 +288,15 @@ class TemplateProcessor:
|
|
|
289
288
|
continue
|
|
290
289
|
|
|
291
290
|
if item.is_file():
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
# Smart merge for settings.json
|
|
292
|
+
if item.name == "settings.json":
|
|
293
|
+
self._merge_settings_json(item, dst_item)
|
|
294
|
+
if not silent:
|
|
295
|
+
console.print(" 🔄 settings.json merged (env variables preserved)")
|
|
296
|
+
else:
|
|
297
|
+
# FORCE OVERWRITE: Always copy other files (no skip)
|
|
298
|
+
warnings = self._copy_file_with_substitution(item, dst_item)
|
|
299
|
+
all_warnings.extend(warnings)
|
|
295
300
|
elif item.is_dir():
|
|
296
301
|
# FORCE OVERWRITE: Always copy directories (no skip)
|
|
297
302
|
self._copy_dir_with_substitution(item, dst_item)
|
|
@@ -352,7 +357,7 @@ class TemplateProcessor:
|
|
|
352
357
|
console.print(" ✅ .moai/ copy complete (variables substituted)")
|
|
353
358
|
|
|
354
359
|
def _copy_claude_md(self, silent: bool = False) -> None:
|
|
355
|
-
"""Copy CLAUDE.md with
|
|
360
|
+
"""Copy CLAUDE.md with smart merge (preserves "## 프로젝트 정보" section)."""
|
|
356
361
|
src = self.template_root / "CLAUDE.md"
|
|
357
362
|
dst = self.target_path / "CLAUDE.md"
|
|
358
363
|
|
|
@@ -361,10 +366,16 @@ class TemplateProcessor:
|
|
|
361
366
|
console.print("⚠️ CLAUDE.md template not found")
|
|
362
367
|
return
|
|
363
368
|
|
|
364
|
-
#
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
369
|
+
# Smart merge: preserve existing "## 프로젝트 정보" section
|
|
370
|
+
if dst.exists():
|
|
371
|
+
self._merge_claude_md(src, dst)
|
|
372
|
+
if not silent:
|
|
373
|
+
console.print(" 🔄 CLAUDE.md merged (프로젝트 정보 preserved)")
|
|
374
|
+
else:
|
|
375
|
+
# First time: just copy
|
|
376
|
+
self._copy_file_with_substitution(src, dst)
|
|
377
|
+
if not silent:
|
|
378
|
+
console.print(" ✅ CLAUDE.md created")
|
|
368
379
|
|
|
369
380
|
def _merge_claude_md(self, src: Path, dst: Path) -> None:
|
|
370
381
|
"""Delegate the smart merge for CLAUDE.md.
|
|
@@ -375,6 +386,24 @@ class TemplateProcessor:
|
|
|
375
386
|
"""
|
|
376
387
|
self.merger.merge_claude_md(src, dst)
|
|
377
388
|
|
|
389
|
+
def _merge_settings_json(self, src: Path, dst: Path) -> None:
|
|
390
|
+
"""Delegate the smart merge for settings.json.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
src: Template settings.json.
|
|
394
|
+
dst: Project settings.json.
|
|
395
|
+
"""
|
|
396
|
+
# Find the latest backup for user settings extraction
|
|
397
|
+
backup_path = None
|
|
398
|
+
if self.backup.backup_dir.exists():
|
|
399
|
+
backups = sorted(self.backup.backup_dir.iterdir(), reverse=True)
|
|
400
|
+
if backups:
|
|
401
|
+
backup_settings = backups[0] / ".claude" / "settings.json"
|
|
402
|
+
if backup_settings.exists():
|
|
403
|
+
backup_path = backup_settings
|
|
404
|
+
|
|
405
|
+
self.merger.merge_settings_json(src, dst, backup_path)
|
|
406
|
+
|
|
378
407
|
def _copy_gitignore(self, silent: bool = False) -> None:
|
|
379
408
|
""".gitignore copy (optional)."""
|
|
380
409
|
src = self.template_root / ".gitignore"
|