moai-adk 0.3.13__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.

Files changed (91) hide show
  1. moai_adk/__init__.py +8 -1
  2. moai_adk/__main__.py +1 -1
  3. moai_adk/cli/commands/__init__.py +1 -1
  4. moai_adk/cli/commands/doctor.py +2 -2
  5. moai_adk/cli/commands/status.py +1 -1
  6. moai_adk/cli/commands/update.py +136 -90
  7. moai_adk/cli/prompts/init_prompts.py +1 -1
  8. moai_adk/core/__init__.py +1 -1
  9. moai_adk/core/git/branch.py +1 -1
  10. moai_adk/core/git/manager.py +1 -1
  11. moai_adk/core/quality/__init__.py +1 -1
  12. moai_adk/core/quality/trust_checker.py +1 -1
  13. moai_adk/core/quality/validators/__init__.py +1 -1
  14. moai_adk/core/quality/validators/base_validator.py +1 -1
  15. moai_adk/core/template/__init__.py +1 -1
  16. moai_adk/core/template/backup.py +2 -1
  17. moai_adk/core/template/config.py +24 -0
  18. moai_adk/core/template/languages.py +1 -1
  19. moai_adk/core/template/merger.py +58 -1
  20. moai_adk/core/template/processor.py +41 -12
  21. moai_adk/templates/.claude/agents/alfred/cc-manager.md +558 -3
  22. moai_adk/templates/.claude/commands/alfred/0-project.md +480 -12
  23. moai_adk/templates/.claude/commands/alfred/1-plan.md +563 -0
  24. moai_adk/templates/.claude/commands/alfred/1-spec.md +15 -516
  25. moai_adk/templates/.claude/commands/alfred/2-build.md +15 -417
  26. moai_adk/templates/.claude/commands/alfred/2-run.md +460 -0
  27. moai_adk/templates/.claude/hooks/alfred/handlers/session.py +10 -31
  28. moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +67 -0
  29. moai_adk/templates/.claude/skills/moai-claude-code/examples.md +513 -0
  30. moai_adk/templates/.claude/skills/moai-claude-code/reference.md +419 -0
  31. moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +332 -0
  32. moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +384 -0
  33. moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +349 -0
  34. moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +552 -0
  35. moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +499 -0
  36. moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +68 -0
  37. moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +64 -0
  38. moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +67 -0
  39. moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +69 -0
  40. moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +69 -0
  41. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +68 -0
  42. moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +67 -0
  43. moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +62 -0
  44. moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +74 -0
  45. moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +66 -0
  46. moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +66 -0
  47. moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +68 -0
  48. moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +59 -0
  49. moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +76 -0
  50. moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +61 -0
  51. moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +63 -0
  52. moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +64 -0
  53. moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +61 -0
  54. moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +54 -0
  55. moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +46 -0
  56. moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +68 -0
  57. moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +68 -0
  58. moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +69 -0
  59. moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +67 -0
  60. moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +66 -0
  61. moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +66 -0
  62. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +67 -0
  63. moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +67 -0
  64. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +66 -0
  65. moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +64 -0
  66. moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +66 -0
  67. moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +67 -0
  68. moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +65 -0
  69. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +65 -0
  70. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +64 -0
  71. moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +66 -0
  72. moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +66 -0
  73. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +68 -0
  74. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +68 -0
  75. moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +67 -0
  76. moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +68 -0
  77. moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +67 -0
  78. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +64 -0
  79. moai_adk/templates/.claude/skills/scripts/standardize_skills.py +166 -0
  80. moai_adk/templates/.claude/skills/scripts/verify_standardization.sh +43 -0
  81. moai_adk/templates/CLAUDE.md +153 -0
  82. moai_adk/templates/__init__.py +1 -1
  83. moai_adk/utils/__init__.py +1 -1
  84. moai_adk/utils/banner.py +7 -7
  85. moai_adk/utils/logger.py +1 -1
  86. {moai_adk-0.3.13.dist-info → moai_adk-0.4.0.dist-info}/METADATA +231 -1
  87. moai_adk-0.4.0.dist-info/RECORD +145 -0
  88. moai_adk-0.3.13.dist-info/RECORD +0 -90
  89. {moai_adk-0.3.13.dist-info → moai_adk-0.4.0.dist-info}/WHEEL +0 -0
  90. {moai_adk-0.3.13.dist-info → moai_adk-0.4.0.dist-info}/entry_points.txt +0 -0
  91. {moai_adk-0.3.13.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
- __version__ = "0.3.13"
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
@@ -1,4 +1,4 @@
1
- # @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_cli_commands.py
1
+ # @CODE:CLI-001 | SPEC: SPEC-CLI-001/spec.md | TEST: tests/unit/test_cli_commands.py
2
2
  """MoAI-ADK CLI Entry Point
3
3
 
4
4
  Implements the CLI entry point:
@@ -1,4 +1,4 @@
1
- # @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_cli_commands.py
1
+ # @CODE:CLI-001 | SPEC: SPEC-CLI-001/spec.md | TEST: tests/unit/test_cli_commands.py
2
2
  """CLI command module
3
3
 
4
4
  Core commands:
@@ -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:
@@ -1,4 +1,4 @@
1
- # @CODE:CLI-001 | SPEC: SPEC-CLI-001.md | TEST: tests/unit/test_cli_commands.py
1
+ # @CODE:CLI-001 | SPEC: SPEC-CLI-001/spec.md | TEST: tests/unit/test_cli_commands.py
2
2
  """MoAI-ADK status command
3
3
 
4
4
  Project status display:
@@ -1,13 +1,13 @@
1
- """Update command"""
1
+ """Update command - Upgrade moai-adk package to the latest version"""
2
2
  import json
3
- from pathlib import Path
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
- return data["info"]["version"]
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 update)"
125
+ help="Only check version (do not upgrade)"
50
126
  )
51
- def update(path: str, force: bool, check: bool) -> None:
52
- """Update template files to the latest version.
127
+ def update(check: bool) -> None:
128
+ """Upgrade moai-adk package to the latest version.
53
129
 
54
- Updates include:
55
- - .claude/ (fully replaced)
56
- - .moai/ (preserve specs and reports)
57
- - CLAUDE.md (merged)
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
- python -m moai_adk update # update with backup
62
- python -m moai_adk update --force # update without backup
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
- project_path = Path(path).resolve()
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
- if not force:
83
- console.print("[yellow]⚠ Cannot check for updates. Use --force to update anyway.[/yellow]")
84
- return
85
- else:
86
- console.print(f" Current version: {current_version}")
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
- # Exit early when --check is provided
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 version.parse(current_version) > version.parse(latest_version):
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 update is needed (version + optimized status) - skip with --force
102
- if not force and latest_version is not None:
103
- current_ver = version.parse(current_version)
104
- latest_ver = version.parse(latest_version)
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 3: update templates
139
- console.print("\n[cyan]📄 Updating templates...[/cyan]")
140
- processor = TemplateProcessor(project_path)
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
- console.print(" [green]✅ .claude/ update complete[/green]")
144
- console.print(" [green]✅ .moai/ update complete (specs/reports preserved)[/green]")
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
- console.print("\n[green]✓ Update complete![/green]")
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
@@ -1,4 +1,4 @@
1
- # @CODE:CLI-PROMPTS-001 | SPEC: SPEC-CLI-001.md
1
+ # @CODE:CLI-PROMPTS-001 | SPEC: SPEC-CLI-001/spec.md
2
2
  """Project initialization prompts
3
3
 
4
4
  Collect interactive project settings
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"""
@@ -1,4 +1,4 @@
1
- # @CODE:CORE-GIT-001 | SPEC: SPEC-CORE-GIT-001.md | TEST: tests/unit/test_git.py
1
+ # @CODE:CORE-GIT-001 | SPEC: SPEC-CORE-GIT-001/spec.md | TEST: tests/unit/test_git.py
2
2
  """
3
3
  Branch naming utilities.
4
4
 
@@ -1,4 +1,4 @@
1
- # @CODE:CORE-GIT-001 | SPEC: SPEC-CORE-GIT-001.md | TEST: tests/unit/test_git.py
1
+ # @CODE:CORE-GIT-001 | SPEC: SPEC-CORE-GIT-001/spec.md | TEST: tests/unit/test_git.py
2
2
  """
3
3
  Git repository management built on GitPython.
4
4
 
@@ -1,4 +1,4 @@
1
- # @CODE:TRUST-001 | SPEC: SPEC-TRUST-001.md | TEST: tests/unit/core/quality/
1
+ # @CODE:TRUST-001 | SPEC: SPEC-TRUST-001/spec.md | TEST: tests/unit/core/quality/
2
2
  """TRUST 원칙 자동 검증 시스템"""
3
3
 
4
4
  from moai_adk.core.quality.trust_checker import TrustChecker
@@ -1,4 +1,4 @@
1
- # @CODE:TRUST-001 | SPEC: SPEC-TRUST-001.md | TEST: tests/unit/core/quality/test_trust_checker.py
1
+ # @CODE:TRUST-001 | SPEC: SPEC-TRUST-001/spec.md | TEST: tests/unit/core/quality/test_trust_checker.py
2
2
  """
3
3
  TRUST 원칙 통합 검증 시스템
4
4
 
@@ -1,4 +1,4 @@
1
- # @CODE:TRUST-001 | SPEC: SPEC-TRUST-001.md
1
+ # @CODE:TRUST-001 | SPEC: SPEC-TRUST-001/spec.md
2
2
  """TRUST 검증기 패키지"""
3
3
 
4
4
  from moai_adk.core.quality.validators.base_validator import ValidationResult
@@ -1,4 +1,4 @@
1
- # @CODE:TRUST-001:VALIDATOR | SPEC: SPEC-TRUST-001.md
1
+ # @CODE:TRUST-001:VALIDATOR | SPEC: SPEC-TRUST-001/spec.md
2
2
  """Base validator class and validation result"""
3
3
 
4
4
  from dataclasses import dataclass
@@ -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 management module."""
3
3
 
4
4
  from moai_adk.core.template.backup import TemplateBackup
@@ -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.
@@ -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.
@@ -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
- # Backup this folder before deletion (safety measure)
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 (FORCE OVERWRITE all files)
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
- # FORCE OVERWRITE: Always copy files (no skip)
293
- warnings = self._copy_file_with_substitution(item, dst_item)
294
- all_warnings.extend(warnings)
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 FORCE OVERWRITE."""
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
- # FORCE OVERWRITE: Always copy template (backup already created in Phase 1)
365
- self._copy_file_with_substitution(src, dst)
366
- if not silent:
367
- console.print(" ✅ CLAUDE.md overwritten (backup available in .moai-backups/)")
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"