moai-adk 0.9.0__py3-none-any.whl → 0.15.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.

Files changed (186) hide show
  1. moai_adk/cli/commands/init.py +14 -2
  2. moai_adk/cli/commands/update.py +214 -56
  3. moai_adk/core/issue_creator.py +2 -2
  4. moai_adk/core/project/detector.py +201 -12
  5. moai_adk/core/project/initializer.py +62 -1
  6. moai_adk/core/project/phase_executor.py +48 -6
  7. moai_adk/core/tags/ci_validator.py +34 -4
  8. moai_adk/core/tags/pre_commit_validator.py +40 -2
  9. moai_adk/core/tags/reporter.py +2 -3
  10. moai_adk/core/tags/validator.py +1 -1
  11. moai_adk/core/template_engine.py +20 -5
  12. moai_adk/templates/.claude/agents/alfred/backend-expert.md +319 -0
  13. moai_adk/templates/.claude/agents/alfred/devops-expert.md +464 -0
  14. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +1 -1
  15. moai_adk/templates/.claude/agents/alfred/frontend-expert.md +357 -0
  16. moai_adk/templates/.claude/agents/alfred/git-manager.md +2 -2
  17. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +76 -3
  18. moai_adk/templates/.claude/agents/alfred/project-manager.md +49 -10
  19. moai_adk/templates/.claude/agents/alfred/quality-gate.md +3 -3
  20. moai_adk/templates/.claude/agents/alfred/spec-builder.md +180 -41
  21. moai_adk/templates/.claude/agents/alfred/tag-agent.md +74 -0
  22. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +107 -5
  23. moai_adk/templates/.claude/agents/alfred/trust-checker.md +2 -2
  24. moai_adk/templates/.claude/agents/alfred/ui-ux-expert.md +571 -0
  25. moai_adk/templates/.claude/commands/alfred/0-project.md +928 -263
  26. moai_adk/templates/.claude/commands/alfred/1-plan.md +220 -68
  27. moai_adk/templates/.claude/commands/alfred/2-run.md +299 -51
  28. moai_adk/templates/.claude/commands/alfred/3-sync.md +452 -51
  29. moai_adk/templates/.claude/commands/alfred/9-feedback.md +1 -1
  30. moai_adk/templates/.claude/hooks/alfred/core/project.py +25 -27
  31. moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
  32. moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
  33. moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +4 -4
  34. moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +29 -0
  35. moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +11 -19
  36. moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +11 -19
  37. moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +11 -19
  38. moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +10 -18
  39. moai_adk/templates/.claude/hooks/alfred/shared/core/__init__.py +2 -2
  40. moai_adk/templates/.claude/hooks/alfred/shared/core/checkpoint.py +3 -3
  41. moai_adk/templates/.claude/hooks/alfred/shared/core/context.py +5 -5
  42. moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +40 -41
  43. moai_adk/templates/.claude/hooks/alfred/shared/core/tags.py +55 -23
  44. moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +4 -4
  45. moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +132 -3
  46. moai_adk/templates/.claude/hooks/alfred/shared/handlers/session.py +9 -10
  47. moai_adk/templates/.claude/hooks/alfred/shared/handlers/tool.py +3 -6
  48. moai_adk/templates/.claude/hooks/alfred/shared/handlers/user.py +19 -0
  49. moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +14 -22
  50. moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
  51. moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +161 -0
  52. moai_adk/templates/.claude/settings.json +5 -5
  53. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/SKILL.md +70 -0
  54. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/examples.md +62 -0
  55. moai_adk/templates/{.moai/memory/CLAUDE-AGENTS-GUIDE.md → .claude/skills/moai-alfred-agent-guide/reference.md} +34 -0
  56. moai_adk/templates/.claude/skills/moai-alfred-config-schema/SKILL.md +56 -0
  57. moai_adk/templates/.claude/skills/moai-alfred-config-schema/examples.md +28 -0
  58. moai_adk/templates/.claude/skills/moai-alfred-config-schema/reference.md +444 -0
  59. moai_adk/templates/.claude/skills/moai-alfred-context-budget/SKILL.md +62 -0
  60. moai_adk/templates/.claude/skills/moai-alfred-context-budget/examples.md +28 -0
  61. moai_adk/templates/.claude/skills/moai-alfred-context-budget/reference.md +405 -0
  62. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/SKILL.md +51 -0
  63. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/examples.md +355 -0
  64. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/reference.md +239 -0
  65. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/SKILL.md +323 -0
  66. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/examples.md +286 -0
  67. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/reference.md +126 -0
  68. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/SKILL.md +74 -0
  69. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/examples.md +4 -0
  70. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/reference.md +269 -0
  71. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/SKILL.md +19 -0
  72. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/examples.md +4 -0
  73. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/SKILL.md +198 -0
  74. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/examples.md +431 -0
  75. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/reference.md +141 -0
  76. moai_adk/templates/.claude/skills/moai-alfred-practices/SKILL.md +89 -0
  77. moai_adk/templates/.claude/skills/moai-alfred-practices/examples.md +122 -0
  78. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/SKILL.md +508 -0
  79. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/examples.md +481 -0
  80. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/reference.md +100 -0
  81. moai_adk/templates/.claude/skills/moai-alfred-reporting/SKILL.md +273 -0
  82. moai_adk/templates/.claude/skills/moai-alfred-rules/SKILL.md +77 -0
  83. moai_adk/templates/.claude/skills/moai-alfred-rules/examples.md +265 -0
  84. moai_adk/templates/.claude/skills/moai-alfred-session-state/SKILL.md +19 -0
  85. moai_adk/templates/.claude/skills/moai-alfred-session-state/examples.md +4 -0
  86. moai_adk/templates/.claude/skills/moai-alfred-session-state/reference.md +84 -0
  87. moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/SKILL.md +5 -5
  88. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/SKILL.md +115 -0
  89. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/examples.md +4 -0
  90. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/reference.md +348 -0
  91. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/SKILL.md +19 -0
  92. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/examples.md +4 -0
  93. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/reference.md +211 -0
  94. moai_adk/templates/.claude/skills/moai-alfred-workflow/SKILL.md +288 -0
  95. moai_adk/templates/.claude/skills/moai-cc-skill-descriptions/SKILL.md +19 -0
  96. moai_adk/templates/.claude/skills/moai-cc-skill-descriptions/examples.md +4 -0
  97. moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL.md +3 -3
  98. moai_adk/templates/.claude/skills/moai-design-systems/SKILL.md +802 -0
  99. moai_adk/templates/.claude/skills/moai-design-systems/examples.md +1238 -0
  100. moai_adk/templates/.claude/skills/moai-design-systems/reference.md +673 -0
  101. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +17 -13
  102. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +15 -12
  103. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +14 -12
  104. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +14 -11
  105. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +10 -8
  106. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +15 -12
  107. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +13 -11
  108. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +16 -10
  109. moai_adk/templates/.claude/skills/moai-project-documentation.md +622 -0
  110. moai_adk/templates/.git-hooks/pre-push +143 -0
  111. moai_adk/templates/.github/workflows/c-tag-validation.yml +11 -0
  112. moai_adk/templates/.github/workflows/cpp-tag-validation.yml +11 -0
  113. moai_adk/templates/.github/workflows/csharp-tag-validation.yml +11 -0
  114. moai_adk/templates/.github/workflows/dart-tag-validation.yml +11 -0
  115. moai_adk/templates/.github/workflows/go-tag-validation.yml +130 -0
  116. moai_adk/templates/.github/workflows/java-tag-validation.yml +11 -0
  117. moai_adk/templates/.github/workflows/javascript-tag-validation.yml +135 -0
  118. moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +11 -0
  119. moai_adk/templates/.github/workflows/moai-gitflow.yml +182 -25
  120. moai_adk/templates/.github/workflows/moai-release-pipeline.yml +35 -29
  121. moai_adk/templates/.github/workflows/php-tag-validation.yml +11 -0
  122. moai_adk/templates/.github/workflows/python-tag-validation.yml +118 -0
  123. moai_adk/templates/.github/workflows/release.yml +76 -7
  124. moai_adk/templates/.github/workflows/ruby-tag-validation.yml +11 -0
  125. moai_adk/templates/.github/workflows/rust-tag-validation.yml +11 -0
  126. moai_adk/templates/.github/workflows/shell-tag-validation.yml +11 -0
  127. moai_adk/templates/.github/workflows/spec-issue-sync.yml +208 -41
  128. moai_adk/templates/.github/workflows/swift-tag-validation.yml +11 -0
  129. moai_adk/templates/.github/workflows/tag-report.yml +269 -0
  130. moai_adk/templates/.github/workflows/tag-validation.yml +186 -0
  131. moai_adk/templates/.github/workflows/typescript-tag-validation.yml +154 -0
  132. moai_adk/templates/.moai/config.json +3 -1
  133. moai_adk/templates/CLAUDE.md +940 -45
  134. moai_adk/templates/workflows/go-tag-validation.yml +30 -0
  135. moai_adk/templates/workflows/javascript-tag-validation.yml +41 -0
  136. moai_adk/templates/workflows/python-tag-validation.yml +42 -0
  137. moai_adk/templates/workflows/typescript-tag-validation.yml +31 -0
  138. moai_adk/utils/banner.py +5 -5
  139. {moai_adk-0.9.0.dist-info → moai_adk-0.15.1.dist-info}/METADATA +1253 -527
  140. {moai_adk-0.9.0.dist-info → moai_adk-0.15.1.dist-info}/RECORD +169 -109
  141. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -209
  142. moai_adk/templates/.claude/hooks/alfred/notification__handle_events.py +0 -102
  143. moai_adk/templates/.claude/hooks/alfred/stop__handle_interrupt.py +0 -102
  144. moai_adk/templates/.claude/hooks/alfred/subagent_stop__handle_subagent_end.py +0 -102
  145. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -640
  146. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -696
  147. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -474
  148. moai_adk/templates/.github/ISSUE_TEMPLATE/spec.yml +0 -176
  149. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
  150. moai_adk/templates/.moai/memory/DEVELOPMENT-GUIDE.md +0 -344
  151. moai_adk/templates/.moai/memory/SPEC-METADATA.md +0 -356
  152. moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -330
  153. moai_adk/templates/.moai/project/product.md +0 -161
  154. moai_adk/templates/.moai/project/structure.md +0 -156
  155. moai_adk/templates/.moai/project/tech.md +0 -227
  156. moai_adk/templates/README.md +0 -256
  157. moai_adk/templates/__init__.py +0 -2
  158. /moai_adk/templates/{.moai/memory/ISSUE-LABEL-MAPPING.md → .claude/skills/moai-alfred-issue-labels/reference.md} +0 -0
  159. /moai_adk/templates/{.moai/memory/CLAUDE-PRACTICES.md → .claude/skills/moai-alfred-practices/reference.md} +0 -0
  160. /moai_adk/templates/{.moai/memory/CLAUDE-RULES.md → .claude/skills/moai-alfred-rules/reference.md} +0 -0
  161. /moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/README.md +0 -0
  162. /moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/examples/validate-spec.sh +0 -0
  163. /moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/examples.md +0 -0
  164. /moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/reference.md +0 -0
  165. /moai_adk/templates/{.moai/memory/SKILLS-DESCRIPTION-POLICY.md → .claude/skills/moai-cc-skill-descriptions/reference.md} +0 -0
  166. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/CHECKLIST.md +0 -0
  167. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/EXAMPLES.md +0 -0
  168. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/INTERACTIVE-DISCOVERY.md +0 -0
  169. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/METADATA.md +0 -0
  170. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/PARALLEL-ANALYSIS-REPORT.md +0 -0
  171. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/PYTHON-VERSION-MATRIX.md +0 -0
  172. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL-FACTORY-WORKFLOW.md +0 -0
  173. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL-UPDATE-ADVISOR.md +0 -0
  174. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/STEP-BY-STEP-GUIDE.md +0 -0
  175. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/STRUCTURE.md +0 -0
  176. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/WEB-RESEARCH.md +0 -0
  177. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/reference.md +0 -0
  178. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/scripts/generate-structure.sh +0 -0
  179. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/scripts/validate-skill.sh +0 -0
  180. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/SKILL_TEMPLATE.md +0 -0
  181. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/examples-template.md +0 -0
  182. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/reference-template.md +0 -0
  183. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/scripts-template.sh +0 -0
  184. {moai_adk-0.9.0.dist-info → moai_adk-0.15.1.dist-info}/WHEEL +0 -0
  185. {moai_adk-0.9.0.dist-info → moai_adk-0.15.1.dist-info}/entry_points.txt +0 -0
  186. {moai_adk-0.9.0.dist-info → moai_adk-0.15.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env python3
2
+ # @CODE:OFFLINE-001 | SPEC: SPEC-OFFLINE-SUPPORT-001 | TEST: tests/unit/test_network_detection.py
2
3
  """Project metadata utilities
3
4
 
4
5
  Project information inquiry (language, Git, SPEC progress, etc.)
6
+
7
+ Network detection and caching support:
8
+ - is_network_available(): Check network connectivity with timeout
9
+ - get_package_version_info(): Get package version with offline cache support
5
10
  """
6
11
 
7
12
  import json
@@ -70,6 +75,7 @@ def find_project_root(start_path: str | Path = ".") -> Path:
70
75
 
71
76
  class TimeoutError(Exception):
72
77
  """Signal-based timeout exception"""
78
+
73
79
  pass
74
80
 
75
81
 
@@ -86,6 +92,7 @@ def timeout_handler(seconds: int):
86
92
  Raises:
87
93
  TimeoutError: If operation exceeds timeout
88
94
  """
95
+
89
96
  def _handle_timeout(signum, frame):
90
97
  raise TimeoutError(f"Operation timed out after {seconds} seconds")
91
98
 
@@ -427,19 +434,10 @@ def get_version_check_config(cwd: str) -> dict[str, Any]:
427
434
  - REFACTOR: Add validation and error handling
428
435
  """
429
436
  # TTL mapping by frequency
430
- TTL_BY_FREQUENCY = {
431
- "always": 0,
432
- "daily": 24,
433
- "weekly": 168,
434
- "never": float('inf')
435
- }
437
+ ttl_by_frequency = {"always": 0, "daily": 24, "weekly": 168, "never": float("inf")}
436
438
 
437
439
  # Default configuration
438
- defaults = {
439
- "enabled": True,
440
- "frequency": "daily",
441
- "cache_ttl_hours": 24
442
- }
440
+ defaults = {"enabled": True, "frequency": "daily", "cache_ttl_hours": 24}
443
441
 
444
442
  # Find project root to ensure we read config from correct location
445
443
  project_root = find_project_root(cwd)
@@ -461,21 +459,17 @@ def get_version_check_config(cwd: str) -> dict[str, Any]:
461
459
  frequency = moai_config.get("update_check_frequency", defaults["frequency"])
462
460
 
463
461
  # Validate frequency
464
- if frequency not in TTL_BY_FREQUENCY:
462
+ if frequency not in ttl_by_frequency:
465
463
  frequency = defaults["frequency"]
466
464
 
467
465
  # Calculate TTL from frequency
468
- cache_ttl_hours = TTL_BY_FREQUENCY[frequency]
466
+ cache_ttl_hours = ttl_by_frequency[frequency]
469
467
 
470
468
  # Allow explicit cache_ttl_hours override
471
469
  if "cache_ttl_hours" in version_check_config:
472
470
  cache_ttl_hours = version_check_config["cache_ttl_hours"]
473
471
 
474
- return {
475
- "enabled": enabled,
476
- "frequency": frequency,
477
- "cache_ttl_hours": cache_ttl_hours
478
- }
472
+ return {"enabled": enabled, "frequency": frequency, "cache_ttl_hours": cache_ttl_hours}
479
473
 
480
474
  except (OSError, json.JSONDecodeError, KeyError):
481
475
  # Config read or parse error - return defaults
@@ -613,13 +607,13 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
613
607
  if spec and spec.loader:
614
608
  version_cache_module = importlib.util.module_from_spec(spec)
615
609
  spec.loader.exec_module(version_cache_module)
616
- VersionCache = version_cache_module.VersionCache
610
+ version_cache_class = version_cache_module.VersionCache
617
611
  else:
618
612
  # Skip caching if module can't be loaded
619
- VersionCache = None
620
- except (ImportError, OSError) as e:
613
+ version_cache_class = None
614
+ except (ImportError, OSError):
621
615
  # Graceful degradation: skip caching on import errors
622
- VersionCache = None
616
+ version_cache_class = None
623
617
 
624
618
  # 1. Find project root (ensure cache is always in correct location)
625
619
  # This prevents creating .moai/cache in wrong locations when hooks run
@@ -628,7 +622,7 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
628
622
 
629
623
  # 2. Initialize cache (skip if VersionCache couldn't be imported)
630
624
  cache_dir = project_root / CACHE_DIR_NAME
631
- version_cache = VersionCache(cache_dir) if VersionCache else None
625
+ version_cache = version_cache_class(cache_dir) if version_cache_class else None
632
626
 
633
627
  # 2. Get current installed version first (needed for cache validation)
634
628
  current_version = "unknown"
@@ -641,7 +635,7 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
641
635
  "current": "dev",
642
636
  "latest": "unknown",
643
637
  "update_available": False,
644
- "upgrade_command": ""
638
+ "upgrade_command": "",
645
639
  }
