moai-adk 0.4.0__py3-none-any.whl → 0.4.4__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 +2 -3
- moai_adk/cli/commands/init.py +10 -5
- moai_adk/cli/commands/update.py +274 -118
- moai_adk/cli/prompts/init_prompts.py +14 -18
- moai_adk/core/diagnostics/slash_commands.py +1 -1
- moai_adk/core/project/backup_utils.py +2 -11
- moai_adk/core/project/checker.py +2 -2
- moai_adk/core/project/phase_executor.py +11 -14
- moai_adk/core/project/validator.py +3 -2
- 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 +21 -8
- moai_adk/core/template/merger.py +14 -4
- moai_adk/core/template/processor.py +24 -5
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +446 -424
- moai_adk/templates/.claude/agents/alfred/debug-helper.md +116 -103
- moai_adk/templates/.claude/agents/alfred/doc-syncer.md +130 -116
- moai_adk/templates/.claude/agents/alfred/git-manager.md +186 -174
- moai_adk/templates/.claude/agents/alfred/implementation-planner.md +227 -213
- moai_adk/templates/.claude/agents/alfred/project-manager.md +216 -128
- moai_adk/templates/.claude/agents/alfred/quality-gate.md +224 -209
- moai_adk/templates/.claude/agents/alfred/spec-builder.md +174 -160
- moai_adk/templates/.claude/agents/alfred/tag-agent.md +151 -139
- moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +209 -196
- moai_adk/templates/.claude/agents/alfred/trust-checker.md +247 -233
- moai_adk/templates/.claude/commands/alfred/0-project.md +756 -640
- moai_adk/templates/.claude/commands/alfred/1-plan.md +343 -333
- moai_adk/templates/.claude/commands/alfred/2-run.md +297 -285
- moai_adk/templates/.claude/commands/alfred/3-sync.md +387 -356
- moai_adk/templates/.claude/hooks/alfred/README.md +52 -52
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +44 -48
- moai_adk/templates/.claude/hooks/alfred/core/__init__.py +17 -17
- moai_adk/templates/.claude/hooks/alfred/core/checkpoint.py +59 -59
- moai_adk/templates/.claude/hooks/alfred/core/context.py +19 -19
- moai_adk/templates/.claude/hooks/alfred/core/project.py +52 -52
- moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +1 -1
- moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +4 -4
- moai_adk/templates/.claude/hooks/alfred/handlers/session.py +27 -27
- moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +16 -17
- moai_adk/templates/.claude/hooks/alfred/handlers/user.py +11 -11
- moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +308 -307
- moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +297 -296
- moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +191 -190
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/SKILL.md +112 -0
- moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/SKILL.md +103 -0
- moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/SKILL.md +103 -0
- moai_adk/templates/.claude/skills/moai-alfred-git-workflow/SKILL.md +95 -0
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/SKILL.md +99 -0
- moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/SKILL.md +105 -0
- moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/SKILL.md +97 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/SKILL.md +97 -0
- moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/SKILL.md +90 -0
- moai_adk/templates/.claude/skills/moai-alfred-trust-validation/SKILL.md +99 -0
- moai_adk/templates/.claude/skills/moai-alfred-tui-survey/SKILL.md +87 -0
- moai_adk/templates/.claude/skills/moai-alfred-tui-survey/examples.md +62 -0
- moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +70 -43
- moai_adk/templates/.claude/skills/moai-claude-code/examples.md +141 -141
- moai_adk/templates/.claude/skills/moai-claude-code/reference.md +179 -165
- moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +78 -78
- moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +90 -90
- moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +39 -25
- moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +117 -74
- moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +131 -134
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +43 -11
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +43 -12
- moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +47 -11
- moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +47 -11
- moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +51 -14
- moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +46 -10
- moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +62 -25
- moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +44 -17
- moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +44 -14
- moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +45 -13
- moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +46 -14
- moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +48 -8
- moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +44 -11
- moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +44 -12
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +44 -12
- moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +44 -43
- moai_adk/templates/.github/workflows/moai-gitflow.yml +36 -35
- moai_adk/templates/.moai/config.json +9 -6
- moai_adk/templates/.moai/memory/development-guide.md +220 -221
- moai_adk/templates/.moai/memory/gitflow-protection-policy.md +85 -85
- moai_adk/templates/.moai/memory/spec-metadata.md +229 -150
- moai_adk/templates/.moai/project/product.md +90 -90
- moai_adk/templates/.moai/project/structure.md +85 -85
- moai_adk/templates/.moai/project/tech.md +117 -117
- moai_adk/templates/CLAUDE.md +564 -709
- moai_adk-0.4.4.dist-info/METADATA +369 -0
- moai_adk-0.4.4.dist-info/RECORD +152 -0
- moai_adk/templates/.claude/commands/alfred/1-spec.md +0 -31
- moai_adk/templates/.claude/commands/alfred/2-build.md +0 -30
- moai_adk/templates/.claude/skills/scripts/standardize_skills.py +0 -166
- moai_adk/templates/.claude/skills/scripts/verify_standardization.sh +0 -43
- moai_adk/templates/.moai/hooks/pre-push.sample +0 -88
- moai_adk-0.4.0.dist-info/METADATA +0 -1816
- moai_adk-0.4.0.dist-info/RECORD +0 -145
- {moai_adk-0.4.0.dist-info → moai_adk-0.4.4.dist-info}/WHEEL +0 -0
- {moai_adk-0.4.0.dist-info → moai_adk-0.4.4.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.4.0.dist-info → moai_adk-0.4.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -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,
|
|
@@ -53,6 +52,7 @@ class PhaseExecutor:
|
|
|
53
52
|
".moai/memory/",
|
|
54
53
|
".claude/",
|
|
55
54
|
".claude/logs/",
|
|
55
|
+
".github/",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
def __init__(self, validator: ProjectValidator) -> None:
|
|
@@ -158,6 +158,7 @@ class PhaseExecutor:
|
|
|
158
158
|
return [
|
|
159
159
|
".claude/",
|
|
160
160
|
".moai/",
|
|
161
|
+
".github/",
|
|
161
162
|
"CLAUDE.md",
|
|
162
163
|
".gitignore",
|
|
163
164
|
]
|
|
@@ -219,7 +220,7 @@ class PhaseExecutor:
|
|
|
219
220
|
# @CODE:INIT-004:VERIFY-001 | Validate installation results
|
|
220
221
|
# @CODE:INIT-004:VALIDATION-CHECK | Comprehensive installation validation
|
|
221
222
|
# Verifies all required files including 4 Alfred command files:
|
|
222
|
-
# - 0-project.md, 1-
|
|
223
|
+
# - 0-project.md, 1-plan.md, 2-run.md, 3-sync.md
|
|
223
224
|
self.validator.validate_installation(project_path)
|
|
224
225
|
|
|
225
226
|
# Initialize Git for team mode
|
|
@@ -227,25 +228,21 @@ class PhaseExecutor:
|
|
|
227
228
|
self._initialize_git(project_path)
|
|
228
229
|
|
|
229
230
|
def _create_backup(self, project_path: Path) -> None:
|
|
230
|
-
"""Create a
|
|
231
|
+
"""Create a single backup (v0.4.2).
|
|
231
232
|
|
|
232
|
-
|
|
233
|
+
Maintains only one backup at .moai-backups/backup/.
|
|
233
234
|
|
|
234
235
|
Args:
|
|
235
236
|
project_path: Project path.
|
|
236
237
|
"""
|
|
237
238
|
# Define backup directory
|
|
238
239
|
backups_dir = project_path / ".moai-backups"
|
|
240
|
+
backup_path = backups_dir / "backup"
|
|
239
241
|
|
|
240
|
-
# Remove
|
|
241
|
-
if
|
|
242
|
-
|
|
243
|
-
if item.is_dir():
|
|
244
|
-
shutil.rmtree(item)
|
|
242
|
+
# Remove existing backup if present
|
|
243
|
+
if backup_path.exists():
|
|
244
|
+
shutil.rmtree(backup_path)
|
|
245
245
|
|
|
246
|
-
# Create new backup directory (.moai-backups/{timestamp}/)
|
|
247
|
-
timestamp = generate_backup_dir_name()
|
|
248
|
-
backup_path = backups_dir / timestamp
|
|
249
246
|
backup_path.mkdir(parents=True, exist_ok=True)
|
|
250
247
|
|
|
251
248
|
# Collect backup targets
|
|
@@ -37,6 +37,7 @@ class ProjectValidator:
|
|
|
37
37
|
".moai/specs/",
|
|
38
38
|
".moai/memory/",
|
|
39
39
|
".claude/",
|
|
40
|
+
".github/",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
# Required files
|
|
@@ -48,8 +49,8 @@ class ProjectValidator:
|
|
|
48
49
|
# Required Alfred command files (SPEC-INIT-004)
|
|
49
50
|
REQUIRED_ALFRED_COMMANDS = [
|
|
50
51
|
"0-project.md",
|
|
51
|
-
"1-
|
|
52
|
-
"2-
|
|
52
|
+
"1-plan.md",
|
|
53
|
+
"2-run.md",
|
|
53
54
|
"3-sync.md",
|
|
54
55
|
]
|
|
55
56
|
|
|
@@ -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
|
|
|
@@ -27,7 +26,15 @@ class TemplateBackup:
|
|
|
27
26
|
target_path: Project path (absolute).
|
|
28
27
|
"""
|
|
29
28
|
self.target_path = target_path.resolve()
|
|
30
|
-
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def backup_dir(self) -> Path:
|
|
32
|
+
"""Get the backup directory path.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Path to .moai-backups directory.
|
|
36
|
+
"""
|
|
37
|
+
return self.target_path / ".moai-backups"
|
|
31
38
|
|
|
32
39
|
def has_existing_files(self) -> bool:
|
|
33
40
|
"""Check whether backup-worthy files already exist.
|
|
@@ -37,21 +44,27 @@ class TemplateBackup:
|
|
|
37
44
|
"""
|
|
38
45
|
return any(
|
|
39
46
|
(self.target_path / item).exists()
|
|
40
|
-
for item in [".moai", ".claude", "CLAUDE.md"]
|
|
47
|
+
for item in [".moai", ".claude", ".github", "CLAUDE.md"]
|
|
41
48
|
)
|
|
42
49
|
|
|
43
50
|
def create_backup(self) -> Path:
|
|
44
|
-
"""Create a
|
|
51
|
+
"""Create a single backup (always at .moai-backups/backup/).
|
|
52
|
+
|
|
53
|
+
Existing backups are overwritten to maintain only one backup copy.
|
|
45
54
|
|
|
46
55
|
Returns:
|
|
47
|
-
Backup path (
|
|
56
|
+
Backup path (always .moai-backups/backup/).
|
|
48
57
|
"""
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
|
|
51
64
|
backup_path.mkdir(parents=True, exist_ok=True)
|
|
52
65
|
|
|
53
66
|
# Copy backup targets
|
|
54
|
-
for item in [".moai", ".claude", "CLAUDE.md"]:
|
|
67
|
+
for item in [".moai", ".claude", ".github", "CLAUDE.md"]:
|
|
55
68
|
src = self.target_path / item
|
|
56
69
|
if not src.exists():
|
|
57
70
|
continue
|
moai_adk/core/template/merger.py
CHANGED
|
@@ -15,6 +15,8 @@ from typing import Any
|
|
|
15
15
|
class TemplateMerger:
|
|
16
16
|
"""Encapsulate template merging logic."""
|
|
17
17
|
|
|
18
|
+
PROJECT_INFO_HEADERS = ("## Project Information", "## 프로젝트 정보")
|
|
19
|
+
|
|
18
20
|
def __init__(self, target_path: Path) -> None:
|
|
19
21
|
"""Initialize the merger.
|
|
20
22
|
|
|
@@ -28,15 +30,15 @@ class TemplateMerger:
|
|
|
28
30
|
|
|
29
31
|
Rules:
|
|
30
32
|
- Use the latest template structure/content.
|
|
31
|
-
- Preserve the existing "##
|
|
33
|
+
- Preserve the existing "## Project Information" section.
|
|
32
34
|
|
|
33
35
|
Args:
|
|
34
36
|
template_path: Template CLAUDE.md.
|
|
35
37
|
existing_path: Existing CLAUDE.md.
|
|
36
38
|
"""
|
|
37
|
-
# Extract the existing
|
|
39
|
+
# Extract the existing project information section
|
|
38
40
|
existing_content = existing_path.read_text(encoding="utf-8")
|
|
39
|
-
project_info_start =
|
|
41
|
+
project_info_start, _ = self._find_project_info_section(existing_content)
|
|
40
42
|
project_info = ""
|
|
41
43
|
if project_info_start != -1:
|
|
42
44
|
# Extract until EOF
|
|
@@ -48,7 +50,7 @@ class TemplateMerger:
|
|
|
48
50
|
# Merge when project info exists
|
|
49
51
|
if project_info:
|
|
50
52
|
# Remove the project info section from the template
|
|
51
|
-
template_project_start =
|
|
53
|
+
template_project_start, _ = self._find_project_info_section(template_content)
|
|
52
54
|
if template_project_start != -1:
|
|
53
55
|
template_content = template_content[:template_project_start].rstrip()
|
|
54
56
|
|
|
@@ -59,6 +61,14 @@ class TemplateMerger:
|
|
|
59
61
|
# No project info; copy the template as-is
|
|
60
62
|
shutil.copy2(template_path, existing_path)
|
|
61
63
|
|
|
64
|
+
def _find_project_info_section(self, content: str) -> tuple[int, str | None]:
|
|
65
|
+
"""Find the project information header in the given content."""
|
|
66
|
+
for header in self.PROJECT_INFO_HEADERS:
|
|
67
|
+
index = content.find(header)
|
|
68
|
+
if index != -1:
|
|
69
|
+
return index, header
|
|
70
|
+
return -1, None
|
|
71
|
+
|
|
62
72
|
def merge_gitignore(self, template_path: Path, existing_path: Path) -> None:
|
|
63
73
|
""".gitignore merge.
|
|
64
74
|
|
|
@@ -178,6 +178,7 @@ class TemplateProcessor:
|
|
|
178
178
|
|
|
179
179
|
self._copy_claude(silent)
|
|
180
180
|
self._copy_moai(silent)
|
|
181
|
+
self._copy_github(silent)
|
|
181
182
|
self._copy_claude_md(silent)
|
|
182
183
|
self._copy_gitignore(silent)
|
|
183
184
|
|
|
@@ -237,7 +238,7 @@ class TemplateProcessor:
|
|
|
237
238
|
Strategy:
|
|
238
239
|
- Alfred folders (commands/agents/hooks/output-styles/alfred) → copy wholesale (delete & overwrite)
|
|
239
240
|
* Creates individual backup before deletion for safety
|
|
240
|
-
* Commands: 0-project.md, 1-
|
|
241
|
+
* Commands: 0-project.md, 1-plan.md, 2-run.md, 3-sync.md
|
|
241
242
|
- Other files/folders → copy individually (preserve existing)
|
|
242
243
|
"""
|
|
243
244
|
src = self.template_root / ".claude"
|
|
@@ -256,7 +257,7 @@ class TemplateProcessor:
|
|
|
256
257
|
# Alfred folders to copy wholesale (overwrite)
|
|
257
258
|
alfred_folders = [
|
|
258
259
|
"hooks/alfred",
|
|
259
|
-
"commands/alfred", # Contains 0-project.md, 1-
|
|
260
|
+
"commands/alfred", # Contains 0-project.md, 1-plan.md, 2-run.md, 3-sync.md
|
|
260
261
|
"output-styles/alfred",
|
|
261
262
|
"agents/alfred",
|
|
262
263
|
]
|
|
@@ -356,8 +357,26 @@ class TemplateProcessor:
|
|
|
356
357
|
if not silent:
|
|
357
358
|
console.print(" ✅ .moai/ copy complete (variables substituted)")
|
|
358
359
|
|
|
360
|
+
def _copy_github(self, silent: bool = False) -> None:
|
|
361
|
+
""".github/ directory copy with variable substitution."""
|
|
362
|
+
src = self.template_root / ".github"
|
|
363
|
+
dst = self.target_path / ".github"
|
|
364
|
+
|
|
365
|
+
if not src.exists():
|
|
366
|
+
if not silent:
|
|
367
|
+
console.print("⚠️ .github/ template not found")
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
if dst.exists():
|
|
371
|
+
shutil.rmtree(dst)
|
|
372
|
+
|
|
373
|
+
self._copy_dir_with_substitution(src, dst)
|
|
374
|
+
|
|
375
|
+
if not silent:
|
|
376
|
+
console.print(" ✅ .github/ copy complete (variables substituted)")
|
|
377
|
+
|
|
359
378
|
def _copy_claude_md(self, silent: bool = False) -> None:
|
|
360
|
-
"""Copy CLAUDE.md with smart merge (preserves "##
|
|
379
|
+
"""Copy CLAUDE.md with smart merge (preserves \"## Project Information\" section)."""
|
|
361
380
|
src = self.template_root / "CLAUDE.md"
|
|
362
381
|
dst = self.target_path / "CLAUDE.md"
|
|
363
382
|
|
|
@@ -366,11 +385,11 @@ class TemplateProcessor:
|
|
|
366
385
|
console.print("⚠️ CLAUDE.md template not found")
|
|
367
386
|
return
|
|
368
387
|
|
|
369
|
-
# Smart merge: preserve existing "##
|
|
388
|
+
# Smart merge: preserve existing "## Project Information" section
|
|
370
389
|
if dst.exists():
|
|
371
390
|
self._merge_claude_md(src, dst)
|
|
372
391
|
if not silent:
|
|
373
|
-
console.print(" 🔄 CLAUDE.md merged (
|
|
392
|
+
console.print(" 🔄 CLAUDE.md merged (project information preserved)")
|
|
374
393
|
else:
|
|
375
394
|
# First time: just copy
|
|
376
395
|
self._copy_file_with_substitution(src, dst)
|