moai-adk 0.10.1__py3-none-any.whl → 0.11.1__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/core/issue_creator.py +2 -2
- moai_adk/core/project/detector.py +285 -12
- moai_adk/core/project/phase_executor.py +4 -0
- moai_adk/core/tags/ci_validator.py +33 -3
- moai_adk/core/template_engine.py +6 -2
- moai_adk/templates/.claude/commands/alfred/0-project.md +60 -62
- moai_adk/templates/.claude/commands/alfred/1-plan.md +6 -0
- moai_adk/templates/.claude/commands/alfred/2-run.md +6 -0
- moai_adk/templates/.claude/commands/alfred/3-sync.md +6 -0
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +8 -9
- moai_adk/templates/.claude/hooks/alfred/core/project.py +22 -28
- moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
- moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +109 -0
- moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +4 -4
- moai_adk/templates/.claude/hooks/alfred/notification__handle_events.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/shared/core/__init__.py +2 -2
- moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +19 -26
- moai_adk/templates/.claude/hooks/alfred/shared/core/tags.py +55 -23
- moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +4 -4
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +134 -3
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/session.py +9 -10
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/tool.py +3 -6
- moai_adk/templates/.claude/hooks/alfred/stop__handle_interrupt.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/subagent_stop__handle_subagent_end.py +10 -15
- moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +11 -20
- moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
- moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +136 -0
- moai_adk/templates/.github/workflows/c-tag-validation.yml +83 -0
- moai_adk/templates/.github/workflows/cpp-tag-validation.yml +79 -0
- moai_adk/templates/.github/workflows/csharp-tag-validation.yml +65 -0
- moai_adk/templates/.github/workflows/dart-tag-validation.yml +82 -0
- moai_adk/templates/.github/workflows/java-tag-validation.yml +75 -0
- moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +67 -0
- moai_adk/templates/.github/workflows/{release.yml → moai-adk-release.yml} +6 -2
- moai_adk/templates/.github/workflows/{tag-validation.yml → moai-adk-tag-validation.yml} +53 -8
- moai_adk/templates/.github/workflows/moai-gitflow.yml +6 -1
- moai_adk/templates/.github/workflows/php-tag-validation.yml +56 -0
- moai_adk/templates/.github/workflows/ruby-tag-validation.yml +68 -0
- moai_adk/templates/.github/workflows/rust-tag-validation.yml +73 -0
- moai_adk/templates/.github/workflows/shell-tag-validation.yml +65 -0
- moai_adk/templates/.github/workflows/swift-tag-validation.yml +79 -0
- moai_adk/templates/.moai/memory/GITFLOW-PROTECTION-POLICY.md +330 -0
- moai_adk/templates/.moai/memory/SPEC-METADATA.md +356 -0
- moai_adk/templates/CLAUDE.md +536 -65
- moai_adk/templates/workflows/go-tag-validation.yml +130 -0
- moai_adk/templates/workflows/javascript-tag-validation.yml +135 -0
- moai_adk/templates/workflows/python-tag-validation.yml +118 -0
- moai_adk/templates/workflows/typescript-tag-validation.yml +154 -0
- {moai_adk-0.10.1.dist-info → moai_adk-0.11.1.dist-info}/METADATA +70 -13
- {moai_adk-0.10.1.dist-info → moai_adk-0.11.1.dist-info}/RECORD +58 -37
- /moai_adk/templates/.github/workflows/{spec-issue-sync.yml → moai-adk-spec-issue-sync.yml} +0 -0
- {moai_adk-0.10.1.dist-info → moai_adk-0.11.1.dist-info}/WHEEL +0 -0
- {moai_adk-0.10.1.dist-info → moai_adk-0.11.1.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.10.1.dist-info → moai_adk-0.11.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -16,9 +16,10 @@ Risky Operations Detected:
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
|
-
import signal
|
|
20
19
|
import sys
|
|
21
|
-
from pathlib import
|
|
20
|
+
from pathlib import
|
|
21
|
+
from utils.timeout import CrossPlatformTimeout, TimeoutError as PlatformTimeoutError
|
|
22
|
+
Path
|
|
22
23
|
from typing import Any
|
|
23
24
|
|
|
24
25
|
# Setup import path for shared modules
|
|
@@ -30,15 +31,9 @@ if str(SHARED_DIR) not in sys.path:
|
|
|
30
31
|
from handlers import handle_pre_tool_use
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
class HookTimeoutError(Exception):
|
|
34
|
-
"""Hook execution timeout exception"""
|
|
35
34
|
pass
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
def _timeout_handler(signum, frame):
|
|
39
|
-
"""Signal handler for 5-second timeout"""
|
|
40
|
-
raise HookTimeoutError("Hook execution exceeded 5-second timeout")
|
|
41
|
-
|
|
42
37
|
|
|
43
38
|
def main() -> None:
|
|
44
39
|
"""Main entry point for PreToolUse hook
|
|
@@ -54,8 +49,8 @@ def main() -> None:
|
|
|
54
49
|
1: Error (timeout, JSON parse failure, handler exception)
|
|
55
50
|
"""
|
|
56
51
|
# Set 5-second timeout
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
timeout = CrossPlatformTimeout(5)
|
|
53
|
+
timeout.start()
|
|
59
54
|
|
|
60
55
|
try:
|
|
61
56
|
# Read JSON payload from stdin
|
|
@@ -69,11 +64,11 @@ def main() -> None:
|
|
|
69
64
|
print(json.dumps(result.to_dict()))
|
|
70
65
|
sys.exit(0)
|
|
71
66
|
|
|
72
|
-
except
|
|
67
|
+
except PlatformTimeoutError:
|
|
73
68
|
# Timeout - return minimal valid response (allow operation to continue)
|
|
74
69
|
timeout_response: dict[str, Any] = {
|
|
75
70
|
"continue": True,
|
|
76
|
-
"systemMessage": "⚠️ Checkpoint creation timeout - operation proceeding without checkpoint"
|
|
71
|
+
"systemMessage": "⚠️ Checkpoint creation timeout - operation proceeding without checkpoint",
|
|
77
72
|
}
|
|
78
73
|
print(json.dumps(timeout_response))
|
|
79
74
|
print("PreToolUse hook timeout after 5 seconds", file=sys.stderr)
|
|
@@ -83,7 +78,7 @@ def main() -> None:
|
|
|
83
78
|
# JSON parse error - allow operation to continue
|
|
84
79
|
error_response: dict[str, Any] = {
|
|
85
80
|
"continue": True,
|
|
86
|
-
"hookSpecificOutput": {"error": f"JSON parse error: {e}"}
|
|
81
|
+
"hookSpecificOutput": {"error": f"JSON parse error: {e}"},
|
|
87
82
|
}
|
|
88
83
|
print(json.dumps(error_response))
|
|
89
84
|
print(f"PreToolUse JSON parse error: {e}", file=sys.stderr)
|
|
@@ -93,7 +88,7 @@ def main() -> None:
|
|
|
93
88
|
# Unexpected error - allow operation to continue
|
|
94
89
|
error_response: dict[str, Any] = {
|
|
95
90
|
"continue": True,
|
|
96
|
-
"hookSpecificOutput": {"error": f"PreToolUse error: {e}"}
|
|
91
|
+
"hookSpecificOutput": {"error": f"PreToolUse error: {e}"},
|
|
97
92
|
}
|
|
98
93
|
print(json.dumps(error_response))
|
|
99
94
|
print(f"PreToolUse unexpected error: {e}", file=sys.stderr)
|
|
@@ -101,7 +96,7 @@ def main() -> None:
|
|
|
101
96
|
|
|
102
97
|
finally:
|
|
103
98
|
# Always cancel alarm
|
|
104
|
-
|
|
99
|
+
timeout.cancel()
|
|
105
100
|
|
|
106
101
|
|
|
107
102
|
if __name__ == "__main__":
|
|
@@ -10,9 +10,10 @@ Output: Continue execution (currently a stub for future enhancements)
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
import signal
|
|
14
13
|
import sys
|
|
15
|
-
from pathlib import
|
|
14
|
+
from pathlib import
|
|
15
|
+
from utils.timeout import CrossPlatformTimeout, TimeoutError as PlatformTimeoutError
|
|
16
|
+
Path
|
|
16
17
|
from typing import Any
|
|
17
18
|
|
|
18
19
|
# Setup import path for shared modules
|
|
@@ -24,15 +25,9 @@ if str(SHARED_DIR) not in sys.path:
|
|
|
24
25
|
from handlers import handle_session_end
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
class HookTimeoutError(Exception):
|
|
28
|
-
"""Hook execution timeout exception"""
|
|
29
28
|
pass
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
def _timeout_handler(signum, frame):
|
|
33
|
-
"""Signal handler for 5-second timeout"""
|
|
34
|
-
raise HookTimeoutError("Hook execution exceeded 5-second timeout")
|
|
35
|
-
|
|
36
31
|
|
|
37
32
|
def main() -> None:
|
|
38
33
|
"""Main entry point for SessionEnd hook
|
|
@@ -48,8 +43,8 @@ def main() -> None:
|
|
|
48
43
|
1: Error (timeout, JSON parse failure, handler exception)
|
|
49
44
|
"""
|
|
50
45
|
# Set 5-second timeout
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
timeout = CrossPlatformTimeout(5)
|
|
47
|
+
timeout.start()
|
|
53
48
|
|
|
54
49
|
try:
|
|
55
50
|
# Read JSON payload from stdin
|
|
@@ -63,11 +58,11 @@ def main() -> None:
|
|
|
63
58
|
print(json.dumps(result.to_dict()))
|
|
64
59
|
sys.exit(0)
|
|
65
60
|
|
|
66
|
-
except
|
|
61
|
+
except PlatformTimeoutError:
|
|
67
62
|
# Timeout - return minimal valid response
|
|
68
63
|
timeout_response: dict[str, Any] = {
|
|
69
64
|
"continue": True,
|
|
70
|
-
"systemMessage": "⚠️ SessionEnd cleanup timeout - session ending anyway"
|
|
65
|
+
"systemMessage": "⚠️ SessionEnd cleanup timeout - session ending anyway",
|
|
71
66
|
}
|
|
72
67
|
print(json.dumps(timeout_response))
|
|
73
68
|
print("SessionEnd hook timeout after 5 seconds", file=sys.stderr)
|
|
@@ -77,7 +72,7 @@ def main() -> None:
|
|
|
77
72
|
# JSON parse error
|
|
78
73
|
error_response: dict[str, Any] = {
|
|
79
74
|
"continue": True,
|
|
80
|
-
"hookSpecificOutput": {"error": f"JSON parse error: {e}"}
|
|
75
|
+
"hookSpecificOutput": {"error": f"JSON parse error: {e}"},
|
|
81
76
|
}
|
|
82
77
|
print(json.dumps(error_response))
|
|
83
78
|
print(f"SessionEnd JSON parse error: {e}", file=sys.stderr)
|
|
@@ -87,7 +82,7 @@ def main() -> None:
|
|
|
87
82
|
# Unexpected error
|
|
88
83
|
error_response: dict[str, Any] = {
|
|
89
84
|
"continue": True,
|
|
90
|
-
"hookSpecificOutput": {"error": f"SessionEnd error: {e}"}
|
|
85
|
+
"hookSpecificOutput": {"error": f"SessionEnd error: {e}"},
|
|
91
86
|
}
|
|
92
87
|
print(json.dumps(error_response))
|
|
93
88
|
print(f"SessionEnd unexpected error: {e}", file=sys.stderr)
|
|
@@ -95,7 +90,7 @@ def main() -> None:
|
|
|
95
90
|
|
|
96
91
|
finally:
|
|
97
92
|
# Always cancel alarm
|
|
98
|
-
|
|
93
|
+
timeout.cancel()
|
|
99
94
|
|
|
100
95
|
|
|
101
96
|
if __name__ == "__main__":
|
|
@@ -10,9 +10,10 @@ Output: System message with formatted project summary
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
import signal
|
|
14
13
|
import sys
|
|
15
|
-
from pathlib import
|
|
14
|
+
from pathlib import
|
|
15
|
+
from utils.timeout import CrossPlatformTimeout, TimeoutError as PlatformTimeoutError
|
|
16
|
+
Path
|
|
16
17
|
from typing import Any
|
|
17
18
|
|
|
18
19
|
# Setup import path for shared modules
|
|
@@ -24,15 +25,9 @@ if str(SHARED_DIR) not in sys.path:
|
|
|
24
25
|
from handlers import handle_session_start
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
class HookTimeoutError(Exception):
|
|
28
|
-
"""Hook execution timeout exception"""
|
|
29
28
|
pass
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
def _timeout_handler(signum, frame):
|
|
33
|
-
"""Signal handler for 5-second timeout"""
|
|
34
|
-
raise HookTimeoutError("Hook execution exceeded 5-second timeout")
|
|
35
|
-
|
|
36
31
|
|
|
37
32
|
def main() -> None:
|
|
38
33
|
"""Main entry point for SessionStart hook
|
|
@@ -48,8 +43,8 @@ def main() -> None:
|
|
|
48
43
|
1: Error (timeout, JSON parse failure, handler exception)
|
|
49
44
|
"""
|
|
50
45
|
# Set 5-second timeout
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
timeout = CrossPlatformTimeout(5)
|
|
47
|
+
timeout.start()
|
|
53
48
|
|
|
54
49
|
try:
|
|
55
50
|
# Read JSON payload from stdin
|
|
@@ -63,11 +58,11 @@ def main() -> None:
|
|
|
63
58
|
print(json.dumps(result.to_dict()))
|
|
64
59
|
sys.exit(0)
|
|
65
60
|
|
|
66
|
-
except
|
|
61
|
+
except PlatformTimeoutError:
|
|
67
62
|
# Timeout - return minimal valid response
|
|
68
63
|
timeout_response: dict[str, Any] = {
|
|
69
64
|
"continue": True,
|
|
70
|
-
"systemMessage": "⚠️ Session start timeout - continuing without project info"
|
|
65
|
+
"systemMessage": "⚠️ Session start timeout - continuing without project info",
|
|
71
66
|
}
|
|
72
67
|
print(json.dumps(timeout_response))
|
|
73
68
|
print("SessionStart hook timeout after 5 seconds", file=sys.stderr)
|
|
@@ -77,7 +72,7 @@ def main() -> None:
|
|
|
77
72
|
# JSON parse error
|
|
78
73
|
error_response: dict[str, Any] = {
|
|
79
74
|
"continue": True,
|
|
80
|
-
"hookSpecificOutput": {"error": f"JSON parse error: {e}"}
|
|
75
|
+
"hookSpecificOutput": {"error": f"JSON parse error: {e}"},
|
|
81
76
|
}
|
|
82
77
|
print(json.dumps(error_response))
|
|
83
78
|
print(f"SessionStart JSON parse error: {e}", file=sys.stderr)
|
|
@@ -87,7 +82,7 @@ def main() -> None:
|
|
|
87
82
|
# Unexpected error
|
|
88
83
|
error_response: dict[str, Any] = {
|
|
89
84
|
"continue": True,
|
|
90
|
-
"hookSpecificOutput": {"error": f"SessionStart error: {e}"}
|
|
85
|
+
"hookSpecificOutput": {"error": f"SessionStart error: {e}"},
|
|
91
86
|
}
|
|
92
87
|
print(json.dumps(error_response))
|
|
93
88
|
print(f"SessionStart unexpected error: {e}", file=sys.stderr)
|
|
@@ -95,7 +90,7 @@ def main() -> None:
|
|
|
95
90
|
|
|
96
91
|
finally:
|
|
97
92
|
# Always cancel alarm
|
|
98
|
-
|
|
93
|
+
timeout.cancel()
|
|
99
94
|
|
|
100
95
|
|
|
101
96
|
if __name__ == "__main__":
|
|
@@ -70,6 +70,7 @@ def find_project_root(start_path: str | Path = ".") -> Path:
|
|
|
70
70
|
|
|
71
71
|
class TimeoutError(Exception):
|
|
72
72
|
"""Signal-based timeout exception"""
|
|
73
|
+
|
|
73
74
|
pass
|
|
74
75
|
|
|
75
76
|
|
|
@@ -86,6 +87,7 @@ def timeout_handler(seconds: int):
|
|
|
86
87
|
Raises:
|
|
87
88
|
TimeoutError: If operation exceeds timeout
|
|
88
89
|
"""
|
|
90
|
+
|
|
89
91
|
def _handle_timeout(signum, frame):
|
|
90
92
|
raise TimeoutError(f"Operation timed out after {seconds} seconds")
|
|
91
93
|
|
|
@@ -427,19 +429,10 @@ def get_version_check_config(cwd: str) -> dict[str, Any]:
|
|
|
427
429
|
- REFACTOR: Add validation and error handling
|
|
428
430
|
"""
|
|
429
431
|
# TTL mapping by frequency
|
|
430
|
-
|
|
431
|
-
"always": 0,
|
|
432
|
-
"daily": 24,
|
|
433
|
-
"weekly": 168,
|
|
434
|
-
"never": float('inf')
|
|
435
|
-
}
|
|
432
|
+
ttl_by_frequency = {"always": 0, "daily": 24, "weekly": 168, "never": float("inf")}
|
|
436
433
|
|
|
437
434
|
# Default configuration
|
|
438
|
-
defaults = {
|
|
439
|
-
"enabled": True,
|
|
440
|
-
"frequency": "daily",
|
|
441
|
-
"cache_ttl_hours": 24
|
|
442
|
-
}
|
|
435
|
+
defaults = {"enabled": True, "frequency": "daily", "cache_ttl_hours": 24}
|
|
443
436
|
|
|
444
437
|
# Find project root to ensure we read config from correct location
|
|
445
438
|
project_root = find_project_root(cwd)
|
|
@@ -461,21 +454,17 @@ def get_version_check_config(cwd: str) -> dict[str, Any]:
|
|
|
461
454
|
frequency = moai_config.get("update_check_frequency", defaults["frequency"])
|
|
462
455
|
|
|
463
456
|
# Validate frequency
|
|
464
|
-
if frequency not in
|
|
457
|
+
if frequency not in ttl_by_frequency:
|
|
465
458
|
frequency = defaults["frequency"]
|
|
466
459
|
|
|
467
460
|
# Calculate TTL from frequency
|
|
468
|
-
cache_ttl_hours =
|
|
461
|
+
cache_ttl_hours = ttl_by_frequency[frequency]
|
|
469
462
|
|
|
470
463
|
# Allow explicit cache_ttl_hours override
|
|
471
464
|
if "cache_ttl_hours" in version_check_config:
|
|
472
465
|
cache_ttl_hours = version_check_config["cache_ttl_hours"]
|
|
473
466
|
|
|
474
|
-
return {
|
|
475
|
-
"enabled": enabled,
|
|
476
|
-
"frequency": frequency,
|
|
477
|
-
"cache_ttl_hours": cache_ttl_hours
|
|
478
|
-
}
|
|
467
|
+
return {"enabled": enabled, "frequency": frequency, "cache_ttl_hours": cache_ttl_hours}
|
|
479
468
|
|
|
480
469
|
except (OSError, json.JSONDecodeError, KeyError):
|
|
481
470
|
# Config read or parse error - return defaults
|
|
@@ -619,13 +608,13 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
|
|
|
619
608
|
if spec and spec.loader:
|
|
620
609
|
version_cache_module = importlib.util.module_from_spec(spec)
|
|
621
610
|
spec.loader.exec_module(version_cache_module)
|
|
622
|
-
|
|
611
|
+
version_cache_class = version_cache_module.VersionCache
|
|
623
612
|
else:
|
|
624
613
|
# Skip caching if module can't be loaded
|
|
625
|
-
|
|
614
|
+
version_cache_class = None
|
|
626
615
|
except (ImportError, OSError):
|
|
627
616
|
# Graceful degradation: skip caching on import errors
|
|
628
|
-
|
|
617
|
+
version_cache_class = None
|
|
629
618
|
|
|
630
619
|
# 1. Find project root (ensure cache is always in correct location)
|
|
631
620
|
# This prevents creating .moai/cache in wrong locations when hooks run
|
|
@@ -634,7 +623,7 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
|
|
|
634
623
|
|
|
635
624
|
# 2. Initialize cache (skip if VersionCache couldn't be imported)
|
|
636
625
|
cache_dir = project_root / CACHE_DIR_NAME
|
|
637
|
-
version_cache =
|
|
626
|
+
version_cache = version_cache_class(cache_dir) if version_cache_class else None
|
|
638
627
|
|
|
639
628
|
# 2. Get current installed version first (needed for cache validation)
|
|
640
629
|
current_version = "unknown"
|
|
@@ -647,7 +636,7 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
|
|
|
647
636
|
"current": "dev",
|
|
648
637
|
"latest": "unknown",
|
|
649
638
|
"update_available": False,
|
|
650
|
-
"upgrade_command": ""
|
|
639
|
+
"upgrade_command": "",
|
|
651
640
|
}
|
|
652
641
|
|
|
653
642
|
# 3. Try to load from cache (fast path with version validation)
|
|
@@ -670,7 +659,7 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
|
|
|
670
659
|
"current": current_version,
|
|
671
660
|
"latest": "unknown",
|
|
672
661
|
"update_available": False,
|
|
673
|
-
"upgrade_command": ""
|
|
662
|
+
"upgrade_command": "",
|
|
674
663
|
}
|
|
675
664
|
|
|
676
665
|
# 5. Check if version check is enabled in config
|
|
@@ -701,7 +690,9 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
|
|
|
701
690
|
release_url = project_urls.get("Changelog", "")
|
|
702
691
|
if not release_url:
|
|
703
692
|
# Fallback to GitHub releases URL pattern
|
|
704
|
-
release_url =
|
|
693
|
+
release_url = (
|
|
694
|
+
f"https://github.com/modu-ai/moai-adk/releases/tag/v{result['latest']}"
|
|
695
|
+
)
|
|
705
696
|
result["release_notes_url"] = release_url
|
|
706
697
|
except (KeyError, AttributeError, TypeError):
|
|
707
698
|
result["release_notes_url"] = None
|
|
@@ -728,7 +719,9 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
|
|
|
728
719
|
result["upgrade_command"] = f"uv pip install --upgrade moai-adk>={result['latest']}"
|
|
729
720
|
|
|
730
721
|
# Detect major version change
|
|
731
|
-
result["is_major_update"] = is_major_version_change(
|
|
722
|
+
result["is_major_update"] = is_major_version_change(
|
|
723
|
+
result["current"], result["latest"]
|
|
724
|
+
)
|
|
732
725
|
else:
|
|
733
726
|
result["is_major_update"] = False
|
|
734
727
|
except (ValueError, AttributeError):
|
|
@@ -38,9 +38,22 @@ from pathlib import Path
|
|
|
38
38
|
from typing import Iterable, List, Optional
|
|
39
39
|
|
|
40
40
|
DEFAULT_CODE_EXTS = (
|
|
41
|
-
".py",
|
|
42
|
-
".
|
|
43
|
-
".
|
|
41
|
+
".py",
|
|
42
|
+
".ts",
|
|
43
|
+
".tsx",
|
|
44
|
+
".js",
|
|
45
|
+
".jsx",
|
|
46
|
+
".go",
|
|
47
|
+
".rs",
|
|
48
|
+
".java",
|
|
49
|
+
".kt",
|
|
50
|
+
".rb",
|
|
51
|
+
".php",
|
|
52
|
+
".c",
|
|
53
|
+
".cpp",
|
|
54
|
+
".cs",
|
|
55
|
+
".swift",
|
|
56
|
+
".scala",
|
|
44
57
|
)
|
|
45
58
|
|
|
46
59
|
|
|
@@ -88,35 +101,49 @@ def _load_rules(cwd: str) -> List[Rule]:
|
|
|
88
101
|
|
|
89
102
|
# Defaults (ordered)
|
|
90
103
|
return [
|
|
91
|
-
Rule(
|
|
92
|
-
include=[".moai/specs/**", "**/SPEC-*/spec.md"],
|
|
93
|
-
expect="@SPEC:",
|
|
94
|
-
exclude=[]
|
|
95
|
-
),
|
|
104
|
+
Rule(include=[".moai/specs/**", "**/SPEC-*/spec.md"], expect="@SPEC:", exclude=[]),
|
|
96
105
|
Rule(
|
|
97
106
|
include=[
|
|
98
|
-
"**/*_test.py",
|
|
99
|
-
"
|
|
100
|
-
"**/*.test.
|
|
101
|
-
"**/*.
|
|
107
|
+
"**/*_test.py",
|
|
108
|
+
"**/test_*.py",
|
|
109
|
+
"**/*.test.ts",
|
|
110
|
+
"**/*.test.tsx",
|
|
111
|
+
"**/*.test.js",
|
|
112
|
+
"**/*.test.jsx",
|
|
113
|
+
"**/*.test.go",
|
|
114
|
+
"**/*.test.rs",
|
|
115
|
+
"**/*.spec.ts",
|
|
116
|
+
"**/*.spec.tsx",
|
|
117
|
+
"tests/**",
|
|
102
118
|
],
|
|
103
119
|
expect="@TEST:",
|
|
104
|
-
exclude=[".claude/**"]
|
|
120
|
+
exclude=[".claude/**"],
|
|
105
121
|
),
|
|
106
122
|
Rule(
|
|
107
123
|
include=["docs/**/*.md", "**/README.md", "**/*.api.md"],
|
|
108
124
|
expect="@DOC:",
|
|
109
|
-
exclude=[".claude/**"]
|
|
125
|
+
exclude=[".claude/**"],
|
|
110
126
|
),
|
|
111
127
|
Rule(
|
|
112
128
|
include=["**/*"],
|
|
113
129
|
expect="@CODE:",
|
|
114
130
|
exclude=[
|
|
115
|
-
"tests/**",
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
|
|
131
|
+
"tests/**",
|
|
132
|
+
"docs/**",
|
|
133
|
+
".moai/**",
|
|
134
|
+
".claude/**",
|
|
135
|
+
"**/*.md",
|
|
136
|
+
"**/*.json",
|
|
137
|
+
"**/*.yml",
|
|
138
|
+
"**/*.yaml",
|
|
139
|
+
"**/*.toml",
|
|
140
|
+
"**/*.lock",
|
|
141
|
+
"**/*.svg",
|
|
142
|
+
"**/*.png",
|
|
143
|
+
"**/*.jpg",
|
|
144
|
+
"**/*.jpeg",
|
|
145
|
+
"**/*.gif",
|
|
146
|
+
],
|
|
120
147
|
),
|
|
121
148
|
]
|
|
122
149
|
|
|
@@ -147,17 +174,22 @@ def _iter_recent_changes(cwd: str) -> Iterable[Path]:
|
|
|
147
174
|
# Staged files
|
|
148
175
|
r1 = subprocess.run(
|
|
149
176
|
["git", "diff", "--name-only", "--cached"],
|
|
150
|
-
cwd=cwd,
|
|
177
|
+
cwd=cwd,
|
|
178
|
+
capture_output=True,
|
|
179
|
+
text=True,
|
|
180
|
+
timeout=1,
|
|
151
181
|
)
|
|
152
182
|
# Modified (unstaged) tracked files
|
|
153
183
|
r2 = subprocess.run(
|
|
154
|
-
["git", "ls-files", "-m"],
|
|
155
|
-
cwd=cwd, capture_output=True, text=True, timeout=1
|
|
184
|
+
["git", "ls-files", "-m"], cwd=cwd, capture_output=True, text=True, timeout=1
|
|
156
185
|
)
|
|
157
186
|
# Untracked (other) files respecting .gitignore
|
|
158
187
|
r3 = subprocess.run(
|
|
159
188
|
["git", "ls-files", "-o", "--exclude-standard"],
|
|
160
|
-
cwd=cwd,
|
|
189
|
+
cwd=cwd,
|
|
190
|
+
capture_output=True,
|
|
191
|
+
text=True,
|
|
192
|
+
timeout=1,
|
|
161
193
|
)
|
|
162
194
|
names = set()
|
|
163
195
|
if r1.returncode == 0:
|
|
@@ -86,7 +86,7 @@ class VersionCache:
|
|
|
86
86
|
return False
|
|
87
87
|
|
|
88
88
|
try:
|
|
89
|
-
with open(self.cache_file,
|
|
89
|
+
with open(self.cache_file, "r") as f:
|
|
90
90
|
data = json.load(f)
|
|
91
91
|
|
|
92
92
|
age_hours = self._calculate_age_hours(data["last_check"])
|
|
@@ -112,7 +112,7 @@ class VersionCache:
|
|
|
112
112
|
return None
|
|
113
113
|
|
|
114
114
|
try:
|
|
115
|
-
with open(self.cache_file,
|
|
115
|
+
with open(self.cache_file, "r") as f:
|
|
116
116
|
return json.load(f)
|
|
117
117
|
except (json.JSONDecodeError, OSError):
|
|
118
118
|
# Graceful degradation on read errors
|
|
@@ -144,7 +144,7 @@ class VersionCache:
|
|
|
144
144
|
version_info["last_check"] = datetime.now(timezone.utc).isoformat()
|
|
145
145
|
|
|
146
146
|
# Write to cache file
|
|
147
|
-
with open(self.cache_file,
|
|
147
|
+
with open(self.cache_file, "w") as f:
|
|
148
148
|
json.dump(version_info, f, indent=2)
|
|
149
149
|
|
|
150
150
|
return True
|
|
@@ -186,7 +186,7 @@ class VersionCache:
|
|
|
186
186
|
return 0.0
|
|
187
187
|
|
|
188
188
|
try:
|
|
189
|
-
with open(self.cache_file,
|
|
189
|
+
with open(self.cache_file, "r") as f:
|
|
190
190
|
data = json.load(f)
|
|
191
191
|
|
|
192
192
|
return self._calculate_age_hours(data["last_check"])
|