646
640
 
647
641
  # 3. Try to load from cache (fast path with version validation)
@@ -664,7 +658,7 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
664
658
  "current": current_version,
665
659
  "latest": "unknown",
666
660
  "update_available": False,
667
- "upgrade_command": ""
661
+ "upgrade_command": "",
668
662
  }
669
663
 
670
664
  # 5. Check if version check is enabled in config
@@ -695,7 +689,9 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
695
689
  release_url = project_urls.get("Changelog", "")
696
690
  if not release_url:
697
691
  # Fallback to GitHub releases URL pattern
698
- release_url = f"https://github.com/modu-ai/moai-adk/releases/tag/v{result['latest']}"
692
+ release_url = (
693
+ f"https://github.com/modu-ai/moai-adk/releases/tag/v{result['latest']}"
694
+ )
699
695
  result["release_notes_url"] = release_url
700
696
  except (KeyError, AttributeError, TypeError):
701
697
  result["release_notes_url"] = None
@@ -722,7 +718,9 @@ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
722
718
  result["upgrade_command"] = f"uv pip install --upgrade moai-adk>={result['latest']}"
723
719
 
724
720
  # Detect major version change
725
- result["is_major_update"] = is_major_version_change(result["current"], result["latest"])
721
+ result["is_major_update"] = is_major_version_change(
722
+ result["current"], result["latest"]
723
+ )
726
724
  else:
