moai-adk 0.4.1__py3-none-any.whl → 0.4.5__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 +7 -1
- moai_adk/cli/commands/update.py +1 -1
- moai_adk/cli/prompts/init_prompts.py +2 -2
- moai_adk/core/project/backup_utils.py +1 -11
- moai_adk/core/project/checker.py +2 -2
- moai_adk/core/project/phase_executor.py +8 -13
- moai_adk/core/quality/__init__.py +1 -1
- moai_adk/core/quality/trust_checker.py +63 -63
- moai_adk/core/quality/validators/__init__.py +1 -1
- moai_adk/core/quality/validators/base_validator.py +1 -1
- moai_adk/core/template/backup.py +10 -5
- moai_adk/core/template/merger.py +2 -5
- moai_adk/core/template/processor.py +3 -3
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +5 -2
- moai_adk/templates/.claude/agents/alfred/project-manager.md +17 -9
- moai_adk/templates/.claude/commands/alfred/0-project.md +96 -13
- moai_adk/templates/.claude/commands/alfred/1-plan.md +7 -6
- moai_adk/templates/.claude/commands/alfred/2-run.md +10 -8
- moai_adk/templates/.claude/commands/alfred/3-sync.md +33 -12
- moai_adk/templates/.moai/memory/spec-metadata.md +80 -0
- moai_adk/templates/CLAUDE.md +227 -0
- {moai_adk-0.4.1.dist-info → moai_adk-0.4.5.dist-info}/METADATA +74 -8
- {moai_adk-0.4.1.dist-info → moai_adk-0.4.5.dist-info}/RECORD +26 -26
- {moai_adk-0.4.1.dist-info → moai_adk-0.4.5.dist-info}/WHEEL +0 -0
- {moai_adk-0.4.1.dist-info → moai_adk-0.4.5.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.4.1.dist-info → moai_adk-0.4.5.dist-info}/licenses/LICENSE +0 -0
moai_adk/__init__.py
CHANGED
|
@@ -4,5 +4,11 @@
|
|
|
4
4
|
SPEC-First TDD Framework with Alfred SuperAgent
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("moai-adk")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
__version__ = "0.4.1-dev"
|
|
13
|
+
|
|
8
14
|
__all__ = ["__version__"]
|
moai_adk/cli/commands/update.py
CHANGED
|
@@ -315,7 +315,7 @@ def update(path: str, force: bool, check: bool) -> None:
|
|
|
315
315
|
console.print("\n[cyan]💾 Creating backup...[/cyan]")
|
|
316
316
|
processor = TemplateProcessor(project_path)
|
|
317
317
|
backup_path = processor.create_backup()
|
|
318
|
-
console.print(f"[green]✓ Backup completed: {backup_path.relative_to(project_path)}[/green]")
|
|
318
|
+
console.print(f"[green]✓ Backup completed: {backup_path.relative_to(project_path)}/[/green]")
|
|
319
319
|
else:
|
|
320
320
|
console.print("\n[yellow]⚠ Skipping backup (--force)[/yellow]")
|
|
321
321
|
|
|
@@ -93,11 +93,11 @@ def prompt_project_setup(
|
|
|
93
93
|
answers["locale"] = initial_locale or "en"
|
|
94
94
|
if initial_locale:
|
|
95
95
|
console.print(
|
|
96
|
-
f"[cyan]🌐 Preferred Language:[/cyan] {answers['locale']} (CLI
|
|
96
|
+
f"[cyan]🌐 Preferred Language:[/cyan] {answers['locale']} (specified via CLI option)"
|
|
97
97
|
)
|
|
98
98
|
else:
|
|
99
99
|
console.print(
|
|
100
|
-
"[cyan]🌐 Preferred Language:[/cyan] en (
|
|
100
|
+
"[cyan]🌐 Preferred Language:[/cyan] en (default, changeable in /alfred:0-project)"
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
# 3. Programming language (auto-detect or manual)
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
Selective backup strategy:
|
|
5
5
|
- Back up only the required files (OR condition)
|
|
6
|
-
- Backup path: .moai-backups/
|
|
6
|
+
- Backup path: .moai-backups/backup/ (v0.4.2)
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from datetime import datetime
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
|
|
12
11
|
# Backup targets (OR condition - back up when any exist)
|
|
@@ -59,15 +58,6 @@ def get_backup_targets(project_path: Path) -> list[str]:
|
|
|
59
58
|
return targets
|
|
60
59
|
|
|
61
60
|
|
|
62
|
-
def generate_backup_dir_name() -> str:
|
|
63
|
-
"""Generate a timestamp-based backup directory name (v0.3.0).
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Timestamp formatted as YYYYMMDD-HHMMSS.
|
|
67
|
-
Note: callers use .moai-backups/{timestamp}/ format.
|
|
68
|
-
"""
|
|
69
|
-
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
70
|
-
return timestamp
|
|
71
61
|
|
|
72
62
|
|
|
73
63
|
def is_protected_path(rel_path: Path) -> bool:
|
moai_adk/core/project/checker.py
CHANGED
|
@@ -298,5 +298,5 @@ def get_permission_fix_message(path: str) -> str:
|
|
|
298
298
|
Platform-specific fix instructions.
|
|
299
299
|
"""
|
|
300
300
|
if platform.system() == "Windows":
|
|
301
|
-
return f"
|
|
302
|
-
return f"chmod 755 {path}
|
|
301
|
+
return f"Run with administrator privileges or verify permissions in the properties of the '{path}' directory"
|
|
302
|
+
return f"Run 'chmod 755 {path}' and try again"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @CODE:INIT-003:PHASE | SPEC: .moai/specs/SPEC-INIT-003/spec.md | TEST: tests/unit/test_init_reinit.py
|
|
2
|
-
"""Phase-based installation executor (SPEC-INIT-003 v0.
|
|
2
|
+
"""Phase-based installation executor (SPEC-INIT-003 v0.4.2)
|
|
3
3
|
|
|
4
4
|
Runs the project initialization across five phases:
|
|
5
|
-
- Phase 1: Preparation (create backup at .moai-backups/
|
|
5
|
+
- Phase 1: Preparation (create single backup at .moai-backups/backup/)
|
|
6
6
|
- Phase 2: Directory (build directory structure)
|
|
7
7
|
- Phase 3: Resource (copy templates while preserving user content)
|
|
8
8
|
- Phase 4: Configuration (generate configuration files)
|
|
@@ -20,7 +20,6 @@ from rich.console import Console
|
|
|
20
20
|
|
|
21
21
|
from moai_adk import __version__
|
|
22
22
|
from moai_adk.core.project.backup_utils import (
|
|
23
|
-
generate_backup_dir_name,
|
|
24
23
|
get_backup_targets,
|
|
25
24
|
has_any_moai_files,
|
|
26
25
|
is_protected_path,
|
|
@@ -229,25 +228,21 @@ class PhaseExecutor:
|
|
|
229
228
|
self._initialize_git(project_path)
|
|
230
229
|
|
|
231
230
|
def _create_backup(self, project_path: Path) -> None:
|
|
232
|
-
"""Create a
|
|
231
|
+
"""Create a single backup (v0.4.2).
|
|
233
232
|
|
|
234
|
-
|
|
233
|
+
Maintains only one backup at .moai-backups/backup/.
|
|
235
234
|
|
|
236
235
|
Args:
|
|
237
236
|
project_path: Project path.
|
|
238
237
|
"""
|
|
239
238
|
# Define backup directory
|
|
240
239
|
backups_dir = project_path / ".moai-backups"
|
|
240
|
+
backup_path = backups_dir / "backup"
|
|
241
241
|
|
|
242
|
-
# Remove
|
|
243
|
-
if
|
|
244
|
-
|
|
245
|
-
if item.is_dir():
|
|
246
|
-
shutil.rmtree(item)
|
|
242
|
+
# Remove existing backup if present
|
|
243
|
+
if backup_path.exists():
|
|
244
|
+
shutil.rmtree(backup_path)
|
|
247
245
|
|
|
248
|
-
# Create new backup directory (.moai-backups/{timestamp}/)
|
|
249
|
-
timestamp = generate_backup_dir_name()
|
|
250
|
-
backup_path = backups_dir / timestamp
|
|
251
246
|
backup_path.mkdir(parents=True, exist_ok=True)
|
|
252
247
|
|
|
253
248
|
# Collect backup targets
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @CODE:TRUST-001 | SPEC: SPEC-TRUST-001/spec.md | TEST: tests/unit/core/quality/test_trust_checker.py
|
|
2
2
|
"""
|
|
3
|
-
TRUST
|
|
4
|
-
|
|
5
|
-
TRUST 5
|
|
6
|
-
- T: Test First (
|
|
7
|
-
- R: Readable (
|
|
8
|
-
- U: Unified (
|
|
9
|
-
- S: Secured (
|
|
10
|
-
- T: Trackable (TAG
|
|
3
|
+
Integrated TRUST principle validation system
|
|
4
|
+
|
|
5
|
+
TRUST 5 principles:
|
|
6
|
+
- T: Test First (test coverage ≥85%)
|
|
7
|
+
- R: Readable (file ≤300 LOC, function ≤50 LOC, parameters ≤5)
|
|
8
|
+
- U: Unified (type safety)
|
|
9
|
+
- S: Secured (vulnerability scanning)
|
|
10
|
+
- T: Trackable (TAG chain integrity)
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import ast
|
|
@@ -18,7 +18,7 @@ from typing import Any
|
|
|
18
18
|
from moai_adk.core.quality.validators.base_validator import ValidationResult
|
|
19
19
|
|
|
20
20
|
# ========================================
|
|
21
|
-
#
|
|
21
|
+
# Constants (descriptive names)
|
|
22
22
|
# ========================================
|
|
23
23
|
MIN_TEST_COVERAGE_PERCENT = 85
|
|
24
24
|
MAX_FILE_LINES_OF_CODE = 300
|
|
@@ -26,20 +26,20 @@ MAX_FUNCTION_LINES_OF_CODE = 50
|
|
|
26
26
|
MAX_FUNCTION_PARAMETERS = 5
|
|
27
27
|
MAX_CYCLOMATIC_COMPLEXITY = 10
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
# File encoding
|
|
30
30
|
DEFAULT_FILE_ENCODING = "utf-8"
|
|
31
31
|
|
|
32
|
-
# TAG
|
|
32
|
+
# TAG prefixes
|
|
33
33
|
TAG_PREFIX_SPEC = "@SPEC:"
|
|
34
34
|
TAG_PREFIX_CODE = "@CODE:"
|
|
35
35
|
TAG_PREFIX_TEST = "@TEST:"
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class TrustChecker:
|
|
39
|
-
"""TRUST
|
|
39
|
+
"""Integrated TRUST principle validator"""
|
|
40
40
|
|
|
41
41
|
def __init__(self):
|
|
42
|
-
"""TrustChecker
|
|
42
|
+
"""Initialize TrustChecker"""
|
|
43
43
|
self.results: dict[str, ValidationResult] = {}
|
|
44
44
|
|
|
45
45
|
# ========================================
|
|
@@ -48,14 +48,14 @@ class TrustChecker:
|
|
|
48
48
|
|
|
49
49
|
def validate_coverage(self, project_path: Path, coverage_data: dict[str, Any]) -> ValidationResult:
|
|
50
50
|
"""
|
|
51
|
-
|
|
51
|
+
Validate test coverage (≥85%)
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
|
-
project_path:
|
|
55
|
-
coverage_data:
|
|
54
|
+
project_path: Project path
|
|
55
|
+
coverage_data: Coverage data (total_coverage, low_coverage_files)
|
|
56
56
|
|
|
57
57
|
Returns:
|
|
58
|
-
ValidationResult:
|
|
58
|
+
ValidationResult: Validation result
|
|
59
59
|
"""
|
|
60
60
|
total_coverage = coverage_data.get("total_coverage", 0)
|
|
61
61
|
|
|
@@ -64,7 +64,7 @@ class TrustChecker:
|
|
|
64
64
|
passed=True, message=f"Test coverage: {total_coverage}% (Target: {MIN_TEST_COVERAGE_PERCENT}%)"
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
-
#
|
|
67
|
+
# Generate detailed information on failure
|
|
68
68
|
low_files = coverage_data.get("low_coverage_files", [])
|
|
69
69
|
details = f"Current coverage: {total_coverage}% (Target: {MIN_TEST_COVERAGE_PERCENT}%)\n"
|
|
70
70
|
details += "Low coverage files:\n"
|
|
@@ -84,15 +84,15 @@ class TrustChecker:
|
|
|
84
84
|
|
|
85
85
|
def validate_file_size(self, src_path: Path) -> ValidationResult:
|
|
86
86
|
"""
|
|
87
|
-
|
|
87
|
+
Validate file size (≤300 LOC)
|
|
88
88
|
|
|
89
89
|
Args:
|
|
90
|
-
src_path:
|
|
90
|
+
src_path: Source code directory path
|
|
91
91
|
|
|
92
92
|
Returns:
|
|
93
|
-
ValidationResult:
|
|
93
|
+
ValidationResult: Validation result
|
|
94
94
|
"""
|
|
95
|
-
#
|
|
95
|
+
# Input validation (security)
|
|
96
96
|
if not src_path.exists():
|
|
97
97
|
return ValidationResult(passed=False, message=f"Source path does not exist: {src_path}", details="")
|
|
98
98
|
|
|
@@ -102,7 +102,7 @@ class TrustChecker:
|
|
|
102
102
|
violations = []
|
|
103
103
|
|
|
104
104
|
for py_file in src_path.rglob("*.py"):
|
|
105
|
-
#
|
|
105
|
+
# Apply guard clause (improves readability)
|
|
106
106
|
if py_file.name.startswith("test_"):
|
|
107
107
|
continue
|
|
108
108
|
|
|
@@ -113,7 +113,7 @@ class TrustChecker:
|
|
|
113
113
|
if loc > MAX_FILE_LINES_OF_CODE:
|
|
114
114
|
violations.append(f"{py_file.name}: {loc} LOC (Limit: {MAX_FILE_LINES_OF_CODE})")
|
|
115
115
|
except (UnicodeDecodeError, PermissionError):
|
|
116
|
-
#
|
|
116
|
+
# Security: handle file access errors
|
|
117
117
|
continue
|
|
118
118
|
|
|
119
119
|
if not violations:
|
|
@@ -126,13 +126,13 @@ class TrustChecker:
|
|
|
126
126
|
|
|
127
127
|
def validate_function_size(self, src_path: Path) -> ValidationResult:
|
|
128
128
|
"""
|
|
129
|
-
|
|
129
|
+
Validate function size (≤50 LOC)
|
|
130
130
|
|
|
131
131
|
Args:
|
|
132
|
-
src_path:
|
|
132
|
+
src_path: Source code directory path
|
|
133
133
|
|
|
134
134
|
Returns:
|
|
135
|
-
ValidationResult:
|
|
135
|
+
ValidationResult: Validation result
|
|
136
136
|
"""
|
|
137
137
|
violations = []
|
|
138
138
|
|
|
@@ -147,11 +147,11 @@ class TrustChecker:
|
|
|
147
147
|
|
|
148
148
|
for node in ast.walk(tree):
|
|
149
149
|
if isinstance(node, ast.FunctionDef):
|
|
150
|
-
# AST
|
|
150
|
+
# AST line numbers are 1-based
|
|
151
151
|
start_line = node.lineno
|
|
152
152
|
end_line = node.end_lineno if node.end_lineno else start_line # type: ignore
|
|
153
153
|
|
|
154
|
-
#
|
|
154
|
+
# Compute actual function lines of code (decorators excluded)
|
|
155
155
|
func_lines = lines[start_line - 1:end_line]
|
|
156
156
|
func_loc = len(func_lines)
|
|
157
157
|
|
|
@@ -172,13 +172,13 @@ class TrustChecker:
|
|
|
172
172
|
|
|
173
173
|
def validate_param_count(self, src_path: Path) -> ValidationResult:
|
|
174
174
|
"""
|
|
175
|
-
|
|
175
|
+
Validate parameter count (≤5)
|
|
176
176
|
|
|
177
177
|
Args:
|
|
178
|
-
src_path:
|
|
178
|
+
src_path: Source code directory path
|
|
179
179
|
|
|
180
180
|
Returns:
|
|
181
|
-
ValidationResult:
|
|
181
|
+
ValidationResult: Validation result
|
|
182
182
|
"""
|
|
183
183
|
violations = []
|
|
184
184
|
|
|
@@ -211,13 +211,13 @@ class TrustChecker:
|
|
|
211
211
|
|
|
212
212
|
def validate_complexity(self, src_path: Path) -> ValidationResult:
|
|
213
213
|
"""
|
|
214
|
-
|
|
214
|
+
Validate cyclomatic complexity (≤10)
|
|
215
215
|
|
|
216
216
|
Args:
|
|
217
|
-
src_path:
|
|
217
|
+
src_path: Source code directory path
|
|
218
218
|
|
|
219
219
|
Returns:
|
|
220
|
-
ValidationResult:
|
|
220
|
+
ValidationResult: Validation result
|
|
221
221
|
"""
|
|
222
222
|
violations = []
|
|
223
223
|
|
|
@@ -250,23 +250,23 @@ class TrustChecker:
|
|
|
250
250
|
|
|
251
251
|
def _calculate_complexity(self, node: ast.FunctionDef) -> int:
|
|
252
252
|
"""
|
|
253
|
-
|
|
253
|
+
Calculate cyclomatic complexity (McCabe complexity)
|
|
254
254
|
|
|
255
255
|
Args:
|
|
256
|
-
node:
|
|
256
|
+
node: Function AST node
|
|
257
257
|
|
|
258
258
|
Returns:
|
|
259
|
-
int:
|
|
259
|
+
int: Cyclomatic complexity
|
|
260
260
|
"""
|
|
261
261
|
complexity = 1
|
|
262
262
|
for child in ast.walk(node):
|
|
263
|
-
#
|
|
263
|
+
# Add 1 for each branching statement
|
|
264
264
|
if isinstance(child, (ast.If, ast.While, ast.For, ast.ExceptHandler, ast.With)):
|
|
265
265
|
complexity += 1
|
|
266
|
-
# and/or
|
|
266
|
+
# Add 1 for each and/or operator
|
|
267
267
|
elif isinstance(child, ast.BoolOp):
|
|
268
268
|
complexity += len(child.values) - 1
|
|
269
|
-
# elif
|
|
269
|
+
# elif is already counted as ast.If, no extra handling needed
|
|
270
270
|
return complexity
|
|
271
271
|
|
|
272
272
|
# ========================================
|
|
@@ -275,22 +275,22 @@ class TrustChecker:
|
|
|
275
275
|
|
|
276
276
|
def validate_tag_chain(self, project_path: Path) -> ValidationResult:
|
|
277
277
|
"""
|
|
278
|
-
TAG
|
|
278
|
+
Validate TAG chain completeness
|
|
279
279
|
|
|
280
280
|
Args:
|
|
281
|
-
project_path:
|
|
281
|
+
project_path: Project path
|
|
282
282
|
|
|
283
283
|
Returns:
|
|
284
|
-
ValidationResult:
|
|
284
|
+
ValidationResult: Validation result
|
|
285
285
|
"""
|
|
286
286
|
specs_dir = project_path / ".moai" / "specs"
|
|
287
287
|
src_dir = project_path / "src"
|
|
288
288
|
|
|
289
|
-
#
|
|
289
|
+
# Scan for TAGs
|
|
290
290
|
spec_tags = self._scan_tags(specs_dir, "@SPEC:")
|
|
291
291
|
code_tags = self._scan_tags(src_dir, "@CODE:")
|
|
292
292
|
|
|
293
|
-
#
|
|
293
|
+
# Validate the chain
|
|
294
294
|
broken_chains = []
|
|
295
295
|
for code_tag in code_tags:
|
|
296
296
|
tag_id = code_tag.split(":")[-1]
|
|
@@ -307,13 +307,13 @@ class TrustChecker:
|
|
|
307
307
|
|
|
308
308
|
def detect_orphan_tags(self, project_path: Path) -> list[str]:
|
|
309
309
|
"""
|
|
310
|
-
|
|
310
|
+
Detect orphan TAGs
|
|
311
311
|
|
|
312
312
|
Args:
|
|
313
|
-
project_path:
|
|
313
|
+
project_path: Project path
|
|
314
314
|
|
|
315
315
|
Returns:
|
|
316
|
-
list[str]:
|
|
316
|
+
list[str]: List of orphan TAGs
|
|
317
317
|
"""
|
|
318
318
|
specs_dir = project_path / ".moai" / "specs"
|
|
319
319
|
src_dir = project_path / "src"
|
|
@@ -331,14 +331,14 @@ class TrustChecker:
|
|
|
331
331
|
|
|
332
332
|
def _scan_tags(self, directory: Path, tag_prefix: str) -> list[str]:
|
|
333
333
|
"""
|
|
334
|
-
|
|
334
|
+
Scan for TAGs in a directory
|
|
335
335
|
|
|
336
336
|
Args:
|
|
337
|
-
directory:
|
|
338
|
-
tag_prefix: TAG
|
|
337
|
+
directory: Directory to scan
|
|
338
|
+
tag_prefix: TAG prefix (for example, "@SPEC:", "@CODE:")
|
|
339
339
|
|
|
340
340
|
Returns:
|
|
341
|
-
list[str]:
|
|
341
|
+
list[str]: List of discovered TAGs
|
|
342
342
|
"""
|
|
343
343
|
if not directory.exists():
|
|
344
344
|
return []
|
|
@@ -362,25 +362,25 @@ class TrustChecker:
|
|
|
362
362
|
|
|
363
363
|
def generate_report(self, results: dict[str, Any], format: str = "markdown") -> str:
|
|
364
364
|
"""
|
|
365
|
-
|
|
365
|
+
Generate validation report
|
|
366
366
|
|
|
367
367
|
Args:
|
|
368
|
-
results:
|
|
369
|
-
format:
|
|
368
|
+
results: Validation result dictionary
|
|
369
|
+
format: Report format ("markdown" or "json")
|
|
370
370
|
|
|
371
371
|
Returns:
|
|
372
|
-
str:
|
|
372
|
+
str: Report string
|
|
373
373
|
"""
|
|
374
374
|
if format == "json":
|
|
375
375
|
return json.dumps(results, indent=2)
|
|
376
376
|
|
|
377
|
-
# Markdown
|
|
377
|
+
# Markdown format
|
|
378
378
|
report = "# TRUST Validation Report\n\n"
|
|
379
379
|
|
|
380
380
|
for category, result in results.items():
|
|
381
381
|
status = "✅ PASS" if result.get("passed", False) else "❌ FAIL"
|
|
382
382
|
value = result.get('value', 'N/A')
|
|
383
|
-
#
|
|
383
|
+
# Add % suffix when the value is numeric
|
|
384
384
|
if isinstance(value, (int, float)):
|
|
385
385
|
value_str = f"{value}%"
|
|
386
386
|
else:
|
|
@@ -398,13 +398,13 @@ class TrustChecker:
|
|
|
398
398
|
|
|
399
399
|
def select_tools(self, project_path: Path) -> dict[str, str]:
|
|
400
400
|
"""
|
|
401
|
-
|
|
401
|
+
Automatically select tools by language
|
|
402
402
|
|
|
403
403
|
Args:
|
|
404
|
-
project_path:
|
|
404
|
+
project_path: Project path
|
|
405
405
|
|
|
406
406
|
Returns:
|
|
407
|
-
dict[str, str]:
|
|
407
|
+
dict[str, str]: Selected tool dictionary
|
|
408
408
|
"""
|
|
409
409
|
config_path = project_path / ".moai" / "config.json"
|
|
410
410
|
if not config_path.exists():
|
|
@@ -432,7 +432,7 @@ class TrustChecker:
|
|
|
432
432
|
"type_checker": "tsc",
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
#
|
|
435
|
+
# Default (Python)
|
|
436
436
|
return {
|
|
437
437
|
"test_framework": "pytest",
|
|
438
438
|
"coverage_tool": "coverage.py",
|
moai_adk/core/template/backup.py
CHANGED
|
@@ -7,7 +7,6 @@ Creates and manages backups to protect user data during template updates.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import shutil
|
|
10
|
-
from datetime import datetime
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
13
12
|
|
|
@@ -49,13 +48,19 @@ class TemplateBackup:
|
|
|
49
48
|
)
|
|
50
49
|
|
|
51
50
|
def create_backup(self) -> Path:
|
|
52
|
-
"""Create a
|
|
51
|
+
"""Create a single backup (always at .moai-backups/backup/).
|
|
52
|
+
|
|
53
|
+
Existing backups are overwritten to maintain only one backup copy.
|
|
53
54
|
|
|
54
55
|
Returns:
|
|
55
|
-
Backup path (
|
|
56
|
+
Backup path (always .moai-backups/backup/).
|
|
56
57
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
backup_path = self.target_path / ".moai-backups" / "backup"
|
|
59
|
+
|
|
60
|
+
# Remove existing backup if present
|
|
61
|
+
if backup_path.exists():
|
|
62
|
+
shutil.rmtree(backup_path)
|
|
63
|
+
|
|
59
64
|
backup_path.mkdir(parents=True, exist_ok=True)
|
|
60
65
|
|
|
61
66
|
# Copy backup targets
|
moai_adk/core/template/merger.py
CHANGED
|
@@ -15,10 +15,7 @@ from typing import Any
|
|
|
15
15
|
class TemplateMerger:
|
|
16
16
|
"""Encapsulate template merging logic."""
|
|
17
17
|
|
|
18
|
-
PROJECT_INFO_HEADERS = (
|
|
19
|
-
"## 프로젝트 정보",
|
|
20
|
-
"## Project Information",
|
|
21
|
-
)
|
|
18
|
+
PROJECT_INFO_HEADERS = ("## Project Information", "## 프로젝트 정보")
|
|
22
19
|
|
|
23
20
|
def __init__(self, target_path: Path) -> None:
|
|
24
21
|
"""Initialize the merger.
|
|
@@ -33,7 +30,7 @@ class TemplateMerger:
|
|
|
33
30
|
|
|
34
31
|
Rules:
|
|
35
32
|
- Use the latest template structure/content.
|
|
36
|
-
- Preserve the existing "##
|
|
33
|
+
- Preserve the existing "## Project Information" section.
|
|
37
34
|
|
|
38
35
|
Args:
|
|
39
36
|
template_path: Template CLAUDE.md.
|
|
@@ -376,7 +376,7 @@ class TemplateProcessor:
|
|
|
376
376
|
console.print(" ✅ .github/ copy complete (variables substituted)")
|
|
377
377
|
|
|
378
378
|
def _copy_claude_md(self, silent: bool = False) -> None:
|
|
379
|
-
"""Copy CLAUDE.md with smart merge (preserves \"##
|
|
379
|
+
"""Copy CLAUDE.md with smart merge (preserves \"## Project Information\" section)."""
|
|
380
380
|
src = self.template_root / "CLAUDE.md"
|
|
381
381
|
dst = self.target_path / "CLAUDE.md"
|
|
382
382
|
|
|
@@ -385,11 +385,11 @@ class TemplateProcessor:
|
|
|
385
385
|
console.print("⚠️ CLAUDE.md template not found")
|
|
386
386
|
return
|
|
387
387
|
|
|
388
|
-
# Smart merge: preserve existing "##
|
|
388
|
+
# Smart merge: preserve existing "## Project Information" section
|
|
389
389
|
if dst.exists():
|
|
390
390
|
self._merge_claude_md(src, dst)
|
|
391
391
|
if not silent:
|
|
392
|
-
console.print(" 🔄 CLAUDE.md merged (
|
|
392
|
+
console.print(" 🔄 CLAUDE.md merged (project information preserved)")
|
|
393
393
|
else:
|
|
394
394
|
# First time: just copy
|
|
395
395
|
self._copy_file_with_substitution(src, dst)
|
|
@@ -24,7 +24,8 @@ model: sonnet
|
|
|
24
24
|
- `Skill("moai-foundation-specs")` – Always checks the command/agent document structure.
|
|
25
25
|
|
|
26
26
|
**Conditional Skill Logic**
|
|
27
|
-
- `Skill("moai-alfred-
|
|
27
|
+
- `Skill("moai-alfred-language-detection")`: Always called first to detect project language/framework, which gates the activation of language-specific skills.
|
|
28
|
+
- `Skill("moai-alfred-tag-scanning")`: Called when a diff or `agent_skill_plan` contains a TAG influence.If the result is "Rules need to be updated", we subsequently chain `Skill("moai-foundation-tags")`.
|
|
28
29
|
- `Skill("moai-foundation-tags")`: Executed only when TAG naming reordering or traceability matrix update is confirmed.
|
|
29
30
|
- `Skill("moai-foundation-trust")`: Rechecks the latest guide when a TRUST policy/version update is detected or requested.
|
|
30
31
|
- `Skill("moai-alfred-trust-validation")`: Called when it is necessary to actually verify whether there is a standard violation based on the quality gate.
|
|
@@ -32,7 +33,9 @@ model: sonnet
|
|
|
32
33
|
- `Skill("moai-alfred-spec-metadata-validation")`: Only the relevant file is verified when a new command/agent document is created or the meta field is modified.
|
|
33
34
|
- Domain skills: When the brief includes CLI/Data Science/Database/DevOps/ML/Mobile/Security needs, add the corresponding item among `Skill("moai-domain-cli-tool")`, `Skill("moai-domain-data-science")`, `Skill("moai-domain-database")`, `Skill("moai-domain-devops")`, `Skill("moai-domain-ml")`, `Skill("moai-domain-mobile-app")`, `Skill("moai-domain-security")`.
|
|
34
35
|
- `Skill("moai-alfred-refactoring-coach")`: Called when the brief includes refactoring/TODO cleanup and a technical debt remediation plan is needed.
|
|
35
|
-
- Language skills: Based on the result of `Skill("moai-alfred-language-detection")`, activate the relevant
|
|
36
|
+
- **Language skills** (23 available): Based on the result of `Skill("moai-alfred-language-detection")`, activate the relevant language skill(s) from the Language Tier:
|
|
37
|
+
- Supported: Python, TypeScript, JavaScript, Java, Go, Rust, C#, C++, C, Clojure, Dart, Elixir, Haskell, Julia, Kotlin, Lua, PHP, R, Ruby, Scala, Shell, SQL, Swift
|
|
38
|
+
- Called as: `Skill("moai-lang-{language-name}")` (e.g., `Skill("moai-lang-python")`)
|
|
36
39
|
- `Skill("moai-claude-code")`: Used to customize the Claude Code output format or reorganize the code example template.
|
|
37
40
|
- `Skill("moai-alfred-tui-survey")`: Provides an interactive survey when changes to operating policies or introduction of standards need to be confirmed with user approval.
|
|
38
41
|
|
|
@@ -40,30 +40,38 @@ You are a Senior Project Manager Agent managing successful projects.
|
|
|
40
40
|
|
|
41
41
|
## 🎯 Key Role
|
|
42
42
|
|
|
43
|
-
**✅ project-manager is called from the `/alfred:
|
|
43
|
+
**✅ project-manager is called from the `/alfred:0-project` command**
|
|
44
44
|
|
|
45
|
-
- When `/alfred:
|
|
45
|
+
- When `/alfred:0-project` is executed, it is called as `Task: project-manager` to perform project analysis
|
|
46
|
+
- Receives **conversation_language** parameter from Alfred (e.g., "ko", "en", "ja", "zh") as first input
|
|
46
47
|
- Directly responsible for project type detection (new/legacy) and document creation
|
|
47
|
-
- Product/structure/tech documents interactively
|
|
48
|
-
- Putting into practice the method and structure of project document creation
|
|
48
|
+
- Product/structure/tech documents written interactively **in the selected language**
|
|
49
|
+
- Putting into practice the method and structure of project document creation with language localization
|
|
49
50
|
|
|
50
51
|
## 🔄 Workflow
|
|
51
52
|
|
|
52
53
|
**What the project-manager actually does:**
|
|
53
54
|
|
|
55
|
+
0. **Conversation Language Setup** (NEW):
|
|
56
|
+
- Receive `conversation_language` parameter from Alfred (e.g., "ko" for Korean, "en" for English)
|
|
57
|
+
- Confirm and announce the selected language in all subsequent interactions
|
|
58
|
+
- Store language preference in context for all generated documents and responses
|
|
59
|
+
- All prompts, questions, and outputs from this point forward are in the selected language
|
|
54
60
|
1. **Project status analysis**: `.moai/project/*.md`, README, read source structure
|
|
55
61
|
2. **Determination of project type**: Decision to introduce new (greenfield) vs. legacy
|
|
56
|
-
3. **User Interview**: Gather information with a question tree tailored to the project type
|
|
57
|
-
4. **Create Document**: Create or update product/structure/tech.md
|
|
62
|
+
3. **User Interview**: Gather information with a question tree tailored to the project type (questions delivered in selected language)
|
|
63
|
+
4. **Create Document**: Create or update product/structure/tech.md (all documents generated in the selected language)
|
|
58
64
|
5. **Prevention of duplication**: Prohibit creation of `.claude/memory/` or `.claude/commands/alfred/*.json` files
|
|
59
|
-
6. **Memory Synchronization**: Leverage CLAUDE.md's existing `@.moai/project/*` import.
|
|
65
|
+
6. **Memory Synchronization**: Leverage CLAUDE.md's existing `@.moai/project/*` import and add language metadata.
|
|
60
66
|
|
|
61
67
|
## 📦 Deliverables and Delivery
|
|
62
68
|
|
|
63
|
-
- Updated `.moai/project/{product,structure,tech}.md`
|
|
64
|
-
-
|
|
69
|
+
- Updated `.moai/project/{product,structure,tech}.md` (in the selected language)
|
|
70
|
+
- Updated `.moai/config.json` with language metadata (conversation_language, language_name)
|
|
71
|
+
- Project overview summary (team size, technology stack, constraints) in selected language
|
|
65
72
|
- Individual/team mode settings confirmation results
|
|
66
73
|
- For legacy projects, organized with "Legacy Context" TODO/DEBT items
|
|
74
|
+
- Language preference confirmation in final summary
|
|
67
75
|
|
|
68
76
|
## ✅ Operational checkpoints
|
|
69
77
|
|