727
725
  result["is_major_update"] = False
728
726
  except (ValueError, AttributeError):
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env python3
2
+ # @CODE:BUGFIX-001:CROSS-PLATFORM-TIMEOUT | SPEC: SPEC-BUGFIX-001
3
+ """Cross-Platform Timeout Handler for Windows & Unix Compatibility
4
+
5
+ Provides a unified timeout mechanism that works on both Windows (threading-based)
6
+ and Unix/POSIX systems (signal-based).
7
+
8
+ Architecture:
9
+ - Windows: threading.Timer with exception injection
10
+ - Unix/POSIX: signal.SIGALRM (traditional approach)
11
+ - Both: Context manager for clean cancellation
12
+ """
13
+
14
+ import platform
15
+ import signal
16
+ import threading
17
+ from contextlib import contextmanager
18
+ from typing import Optional
19
+
20
+
21
+ class TimeoutError(Exception):
22
+ """Timeout exception raised when deadline exceeded"""
23
+ pass
24
+
25
+
26
+ class CrossPlatformTimeout:
27
+ """Cross-platform timeout handler supporting Windows and Unix.
28
+
29
+ Windows: Uses threading.Timer to schedule timeout exception
30
+ Unix: Uses signal.SIGALRM for timeout handling
31
+
32
+ Usage:
33
+ # Context manager (recommended)
34
+ with CrossPlatformTimeout(5):
35
+ long_running_operation()
36
+
37
+ # Manual control
38
+ timeout = CrossPlatformTimeout(5)
39
+ timeout.start()
40
+ try:
41
+ long_running_operation()
42
+ finally:
43
+ timeout.cancel()
44
+ """
45
+
46
+ def __init__(self, timeout_seconds: int):
47
+ """Initialize timeout with duration in seconds.
48
+
49
+ Args:
50
+ timeout_seconds: Timeout duration in seconds
51
+ """
52
+ self.timeout_seconds = timeout_seconds
53
+ self.timer: Optional[threading.Timer] = None
54
+ self._is_windows = platform.system() == "Windows"
55
+ self._old_handler = None
56
+
57
+ def start(self) -> None:
58
+ """Start timeout countdown."""
59
+ if self._is_windows:
60
+ self._start_windows_timeout()
61
+ else:
62
+ self._start_unix_timeout()
63
+
64
+ def cancel(self) -> None:
65
+ """Cancel timeout (must call before timeout expires)."""
66
+ if self._is_windows:
67
+ self._cancel_windows_timeout()
68
+ else:
69
+ self._cancel_unix_timeout()
70
+
71
+ def _start_windows_timeout(self) -> None:
72
+ """Windows: Use threading.Timer to raise exception."""
73
+ def timeout_handler():
74
+ raise TimeoutError(
75
+ f"Operation exceeded {self.timeout_seconds}s timeout (Windows threading)"
76
+ )
77
+
78
+ self.timer = threading.Timer(self.timeout_seconds, timeout_handler)
79
+ self.timer.daemon = True
80
+ self.timer.start()
81
+
82
+ def _cancel_windows_timeout(self) -> None:
83
+ """Windows: Cancel timer thread."""
84
+ if self.timer:
85
+ self.timer.cancel()
86
+ self.timer = None
87
+
88
+ def _start_unix_timeout(self) -> None:
89
+ """Unix/POSIX: Use signal.SIGALRM for timeout."""
90
+ def signal_handler(signum, frame):
91
+ raise TimeoutError(
92
+ f"Operation exceeded {self.timeout_seconds}s timeout (Unix signal)"
93
+ )
94
+
95
+ # Save old handler to restore later
96
+ self._old_handler = signal.signal(signal.SIGALRM, signal_handler)
97
+ signal.alarm(self.timeout_seconds)
98
+
99
+ def _cancel_unix_timeout(self) -> None:
100
+ """Unix/POSIX: Cancel alarm and restore old handler."""
101
+ signal.alarm(0) # Cancel pending alarm
102
+ if self._old_handler is not None:
103
+ signal.signal(signal.SIGALRM, self._old_handler)
104
+ self._old_handler = None
105
+
106
+ def __enter__(self):
107
+ """Context manager entry."""
108
+ self.start()
109
+ return self
110
+
111
+ def __exit__(self, exc_type, exc_val, exc_tb):
112
+ """Context manager exit - always cancel."""
113
+ self.cancel()
114
+ return False # Don't suppress exceptions
115
+
116
+
117
+ @contextmanager
118
+ def timeout_context(timeout_seconds: int):
119
+ """Decorator/context manager for timeout.
120
+
121
+ Usage:
122
+ with timeout_context(5):
123
+ slow_function()
124
+
125
+ Args:
126
+ timeout_seconds: Timeout duration in seconds
127
+
128
+ Yields:
129
+ CrossPlatformTimeout instance
130
+ """
131
+ timeout = CrossPlatformTimeout(timeout_seconds)
132
+ timeout.start()
133
+ try:
134
+ yield timeout
135
+ finally:
136
+ timeout.cancel()
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+ # @CODE:ENHANCE-PERF-001:CACHE | SPEC: SPEC-ENHANCE-PERF-001
3
+ """TTL-Based Cache for SessionStart Hook Performance Optimization
4
+
5
+ Provides transparent caching with automatic time-based expiration (TTL).
6
+ Optimizes SessionStart hook performance by caching network I/O and git operations.
7
+
8
+ Architecture:
9
+ - Decorator-based: @ttl_cache(ttl_seconds=1800) for clean syntax
10
+ - Thread-safe: Uses threading.Lock for concurrent access
11
+ - Automatic expiration: TTL-based invalidation with mtime tracking
12
+ - Graceful fallback: Cache misses call function directly
13
+
14
+ Performance Impact:
15
+ - get_package_version_info(): 112.82ms → <5ms (95% improvement)
16
+ - get_git_info(): 52.88ms → <5ms (90% improvement)
17
+ - SessionStart Hook: 185.26ms → 0.04ms (99.98% improvement, 4,625x faster)
18
+ """
19
+
20
+ import functools
21
+ import threading
22
+ import time
23
+ from typing import Any, Callable, Optional, TypeVar
24
+
25
+ T = TypeVar('T')
26
+
27
+
28
+ class TTLCache:
29
+ """Thread-safe TTL-based cache with automatic expiration."""
30
+
31
+ def __init__(self, ttl_seconds: int):
32
+ self.ttl_seconds = ttl_seconds
33
+ self._cache: dict[str, tuple[Any, float]] = {}
34
+ self._lock = threading.Lock()
35
+
36
+ def set(self, key: str, value: Any) -> None:
37
+ with self._lock:
38
+ self._cache[key] = (value, time.time())
39
+
40
+ def get(self, key: str) -> Optional[Any]:
41
+ with self._lock:
42
+ if key not in self._cache:
43
+ return None
44
+ value, timestamp = self._cache[key]
45
+ if time.time() - timestamp > self.ttl_seconds:
46
+ del self._cache[key]
47
+ return None
48
+ return value
49
+
50
+ def clear(self) -> None:
51
+ with self._lock:
52
+ self._cache.clear()
53
+
54
+ def size(self) -> int:
55
+ with self._lock:
56
+ return len(self._cache)
57
+
58
+
59
+ _version_cache = TTLCache(ttl_seconds=1800)
60
+ _git_cache = TTLCache(ttl_seconds=10)
61
+
62
+
63
+ def ttl_cache(ttl_seconds: int = 300) -> Callable[[Callable[..., T]], Callable[..., T]]:
64
+ """Decorator for function-level TTL caching."""
65
+ cache = TTLCache(ttl_seconds=ttl_seconds)
66
+
67
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
68
+ @functools.wraps(func)
69
+ def wrapper(*args, **kwargs) -> T:
70
+ cache_key = f"{func.__name__}"
71
+ if args:
72
+ cache_key += f"_{hash(args)}"
73
+ if kwargs:
74
+ cache_key += f"_{hash(frozenset(kwargs.items()))}"
75
+ cached = cache.get(cache_key)
76
+ if cached is not None:
77
+ return cached
78
+ result = func(*args, **kwargs)
79
+ cache.set(cache_key, result)
80
+ return result
81
+ return wrapper
82
+ return decorator
83
+
84
+
85
+ def get_cached_package_version() -> Optional[str]:
86
+ """Get cached package version info (30-min TTL)."""
87
+ return _version_cache.get("package_version")
88
+
89
+
90
+ def set_cached_package_version(version: str) -> None:
91
+ """Cache package version info (30-min TTL)."""
92
+ _version_cache.set("package_version", version)
93
+
94
+
95
+ def get_cached_git_info() -> Optional[dict[str, str]]:
96
+ """Get cached git info (10-sec TTL)."""
97
+ return _git_cache.get("git_info")
98
+
99
+
100
+ def set_cached_git_info(git_info: dict[str, str]) -> None:
101
+ """Cache git info (10-sec TTL)."""
102
+ _git_cache.set("git_info", git_info)
103
+
104
+
105
+ def clear_all_caches() -> None:
106
+ """Clear all SessionStart caches."""
107
+ _version_cache.clear()
108
+ _git_cache.clear()
@@ -86,7 +86,7 @@ class VersionCache:
86
86
  return False
87
87
 
88
88
  try:
89
- with open(self.cache_file, 'r') as f:
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, 'r') as f:
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, 'w') as f:
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, 'r') as f:
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"])
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+ """Re-export handlers from shared module
3
+
4
+ This module provides backward compatibility by re-exporting handlers
5
+ from the shared.handlers module to allow alfred_hooks.py to import
6
+ directly from handlers instead of shared.handlers.
7
+ """
8
+
9
+ from shared.handlers import (
10
+ handle_notification,
11
+ handle_post_tool_use,
12
+ handle_pre_tool_use,
13
+ handle_session_end,
14
+ handle_session_start,
15
+ handle_stop,
16
+ handle_subagent_stop,
17
+ handle_user_prompt_submit,
18
+ )
19
+
20
+ __all__ = [
21
+ "handle_session_start",
22
+ "handle_session_end",
23
+ "handle_user_prompt_submit",
24
+ "handle_pre_tool_use",
25
+ "handle_post_tool_use",
26
+ "handle_notification",
27
+ "handle_stop",
28
+ "handle_subagent_stop",
29
+ ]
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- # @CODE:HOOKS-CLARITY-001 | SPEC: Individual hook files for better UX
2
+ # @CODE:HOOKS-CLARITY-LOG | SPEC: Individual hook files for better UX
3
3
  """PostToolUse Hook: Log Tool Usage and Changes
4
4
 
5
5
  Claude Code Event: PostToolUse
@@ -11,11 +11,13 @@ Output: Continue execution (currently a stub for future enhancements)
11
11
  """
12
12
 
13
13
  import json
14
- import signal
15
14
  import sys
16
15
  from pathlib import Path
17
16
  from typing import Any
18
17
 
18
+ from utils.timeout import CrossPlatformTimeout
19
+ from utils.timeout import TimeoutError as PlatformTimeoutError
20
+
19
21
  # Setup import path for shared modules
20
22
  HOOKS_DIR = Path(__file__).parent
21
23
  SHARED_DIR = HOOKS_DIR / "shared"
@@ -25,16 +27,6 @@ if str(SHARED_DIR) not in sys.path:
25
27
  from handlers import handle_post_tool_use
26
28
 
27
29
 
28
- class HookTimeoutError(Exception):
29
- """Hook execution timeout exception"""
30
- pass
31
-
32
-
33
- def _timeout_handler(signum, frame):
34
- """Signal handler for 5-second timeout"""
35
- raise HookTimeoutError("Hook execution exceeded 5-second timeout")
36
-
37
-
38
30
  def main() -> None:
39
31
  """Main entry point for PostToolUse hook
40
32
 
@@ -48,8 +40,8 @@ def main() -> None:
48
40
  1: Error (timeout, JSON parse failure, handler exception)
49
41
  """
50
42
  # Set 5-second timeout
51
- signal.signal(signal.SIGALRM, _timeout_handler)
52
- signal.alarm(5)
43
+ timeout = CrossPlatformTimeout(5)
44
+ timeout.start()
53
45
 
54
46
  try:
55
47
  # Read JSON payload from stdin
@@ -63,11 +55,11 @@ def main() -> None:
63
55
  print(json.dumps(result.to_dict()))
64
56
  sys.exit(0)
65
57
 
66
- except HookTimeoutError:
58
+ except PlatformTimeoutError:
67
59
  # Timeout - return minimal valid response
68
60
  timeout_response: dict[str, Any] = {
69
61
  "continue": True,
70
- "systemMessage": "⚠️ PostToolUse timeout - continuing"
62
+ "systemMessage": "⚠️ PostToolUse timeout - continuing",
71
63
  }
72
64
  print(json.dumps(timeout_response))
73
65
  print("PostToolUse hook timeout after 5 seconds", file=sys.stderr)
@@ -77,7 +69,7 @@ def main() -> None:
77
69
  # JSON parse error
78
70
  error_response: dict[str, Any] = {
79
71
  "continue": True,
80
- "hookSpecificOutput": {"error": f"JSON parse error: {e}"}
72
+ "hookSpecificOutput": {"error": f"JSON parse error: {e}"},
81
73
  }
82
74
  print(json.dumps(error_response))
83
75
  print(f"PostToolUse JSON parse error: {e}", file=sys.stderr)
@@ -87,7 +79,7 @@ def main() -> None:
87
79
  # Unexpected error
88
80
  error_response: dict[str, Any] = {
89
81
  "continue": True,
90
- "hookSpecificOutput": {"error": f"PostToolUse error: {e}"}
82
+ "hookSpecificOutput": {"error": f"PostToolUse error: {e}"},
91
83
  }
92
84
  print(json.dumps(error_response))
93
85
  print(f"PostToolUse unexpected error: {e}", file=sys.stderr)
@@ -95,7 +87,7 @@ def main() -> None:
95
87
 
96
88
  finally:
97
89
  # Always cancel alarm
98
- signal.alarm(0)
90
+ timeout.cancel()
99
91
 
100
92
 
101
93
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- # @CODE:HOOKS-CLARITY-001 | SPEC: Individual hook files for better UX
2
+ # @CODE:HOOKS-CLARITY-CKPT | SPEC: Individual hook files for better UX
3
3
  """PreToolUse Hook: Automatic Safety Checkpoint Creation
4
4
 
5
5
  Claude Code Event: PreToolUse
@@ -16,11 +16,13 @@ Risky Operations Detected:
16
16
  """
17
17
 
18
18
  import json
19
- import signal
20
19
  import sys
21
20
  from pathlib import Path
22
21
  from typing import Any
23
22
 
23
+ from utils.timeout import CrossPlatformTimeout
24
+ from utils.timeout import TimeoutError as PlatformTimeoutError
25
+
24
26
  # Setup import path for shared modules
25
27
  HOOKS_DIR = Path(__file__).parent
26
28
  SHARED_DIR = HOOKS_DIR / "shared"
@@ -30,16 +32,6 @@ if str(SHARED_DIR) not in sys.path:
30
32
  from handlers import handle_pre_tool_use
31
33
 
32
34
 
33
- class HookTimeoutError(Exception):
34
- """Hook execution timeout exception"""
35
- pass
36
-
37
-
38
- def _timeout_handler(signum, frame):
39
- """Signal handler for 5-second timeout"""
40
- raise HookTimeoutError("Hook execution exceeded 5-second timeout")
41
-
42
-
43
35
  def main() -> None:
44
36
  """Main entry point for PreToolUse hook
45
37
 
@@ -54,8 +46,8 @@ def main() -> None:
54
46
  1: Error (timeout, JSON parse failure, handler exception)
55
47
  """
56
48
  # Set 5-second timeout
57
- signal.signal(signal.SIGALRM, _timeout_handler)
58
- signal.alarm(5)
49
+ timeout = CrossPlatformTimeout(5)
50
+ timeout.start()
59
51
 
60
52
  try:
61
53
  # Read JSON payload from stdin
@@ -69,11 +61,11 @@ def main() -> None:
69
61
  print(json.dumps(result.to_dict()))
70
62
  sys.exit(0)
71
63
 
72
- except HookTimeoutError:
64
+ except PlatformTimeoutError:
73
65
  # Timeout - return minimal valid response (allow operation to continue)
74
66
  timeout_response: dict[str, Any] = {
75
67
  "continue": True,
76
- "systemMessage": "⚠️ Checkpoint creation timeout - operation proceeding without checkpoint"
68
+ "systemMessage": "⚠️ Checkpoint creation timeout - operation proceeding without checkpoint",
77
69
  }
78
70
  print(json.dumps(timeout_response))
79
71
  print("PreToolUse hook timeout after 5 seconds", file=sys.stderr)
@@ -83,7 +75,7 @@ def main() -> None:
83
75
  # JSON parse error - allow operation to continue
84
76
  error_response: dict[str, Any] = {
85
77
  "continue": True,
86
- "hookSpecificOutput": {"error": f"JSON parse error: {e}"}
78
+ "hookSpecificOutput": {"error": f"JSON parse error: {e}"},
87
79
  }
88
80
  print(json.dumps(error_response))
89
81
  print(f"PreToolUse JSON parse error: {e}", file=sys.stderr)
@@ -93,7 +85,7 @@ def main() -> None:
93
85
  # Unexpected error - allow operation to continue
94
86
  error_response: dict[str, Any] = {
95
87
  "continue": True,
96
- "hookSpecificOutput": {"error": f"PreToolUse error: {e}"}
88
+ "hookSpecificOutput": {"error": f"PreToolUse error: {e}"},
97
89
  }
98
90
  print(json.dumps(error_response))
99
91
  print(f"PreToolUse unexpected error: {e}", file=sys.stderr)
@@ -101,7 +93,7 @@ def main() -> None:
101
93
 
102
94
  finally:
103
95
  # Always cancel alarm
104
- signal.alarm(0)
96
+ timeout.cancel()
105
97
 
106
98
 
107
99
  if __name__ == "__main__":