moai-adk 0.11.0__py3-none-any.whl → 0.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of moai-adk might be problematic. Click here for more details.

Files changed (268) hide show
  1. moai_adk/core/issue_creator.py +2 -2
  2. moai_adk/core/project/detector.py +195 -12
  3. moai_adk/core/tags/ci_validator.py +1 -1
  4. moai_adk/core/tags/pre_commit_validator.py +1 -1
  5. moai_adk/core/tags/validator.py +1 -1
  6. moai_adk/templates/.claude/agents/alfred/cc-manager.md +316 -0
  7. moai_adk/templates/.claude/agents/alfred/debug-helper.md +208 -0
  8. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +214 -0
  9. moai_adk/templates/.claude/agents/alfred/git-manager.md +406 -0
  10. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +350 -0
  11. moai_adk/templates/.claude/agents/alfred/project-manager.md +273 -0
  12. moai_adk/templates/.claude/agents/alfred/quality-gate.md +343 -0
  13. moai_adk/templates/.claude/agents/alfred/skill-factory.md +865 -0
  14. moai_adk/templates/.claude/agents/alfred/spec-builder.md +287 -0
  15. moai_adk/templates/.claude/agents/alfred/tag-agent.md +287 -0
  16. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +326 -0
  17. moai_adk/templates/.claude/agents/alfred/trust-checker.md +375 -0
  18. moai_adk/templates/.claude/commands/alfred/0-project.md +1275 -0
  19. moai_adk/templates/.claude/commands/alfred/1-plan.md +734 -0
  20. moai_adk/templates/.claude/commands/alfred/2-run.md +551 -0
  21. moai_adk/templates/.claude/commands/alfred/3-sync.md +689 -0
  22. moai_adk/templates/.claude/commands/alfred/9-feedback.md +149 -0
  23. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +206 -0
  24. moai_adk/templates/.claude/hooks/alfred/core/project.py +742 -0
  25. moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
  26. moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
  27. moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
  28. moai_adk/templates/.claude/hooks/alfred/notification__handle_events.py +95 -0
  29. moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +95 -0
  30. moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +101 -0
  31. moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +95 -0
  32. moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +97 -0
  33. moai_adk/templates/.claude/hooks/alfred/shared/core/__init__.py +170 -0
  34. moai_adk/templates/.claude/hooks/alfred/shared/core/checkpoint.py +271 -0
  35. moai_adk/templates/.claude/hooks/alfred/shared/core/context.py +67 -0
  36. moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +749 -0
  37. moai_adk/templates/.claude/hooks/alfred/shared/core/tags.py +230 -0
  38. moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
  39. moai_adk/templates/.claude/hooks/alfred/shared/handlers/__init__.py +21 -0
  40. moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +154 -0
  41. moai_adk/templates/.claude/hooks/alfred/shared/handlers/session.py +174 -0
  42. moai_adk/templates/.claude/hooks/alfred/shared/handlers/tool.py +87 -0
  43. moai_adk/templates/.claude/hooks/alfred/shared/handlers/user.py +61 -0
  44. moai_adk/templates/.claude/hooks/alfred/stop__handle_interrupt.py +95 -0
  45. moai_adk/templates/.claude/hooks/alfred/subagent_stop__handle_subagent_end.py +95 -0
  46. moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +111 -0
  47. moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
  48. moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +136 -0
  49. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +640 -0
  50. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +696 -0
  51. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +474 -0
  52. moai_adk/templates/.claude/settings.json +144 -0
  53. moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/SKILL.md +113 -0
  54. moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/examples.md +29 -0
  55. moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/reference.md +28 -0
  56. moai_adk/templates/.claude/skills/moai-alfred-git-workflow/SKILL.md +122 -0
  57. moai_adk/templates/.claude/skills/moai-alfred-git-workflow/examples.md +29 -0
  58. moai_adk/templates/.claude/skills/moai-alfred-git-workflow/reference.md +29 -0
  59. moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/SKILL.md +237 -0
  60. moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/examples.md +615 -0
  61. moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/reference.md +653 -0
  62. moai_adk/templates/.claude/skills/moai-alfred-language-detection/SKILL.md +113 -0
  63. moai_adk/templates/.claude/skills/moai-alfred-language-detection/examples.md +29 -0
  64. moai_adk/templates/.claude/skills/moai-alfred-language-detection/reference.md +28 -0
  65. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/SKILL.md +113 -0
  66. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/examples.md +29 -0
  67. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/reference.md +28 -0
  68. moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/SKILL.md +113 -0
  69. moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/examples.md +29 -0
  70. moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/reference.md +28 -0
  71. moai_adk/templates/.claude/skills/moai-alfred-trust-validation/SKILL.md +113 -0
  72. moai_adk/templates/.claude/skills/moai-alfred-trust-validation/examples.md +29 -0
  73. moai_adk/templates/.claude/skills/moai-alfred-trust-validation/reference.md +28 -0
  74. moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +269 -0
  75. moai_adk/templates/.claude/skills/moai-cc-agents/templates/agent-template.md +32 -0
  76. moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +298 -0
  77. moai_adk/templates/.claude/skills/moai-cc-claude-md/templates/CLAUDE-template.md +26 -0
  78. moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +307 -0
  79. moai_adk/templates/.claude/skills/moai-cc-commands/templates/command-template.md +21 -0
  80. moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +252 -0
  81. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/pre-bash-check.sh +19 -0
  82. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/preserve-permissions.sh +19 -0
  83. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/validate-bash-command.py +24 -0
  84. moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +199 -0
  85. moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/templates/settings-mcp-template.json +39 -0
  86. moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +316 -0
  87. moai_adk/templates/.claude/skills/moai-cc-memory/templates/session-summary-template.md +18 -0
  88. moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +263 -0
  89. moai_adk/templates/.claude/skills/moai-cc-settings/templates/settings-complete-template.json +30 -0
  90. moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +291 -0
  91. moai_adk/templates/.claude/skills/moai-cc-skills/templates/SKILL-template.md +15 -0
  92. moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +290 -0
  93. moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +1633 -0
  94. moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +660 -0
  95. moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +123 -0
  96. moai_adk/templates/.claude/skills/moai-domain-cli-tool/examples.md +29 -0
  97. moai_adk/templates/.claude/skills/moai-domain-cli-tool/reference.md +30 -0
  98. moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +123 -0
  99. moai_adk/templates/.claude/skills/moai-domain-data-science/examples.md +29 -0
  100. moai_adk/templates/.claude/skills/moai-domain-data-science/reference.md +30 -0
  101. moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +123 -0
  102. moai_adk/templates/.claude/skills/moai-domain-database/examples.md +29 -0
  103. moai_adk/templates/.claude/skills/moai-domain-database/reference.md +30 -0
  104. moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +124 -0
  105. moai_adk/templates/.claude/skills/moai-domain-devops/examples.md +29 -0
  106. moai_adk/templates/.claude/skills/moai-domain-devops/reference.md +31 -0
  107. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +124 -0
  108. moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +29 -0
  109. moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +31 -0
  110. moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +123 -0
  111. moai_adk/templates/.claude/skills/moai-domain-ml/examples.md +29 -0
  112. moai_adk/templates/.claude/skills/moai-domain-ml/reference.md +30 -0
  113. moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +123 -0
  114. moai_adk/templates/.claude/skills/moai-domain-mobile-app/examples.md +29 -0
  115. moai_adk/templates/.claude/skills/moai-domain-mobile-app/reference.md +30 -0
  116. moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +123 -0
  117. moai_adk/templates/.claude/skills/moai-domain-security/examples.md +29 -0
  118. moai_adk/templates/.claude/skills/moai-domain-security/reference.md +30 -0
  119. moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +123 -0
  120. moai_adk/templates/.claude/skills/moai-domain-web-api/examples.md +29 -0
  121. moai_adk/templates/.claude/skills/moai-domain-web-api/reference.md +30 -0
  122. moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +303 -0
  123. moai_adk/templates/.claude/skills/moai-essentials-debug/examples.md +1064 -0
  124. moai_adk/templates/.claude/skills/moai-essentials-debug/reference.md +1047 -0
  125. moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +113 -0
  126. moai_adk/templates/.claude/skills/moai-essentials-perf/examples.md +29 -0
  127. moai_adk/templates/.claude/skills/moai-essentials-perf/reference.md +28 -0
  128. moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +113 -0
  129. moai_adk/templates/.claude/skills/moai-essentials-refactor/examples.md +29 -0
  130. moai_adk/templates/.claude/skills/moai-essentials-refactor/reference.md +28 -0
  131. moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +113 -0
  132. moai_adk/templates/.claude/skills/moai-essentials-review/examples.md +29 -0
  133. moai_adk/templates/.claude/skills/moai-essentials-review/reference.md +28 -0
  134. moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +116 -0
  135. moai_adk/templates/.claude/skills/moai-foundation-ears/examples.md +29 -0
  136. moai_adk/templates/.claude/skills/moai-foundation-ears/reference.md +28 -0
  137. moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +122 -0
  138. moai_adk/templates/.claude/skills/moai-foundation-git/examples.md +29 -0
  139. moai_adk/templates/.claude/skills/moai-foundation-git/reference.md +29 -0
  140. moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +113 -0
  141. moai_adk/templates/.claude/skills/moai-foundation-langs/examples.md +29 -0
  142. moai_adk/templates/.claude/skills/moai-foundation-langs/reference.md +28 -0
  143. moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +113 -0
  144. moai_adk/templates/.claude/skills/moai-foundation-specs/examples.md +29 -0
  145. moai_adk/templates/.claude/skills/moai-foundation-specs/reference.md +28 -0
  146. moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +113 -0
  147. moai_adk/templates/.claude/skills/moai-foundation-tags/examples.md +29 -0
  148. moai_adk/templates/.claude/skills/moai-foundation-tags/reference.md +28 -0
  149. moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +307 -0
  150. moai_adk/templates/.claude/skills/moai-foundation-trust/examples.md +0 -0
  151. moai_adk/templates/.claude/skills/moai-foundation-trust/reference.md +1099 -0
  152. moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +124 -0
  153. moai_adk/templates/.claude/skills/moai-lang-c/examples.md +29 -0
  154. moai_adk/templates/.claude/skills/moai-lang-c/reference.md +31 -0
  155. moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +124 -0
  156. moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +29 -0
  157. moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +31 -0
  158. moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +123 -0
  159. moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +29 -0
  160. moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +30 -0
  161. moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +123 -0
  162. moai_adk/templates/.claude/skills/moai-lang-dart/examples.md +29 -0
  163. moai_adk/templates/.claude/skills/moai-lang-dart/reference.md +30 -0
  164. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +124 -0
  165. moai_adk/templates/.claude/skills/moai-lang-go/examples.md +29 -0
  166. moai_adk/templates/.claude/skills/moai-lang-go/reference.md +31 -0
  167. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +124 -0
  168. moai_adk/templates/.claude/skills/moai-lang-java/examples.md +29 -0
  169. moai_adk/templates/.claude/skills/moai-lang-java/reference.md +31 -0
  170. moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +125 -0
  171. moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +29 -0
  172. moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +32 -0
  173. moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +124 -0
  174. moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +29 -0
  175. moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +31 -0
  176. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +123 -0
  177. moai_adk/templates/.claude/skills/moai-lang-php/examples.md +29 -0
  178. moai_adk/templates/.claude/skills/moai-lang-php/reference.md +30 -0
  179. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +431 -0
  180. moai_adk/templates/.claude/skills/moai-lang-python/examples.md +624 -0
  181. moai_adk/templates/.claude/skills/moai-lang-python/reference.md +316 -0
  182. moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +123 -0
  183. moai_adk/templates/.claude/skills/moai-lang-r/examples.md +29 -0
  184. moai_adk/templates/.claude/skills/moai-lang-r/reference.md +30 -0
  185. moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +124 -0
  186. moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +29 -0
  187. moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +31 -0
  188. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +124 -0
  189. moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +29 -0
  190. moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +31 -0
  191. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +123 -0
  192. moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +29 -0
  193. moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +30 -0
  194. moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +123 -0
  195. moai_adk/templates/.claude/skills/moai-lang-shell/examples.md +29 -0
  196. moai_adk/templates/.claude/skills/moai-lang-shell/reference.md +30 -0
  197. moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +124 -0
  198. moai_adk/templates/.claude/skills/moai-lang-sql/examples.md +29 -0
  199. moai_adk/templates/.claude/skills/moai-lang-sql/reference.md +31 -0
  200. moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +123 -0
  201. moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +29 -0
  202. moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +30 -0
  203. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +127 -0
  204. moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +29 -0
  205. moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +34 -0
  206. moai_adk/templates/.claude/skills/moai-skill-factory/CHECKLIST.md +482 -0
  207. moai_adk/templates/.claude/skills/moai-skill-factory/EXAMPLES.md +278 -0
  208. moai_adk/templates/.claude/skills/moai-skill-factory/INTERACTIVE-DISCOVERY.md +524 -0
  209. moai_adk/templates/.claude/skills/moai-skill-factory/METADATA.md +477 -0
  210. moai_adk/templates/.claude/skills/moai-skill-factory/PARALLEL-ANALYSIS-REPORT.md +429 -0
  211. moai_adk/templates/.claude/skills/moai-skill-factory/PYTHON-VERSION-MATRIX.md +391 -0
  212. moai_adk/templates/.claude/skills/moai-skill-factory/SKILL-FACTORY-WORKFLOW.md +431 -0
  213. moai_adk/templates/.claude/skills/moai-skill-factory/SKILL-UPDATE-ADVISOR.md +577 -0
  214. moai_adk/templates/.claude/skills/moai-skill-factory/SKILL.md +271 -0
  215. moai_adk/templates/.claude/skills/moai-skill-factory/STEP-BY-STEP-GUIDE.md +466 -0
  216. moai_adk/templates/.claude/skills/moai-skill-factory/STRUCTURE.md +583 -0
  217. moai_adk/templates/.claude/skills/moai-skill-factory/WEB-RESEARCH.md +526 -0
  218. moai_adk/templates/.claude/skills/moai-skill-factory/reference.md +465 -0
  219. moai_adk/templates/.claude/skills/moai-skill-factory/scripts/generate-structure.sh +328 -0
  220. moai_adk/templates/.claude/skills/moai-skill-factory/scripts/validate-skill.sh +312 -0
  221. moai_adk/templates/.claude/skills/moai-skill-factory/templates/SKILL_TEMPLATE.md +245 -0
  222. moai_adk/templates/.claude/skills/moai-skill-factory/templates/examples-template.md +285 -0
  223. moai_adk/templates/.claude/skills/moai-skill-factory/templates/reference-template.md +278 -0
  224. moai_adk/templates/.claude/skills/moai-skill-factory/templates/scripts-template.sh +303 -0
  225. moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +137 -0
  226. moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +219 -0
  227. moai_adk/templates/.claude/skills/moai-spec-authoring/examples/validate-spec.sh +161 -0
  228. moai_adk/templates/.claude/skills/moai-spec-authoring/examples.md +541 -0
  229. moai_adk/templates/.claude/skills/moai-spec-authoring/reference.md +622 -0
  230. moai_adk/templates/.github/workflows/c-tag-validation.yml +83 -0
  231. moai_adk/templates/.github/workflows/cpp-tag-validation.yml +79 -0
  232. moai_adk/templates/.github/workflows/csharp-tag-validation.yml +65 -0
  233. moai_adk/templates/.github/workflows/dart-tag-validation.yml +82 -0
  234. moai_adk/templates/.github/workflows/java-tag-validation.yml +75 -0
  235. moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +67 -0
  236. moai_adk/templates/.github/workflows/php-tag-validation.yml +56 -0
  237. moai_adk/templates/.github/workflows/ruby-tag-validation.yml +68 -0
  238. moai_adk/templates/.github/workflows/rust-tag-validation.yml +73 -0
  239. moai_adk/templates/.github/workflows/shell-tag-validation.yml +65 -0
  240. moai_adk/templates/.github/workflows/swift-tag-validation.yml +79 -0
  241. moai_adk/templates/.moai/config.json +113 -0
  242. moai_adk/templates/.moai/memory/CLAUDE-AGENTS-GUIDE.md +208 -0
  243. moai_adk/templates/.moai/memory/CLAUDE-PRACTICES.md +369 -0
  244. moai_adk/templates/.moai/memory/CLAUDE-RULES.md +539 -0
  245. moai_adk/templates/.moai/memory/DEVELOPMENT-GUIDE.md +344 -0
  246. moai_adk/templates/.moai/memory/GITFLOW-PROTECTION-POLICY.md +330 -0
  247. moai_adk/templates/.moai/memory/ISSUE-LABEL-MAPPING.md +150 -0
  248. moai_adk/templates/.moai/memory/SKILLS-DESCRIPTION-POLICY.md +218 -0
  249. moai_adk/templates/.moai/memory/SPEC-METADATA.md +356 -0
  250. moai_adk/templates/.moai/memory/gitflow-protection-policy.md +330 -0
  251. moai_adk/templates/.moai/memory/spec-metadata.md +356 -0
  252. moai_adk/templates/.moai/project/product.md +161 -0
  253. moai_adk/templates/.moai/project/structure.md +156 -0
  254. moai_adk/templates/.moai/project/tech.md +227 -0
  255. moai_adk/templates/CLAUDE.md +90 -10
  256. moai_adk/templates/workflows/go-tag-validation.yml +130 -0
  257. moai_adk/templates/workflows/javascript-tag-validation.yml +135 -0
  258. moai_adk/templates/workflows/python-tag-validation.yml +118 -0
  259. moai_adk/templates/workflows/typescript-tag-validation.yml +154 -0
  260. {moai_adk-0.11.0.dist-info → moai_adk-0.12.0.dist-info}/METADATA +43 -1
  261. moai_adk-0.12.0.dist-info/RECORD +330 -0
  262. moai_adk-0.11.0.dist-info/RECORD +0 -77
  263. /moai_adk/templates/.github/workflows/{release.yml → moai-adk-release.yml} +0 -0
  264. /moai_adk/templates/.github/workflows/{spec-issue-sync.yml → moai-adk-spec-issue-sync.yml} +0 -0
  265. /moai_adk/templates/.github/workflows/{tag-validation.yml → moai-adk-tag-validation.yml} +0 -0
  266. {moai_adk-0.11.0.dist-info → moai_adk-0.12.0.dist-info}/WHEEL +0 -0
  267. {moai_adk-0.11.0.dist-info → moai_adk-0.12.0.dist-info}/entry_points.txt +0 -0
  268. {moai_adk-0.11.0.dist-info → moai_adk-0.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,742 @@
1
+ #!/usr/bin/env python3
2
+ """Project metadata utilities
3
+
4
+ Project information inquiry (language, Git, SPEC progress, etc.)
5
+ """
6
+
7
+ import json
8
+ import socket
9
+ import subprocess
10
+ from contextlib import contextmanager
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ # Cache directory for version check results
15
+ CACHE_DIR_NAME = ".moai/cache"
16
+
17
+
18
+ def find_project_root(start_path: str | Path = ".") -> Path:
19
+ """Find MoAI-ADK project root by searching upward for .moai/config.json
20
+
21
+ Traverses up the directory tree until it finds .moai/config.json or CLAUDE.md,
22
+ which indicates the project root. This ensures cache and other files are
23
+ always created in the correct location, regardless of where hooks execute.
24
+
25
+ Args:
26
+ start_path: Starting directory (default: current directory)
27
+
28
+ Returns:
29
+ Project root Path. If not found, returns start_path as absolute path.
30
+
31
+ Examples:
32
+ >>> find_project_root(".")
33
+ Path("/Users/user/my-project")
34
+ >>> find_project_root(".claude/hooks/alfred")
35
+ Path("/Users/user/my-project") # Found root 3 levels up
36
+
37
+ Notes:
38
+ - Searches for .moai/config.json first (most reliable)
39
+ - Falls back to CLAUDE.md if config.json not found
40
+ - Max depth: 10 levels up (prevent infinite loop)
41
+ - Returns absolute path for consistency
42
+
43
+ TDD History:
44
+ - RED: 4 test scenarios (root, nested, not found, symlinks)
45
+ - GREEN: Minimal upward search with .moai/config.json detection
46
+ - REFACTOR: Add CLAUDE.md fallback, max depth limit, absolute path return
47
+ """
48
+ current = Path(start_path).resolve()
49
+ max_depth = 10 # Prevent infinite loop
50
+
51
+ for _ in range(max_depth):
52
+ # Check for .moai/config.json (primary indicator)
53
+ if (current / ".moai" / "config.json").exists():
54
+ return current
55
+
56
+ # Check for CLAUDE.md (secondary indicator)
57
+ if (current / "CLAUDE.md").exists():
58
+ return current
59
+
60
+ # Move up one level
61
+ parent = current.parent
62
+ if parent == current: # Reached filesystem root
63
+ break
64
+ current = parent
65
+
66
+ # Not found - return start_path as absolute
67
+ return Path(start_path).resolve()
68
+
69
+
70
+ class TimeoutError(Exception):
71
+ """Signal-based timeout exception"""
72
+
73
+ pass
74
+
75
+
76
+ @contextmanager
77
+ def timeout_handler(seconds: int):
78
+ """Hard timeout using SIGALRM (works on Unix systems including macOS)
79
+
80
+ This uses kernel-level signal to interrupt ANY blocking operation,
81
+ even if subprocess.run() timeout fails on macOS.
82
+
83
+ Args:
84
+ seconds: Timeout duration in seconds
85
+
86
+ Raises:
87
+ TimeoutError: If operation exceeds timeout
88
+ """
89
+
90
+ def _handle_timeout(signum, frame):
91
+ raise TimeoutError(f"Operation timed out after {seconds} seconds")
92
+
93
+ # Set the signal handler
94
+ old_handler = signal.signal(signal.SIGALRM, _handle_timeout)
95
+ signal.alarm(seconds)
96
+ try:
97
+ yield
98
+ finally:
99
+ timeout.cancel() # Disable alarm
100
+ signal.signal(signal.SIGALRM, old_handler)
101
+
102
+
103
+ def detect_language(cwd: str) -> str:
104
+ """Detect project language (supports 20 items languages)
105
+
106
+ Browse the File system to detect your project's main development language.
107
+ First, check configuration files such as pyproject.toml and tsconfig.json.
108
+ Apply TypeScript first principles (if tsconfig.json exists).
109
+
110
+ Args:
111
+ cwd: Project root directory path (both absolute and relative paths are possible)
112
+
113
+ Returns:
114
+ Detected language name (lowercase). If detection fails, "Unknown Language" is returned.
115
+ Supported languages: python, typescript, javascript, java, go, rust,
116
+ dart, swift, kotlin, php, ruby, elixir, scala,
117
+ clojure, cpp, c, csharp, haskell, shell, lua
118
+
119
+ Examples:
120
+ >>> detect_language("/path/to/python/project")
121
+ 'python'
122
+ >>> detect_language("/path/to/typescript/project")
123
+ 'typescript'
124
+ >>> detect_language("/path/to/unknown/project")
125
+ 'Unknown Language'
126
+
127
+ TDD History:
128
+ - RED: Write a 21 items language detection test (20 items language + 1 items unknown)
129
+ - GREEN: 20 items language + unknown implementation, all tests passed
130
+ - REFACTOR: Optimize file inspection order, apply TypeScript priority principle
131
+ """
132
+ cwd_path = Path(cwd)
133
+
134
+ # Language detection mapping
135
+ language_files = {
136
+ "pyproject.toml": "python",
137
+ "tsconfig.json": "typescript",
138
+ "package.json": "javascript",
139
+ "pom.xml": "java",
140
+ "go.mod": "go",
141
+ "Cargo.toml": "rust",
142
+ "pubspec.yaml": "dart",
143
+ "Package.swift": "swift",
144
+ "build.gradle.kts": "kotlin",
145
+ "composer.json": "php",
146
+ "Gemfile": "ruby",
147
+ "mix.exs": "elixir",
148
+ "build.sbt": "scala",
149
+ "project.clj": "clojure",
150
+ "CMakeLists.txt": "cpp",
151
+ "Makefile": "c",
152
+ }
153
+
154
+ # Check standard language files
155
+ for file_name, language in language_files.items():
156
+ if (cwd_path / file_name).exists():
157
+ # Special handling for package.json - prefer typescript if tsconfig exists
158
+ if file_name == "package.json" and (cwd_path / "tsconfig.json").exists():
159
+ return "typescript"
160
+ return language
161
+
162
+ # Check for C# project files (*.csproj)
163
+ if any(cwd_path.glob("*.csproj")):
164
+ return "csharp"
165
+
166
+ # Check for Haskell project files (*.cabal)
167
+ if any(cwd_path.glob("*.cabal")):
168
+ return "haskell"
169
+
170
+ # Check for Shell scripts (*.sh)
171
+ if any(cwd_path.glob("*.sh")):
172
+ return "shell"
173
+
174
+ # Check for Lua files (*.lua)
175
+ if any(cwd_path.glob("*.lua")):
176
+ return "lua"
177
+
178
+ return "Unknown Language"
179
+
180
+
181
+ def _run_git_command(args: list[str], cwd: str, timeout: int = 2) -> str:
182
+ """Git command execution with HARD timeout protection
183
+
184
+ Safely execute Git commands and return output.
185
+ Uses SIGALRM (kernel-level interrupt) to handle macOS subprocess timeout bug.
186
+ Eliminates code duplication and provides consistent error handling.
187
+
188
+ Args:
189
+ args: Git command argument list (git adds automatically)
190
+ cwd: Execution directory path
191
+ timeout: Timeout (seconds, default 2 seconds)
192
+
193
+ Returns:
194
+ Git command output (stdout, removing leading and trailing spaces)
195
+
196
+ Raises:
197
+ subprocess.TimeoutExpired: Timeout exceeded (via TimeoutError)
198
+ subprocess.CalledProcessError: Git command failed
199
+
200
+ Examples:
201
+ >>> _run_git_command(["branch", "--show-current"], ".")
202
+ 'main'
203
+
204
+ TDD History:
205
+ - RED: Git command hang scenario test
206
+ - GREEN: SIGALRM-based timeout implementation
207
+ - REFACTOR: Exception conversion to subprocess.TimeoutExpired
208
+ """
209
+ try:
210
+ with timeout_handler(timeout):
211
+ result = subprocess.run(
212
+ ["git"] + args,
213
+ cwd=cwd,
214
+ capture_output=True,
215
+ text=True,
216
+ check=False, # Don't raise on non-zero exit - we'll check manually
217
+ )
218
+
219
+ # Check exit code manually
220
+ if result.returncode != 0:
221
+ raise subprocess.CalledProcessError(
222
+ result.returncode, ["git"] + args, result.stdout, result.stderr
223
+ )
224
+
225
+ return result.stdout.strip()
226
+
227
+ except TimeoutError:
228
+ # Convert to subprocess.TimeoutExpired for consistent error handling
229
+ raise subprocess.TimeoutExpired(["git"] + args, timeout)
230
+
231
+
232
+ def get_git_info(cwd: str) -> dict[str, Any]:
233
+ """Gather Git repository information
234
+
235
+ View the current status of a Git repository.
236
+ Returns the branch name, commit hash, number of changes, and last commit message.
237
+ If it is not a Git repository, it returns an empty dictionary.
238
+
239
+ Args:
240
+ cwd: Project root directory path
241
+
242
+ Returns:
243
+ Git information dictionary. Includes the following keys:
244
+ - branch: Current branch name (str)
245
+ - commit: Current commit hash (str, full hash)
246
+ - changes: Number of changed files (int, staged + unstaged)
247
+ - last_commit: Last commit message (str, subject only)
248
+
249
+ Empty dictionary {} if it is not a Git repository or the query fails.
250
+
251
+ Examples:
252
+ >>> get_git_info("/path/to/git/repo")
253
+ {'branch': 'main', 'commit': 'abc123...', 'changes': 3, 'last_commit': 'Fix bug'}
254
+ >>> get_git_info("/path/to/non-git")
255
+ {}
256
+
257
+ Notes:
258
+ - Timeout: 2 seconds for each Git command
259
+ - Security: Safe execution with subprocess.run(shell=False)
260
+ - Error handling: Returns an empty dictionary in case of all exceptions
261
+ - Commit message limited to 50 characters for display purposes
262
+
263
+ TDD History:
264
+ - RED: 3 items scenario test (Git repo, non-Git, error)
265
+ - GREEN: Implementation of subprocess-based Git command execution
266
+ - REFACTOR: Add timeout (2 seconds), strengthen exception handling, remove duplicates with helper function
267
+ - UPDATE: Added last_commit message field for SessionStart display
268
+ """
269
+ try:
270
+ # Check if it's a git repository
271
+ _run_git_command(["rev-parse", "--git-dir"], cwd)
272
+
273
+ # Get branch name, commit hash, and changes
274
+ branch = _run_git_command(["branch", "--show-current"], cwd)
275
+ commit = _run_git_command(["rev-parse", "HEAD"], cwd)
276
+ status_output = _run_git_command(["status", "--short"], cwd)
277
+ changes = len([line for line in status_output.splitlines() if line])
278
+
279
+ # Get last commit message (subject only, limited to 50 chars)
280
+ last_commit = _run_git_command(["log", "-1", "--format=%s"], cwd)
281
+ if len(last_commit) > 50:
282
+ last_commit = last_commit[:47] + "..."
283
+
284
+ return {
285
+ "branch": branch,
286
+ "commit": commit,
287
+ "changes": changes,
288
+ "last_commit": last_commit,
289
+ }
290
+
291
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError):
292
+ return {}
293
+
294
+
295
+ def count_specs(cwd: str) -> dict[str, int]:
296
+ """SPEC File count and progress calculation
297
+
298
+ Browse the .moai/specs/ directory to find the number of SPEC Files and
299
+ Counts the number of SPECs with status: completed.
300
+
301
+ Args:
302
+ cwd: Project root directory path (or any subdirectory, will search upward)
303
+
304
+ Returns:
305
+ SPEC progress dictionary. Includes the following keys:
306
+ - completed: Number of completed SPECs (int)
307
+ - total: total number of SPECs (int)
308
+ - percentage: completion percentage (int, 0~100)
309
+
310
+ All 0 if .moai/specs/ directory does not exist
311
+
312
+ Examples:
313
+ >>> count_specs("/path/to/project")
314
+ {'completed': 2, 'total': 5, 'percentage': 40}
315
+ >>> count_specs("/path/to/no-specs")
316
+ {'completed': 0, 'total': 0, 'percentage': 0}
317
+
318
+ Notes:
319
+ - SPEC File Location: .moai/specs/SPEC-{ID}/spec.md
320
+ - Completion condition: Include "status: completed" in YAML front matter
321
+ - If parsing fails, the SPEC is considered incomplete.
322
+ - Automatically finds project root to locate .moai/specs/
323
+
324
+ TDD History:
325
+ - RED: 5 items scenario test (0/0, 2/5, 5/5, no directory, parsing error)
326
+ - GREEN: SPEC search with Path.iterdir(), YAML parsing implementation
327
+ - REFACTOR: Strengthened exception handling, improved percentage calculation safety
328
+ - UPDATE: Add project root detection for consistent path resolution
329
+ """
330
+ # Find project root to ensure we read specs from correct location
331
+ project_root = find_project_root(cwd)
332
+ specs_dir = project_root / ".moai" / "specs"
333
+
334
+ if not specs_dir.exists():
335
+ return {"completed": 0, "total": 0, "percentage": 0}
336
+
337
+ completed = 0
338
+ total = 0
339
+
340
+ for spec_dir in specs_dir.iterdir():
341
+ if not spec_dir.is_dir() or not spec_dir.name.startswith("SPEC-"):
342
+ continue
343
+
344
+ spec_file = spec_dir / "spec.md"
345
+ if not spec_file.exists():
346
+ continue
347
+
348
+ total += 1
349
+
350
+ # Parse YAML front matter
351
+ try:
352
+ content = spec_file.read_text()
353
+ if content.startswith("---"):
354
+ yaml_end = content.find("---", 3)
355
+ if yaml_end > 0:
356
+ yaml_content = content[3:yaml_end]
357
+ if "status: completed" in yaml_content:
358
+ completed += 1
359
+ except (OSError, UnicodeDecodeError):
360
+ # File read failure or encoding error - considered incomplete
361
+ pass
362
+
363
+ percentage = int(completed / total * 100) if total > 0 else 0
364
+
365
+ return {
366
+ "completed": completed,
367
+ "total": total,
368
+ "percentage": percentage,
369
+ }
370
+
371
+
372
+ def get_project_language(cwd: str) -> str:
373
+ """Determine the primary project language (prefers config.json).
374
+
375
+ Args:
376
+ cwd: Project root directory (or any subdirectory, will search upward).
377
+
378
+ Returns:
379
+ Language string in lower-case.
380
+
381
+ Notes:
382
+ - Reads ``.moai/config.json`` first for a quick answer.
383
+ - Falls back to ``detect_language`` if configuration is missing.
384
+ - Automatically finds project root to locate .moai/config.json
385
+ """
386
+ # Find project root to ensure we read config from correct location
387
+ project_root = find_project_root(cwd)
388
+ config_path = project_root / ".moai" / "config.json"
389
+ if config_path.exists():
390
+ try:
391
+ config = json.loads(config_path.read_text())
392
+ lang = config.get("language", "")
393
+ if lang:
394
+ return lang
395
+ except (OSError, json.JSONDecodeError):
396
+ # Fall back to detection on parse errors
397
+ pass
398
+
399
+ # Fall back to the original language detection routine (use project root)
400
+ return detect_language(str(project_root))
401
+
402
+
403
+ # @CODE:CONFIG-INTEGRATION-001
404
+ def get_version_check_config(cwd: str) -> dict[str, Any]:
405
+ """Read version check configuration from .moai/config.json
406
+
407
+ Returns version check settings with sensible defaults.
408
+ Supports frequency-based cache TTL configuration.
409
+
410
+ Args:
411
+ cwd: Project root directory path
412
+
413
+ Returns:
414
+ dict with keys:
415
+ - "enabled": Boolean (default: True)
416
+ - "frequency": "always" | "daily" | "weekly" | "never" (default: "daily")
417
+ - "cache_ttl_hours": TTL in hours based on frequency
418
+
419
+ Frequency to TTL mapping:
420
+ - "always": 0 hours (no caching)
421
+ - "daily": 24 hours
422
+ - "weekly": 168 hours (7 days)
423
+ - "never": infinity (never check)
424
+
425
+ TDD History:
426
+ - RED: 8 test scenarios (defaults, custom, disabled, TTL, etc.)
427
+ - GREEN: Minimal config reading with defaults
428
+ - REFACTOR: Add validation and error handling
429
+ """
430
+ # TTL mapping by frequency
431
+ ttl_by_frequency = {"always": 0, "daily": 24, "weekly": 168, "never": float("inf")}
432
+
433
+ # Default configuration
434
+ defaults = {"enabled": True, "frequency": "daily", "cache_ttl_hours": 24}
435
+
436
+ # Find project root to ensure we read config from correct location
437
+ project_root = find_project_root(cwd)
438
+ config_path = project_root / ".moai" / "config.json"
439
+ if not config_path.exists():
440
+ return defaults
441
+
442
+ try:
443
+ config = json.loads(config_path.read_text())
444
+
445
+ # Extract moai.version_check section
446
+ moai_config = config.get("moai", {})
447
+ version_check_config = moai_config.get("version_check", {})
448
+
449
+ # Read enabled flag (default: True)
450
+ enabled = version_check_config.get("enabled", defaults["enabled"])
451
+
452
+ # Read frequency (default: "daily")
453
+ frequency = moai_config.get("update_check_frequency", defaults["frequency"])
454
+
455
+ # Validate frequency
456
+ if frequency not in ttl_by_frequency:
457
+ frequency = defaults["frequency"]
458
+
459
+ # Calculate TTL from frequency
460
+ cache_ttl_hours = ttl_by_frequency[frequency]
461
+
462
+ # Allow explicit cache_ttl_hours override
463
+ if "cache_ttl_hours" in version_check_config:
464
+ cache_ttl_hours = version_check_config["cache_ttl_hours"]
465
+
466
+ return {"enabled": enabled, "frequency": frequency, "cache_ttl_hours": cache_ttl_hours}
467
+
468
+ except (OSError, json.JSONDecodeError, KeyError):
469
+ # Config read or parse error - return defaults
470
+ return defaults
471
+
472
+
473
+ # @CODE:NETWORK-DETECT-001
474
+ def is_network_available(timeout_seconds: float = 0.1) -> bool:
475
+ """Quick network availability check using socket.
476
+
477
+ Does NOT check PyPI specifically, just basic connectivity.
478
+ Returns immediately on success (< 50ms typically).
479
+ Returns False on any error without raising exceptions.
480
+
481
+ Args:
482
+ timeout_seconds: Socket timeout in seconds (default 0.1s)
483
+
484
+ Returns:
485
+ True if network appears available, False otherwise
486
+
487
+ Examples:
488
+ >>> is_network_available()
489
+ True # Network is available
490
+ >>> is_network_available(timeout_seconds=0.001)
491
+ False # Timeout too short, returns False
492
+
493
+ TDD History:
494
+ - RED: 3 test scenarios (success, failure, timeout)
495
+ - GREEN: Minimal socket.create_connection implementation
496
+ - REFACTOR: Add error handling for all exception types
497
+ """
498
+ try:
499
+ # Try connecting to Google's public DNS server (8.8.8.8:53)
500
+ # This is a reliable host that's typically reachable
501
+ connection = socket.create_connection(("8.8.8.8", 53), timeout=timeout_seconds)
502
+ connection.close()
503
+ return True
504
+ except (socket.timeout, OSError, Exception):
505
+ # Any connection error means network is unavailable
506
+ # This includes: timeout, connection refused, network unreachable, etc.
507
+ return False
508
+
509
+
510
+ # @CODE:VERSION-DETECT-MAJOR-001
511
+ def is_major_version_change(current: str, latest: str) -> bool:
512
+ """Detect if version change is a major version bump.
513
+
514
+ A major version change is when the first (major) component increases:
515
+ - 0.8.1 → 1.0.0: True (0 → 1)
516
+ - 1.2.3 → 2.0.0: True (1 → 2)
517
+ - 0.8.1 → 0.9.0: False (0 → 0, minor changed)
518
+ - 1.2.3 → 1.3.0: False (1 → 1)
519
+
520
+ Args:
521
+ current: Current version string (e.g., "0.8.1")
522
+ latest: Latest version string (e.g., "1.0.0")
523
+
524
+ Returns:
525
+ True if major version increased, False otherwise
526
+
527
+ Examples:
528
+ >>> is_major_version_change("0.8.1", "1.0.0")
529
+ True
530
+ >>> is_major_version_change("0.8.1", "0.9.0")
531
+ False
532
+ >>> is_major_version_change("dev", "1.0.0")
533
+ False # Invalid versions return False
534
+
535
+ TDD History:
536
+ - RED: 4 test scenarios (0→1, 1→2, minor, invalid)
537
+ - GREEN: Minimal version parsing and comparison
538
+ - REFACTOR: Improve error handling for invalid versions
539
+ """
540
+ try:
541
+ # Parse version strings into integer components
542
+ current_parts = [int(x) for x in current.split(".")]
543
+ latest_parts = [int(x) for x in latest.split(".")]
544
+
545
+ # Compare major version (first component)
546
+ if len(current_parts) >= 1 and len(latest_parts) >= 1:
547
+ return latest_parts[0] > current_parts[0]
548
+
549
+ # If parsing succeeds but empty, no major change
550
+ return False
551
+
552
+ except (ValueError, AttributeError, IndexError):
553
+ # Invalid version format - return False (no exception)
554
+ return False
555
+
556
+
557
+ # @CODE:VERSION-CACHE-INTEGRATION-001
558
+ def get_package_version_info(cwd: str = ".") -> dict[str, Any]:
559
+ """Check MoAI-ADK current and latest version with caching and offline support.
560
+
561
+ Execution flow:
562
+ 1. Try to load from cache (< 50ms)
563
+ 2. If cache invalid, check network
564
+ 3. If network available, query PyPI
565
+ 4. If network unavailable, return current version only
566
+ 5. Save result to cache for next time
567
+
568
+ Args:
569
+ cwd: Project root directory (for cache location)
570
+
571
+ Returns:
572
+ dict with keys:
573
+ - "current": Current installed version
574
+ - "latest": Latest version available on PyPI
575
+ - "update_available": Boolean indicating if update is available
576
+ - "upgrade_command": Recommended upgrade command (if update available)
577
+ - "release_notes_url": URL to release notes (Phase 3)
578
+ - "is_major_update": Boolean indicating major version change (Phase 3)
579
+
580
+ Note:
581
+ - Cache hit (< 24 hours): Returns in ~20ms, no network access
582
+ - Cache miss + online: Query PyPI (1s timeout), cache result
583
+ - Cache miss + offline: Return current version only (~100ms)
584
+ - Offline + cached: Return from cache in ~20ms
585
+
586
+ TDD History:
587
+ - RED: 5 test scenarios (network detection, cache integration, offline mode)
588
+ - GREEN: Integrate VersionCache with network detection
589
+ - REFACTOR: Extract cache directory constant, improve error handling
590
+ - Phase 3: Add release_notes_url and is_major_update fields (@CODE:VERSION-INTEGRATE-FIELDS-001)
591
+ """
592
+ import importlib.util
593
+ import urllib.error
594
+ import urllib.request
595
+ from importlib.metadata import PackageNotFoundError, version
596
+
597
+ # Import VersionCache from the same directory (using dynamic import for testing compatibility)
598
+ try:
599
+ version_cache_path = Path(__file__).parent / "version_cache.py"
600
+ spec = importlib.util.spec_from_file_location("version_cache", version_cache_path)
601
+ if spec and spec.loader:
602
+ version_cache_module = importlib.util.module_from_spec(spec)
603
+ spec.loader.exec_module(version_cache_module)
604
+ version_cache_class = version_cache_module.VersionCache
605
+ else:
606
+ # Skip caching if module can't be loaded
607
+ version_cache_class = None
608
+ except (ImportError, OSError):
609
+ # Graceful degradation: skip caching on import errors
610
+ version_cache_class = None
611
+
612
+ # 1. Find project root (ensure cache is always in correct location)
613
+ # This prevents creating .moai/cache in wrong locations when hooks run
614
+ # from subdirectories like .claude/hooks/alfred/
615
+ project_root = find_project_root(cwd)
616
+
617
+ # 2. Initialize cache (skip if VersionCache couldn't be imported)
618
+ cache_dir = project_root / CACHE_DIR_NAME
619
+ version_cache = version_cache_class(cache_dir) if version_cache_class else None
620
+
621
+ # 2. Get current installed version first (needed for cache validation)
622
+ current_version = "unknown"
623
+ try:
624
+ current_version = version("moai-adk")
625
+ except PackageNotFoundError:
626
+ current_version = "dev"
627
+ # Dev mode - skip cache and return immediately
628
+ return {
629
+ "current": "dev",
630
+ "latest": "unknown",
631
+ "update_available": False,
632
+ "upgrade_command": "",
633
+ }
634
+
635
+ # 3. Try to load from cache (fast path with version validation)
636
+ if version_cache and version_cache.is_valid():
637
+ cached_info = version_cache.load()
638
+ if cached_info:
639
+ # Only use cache if the cached version matches current installed version
640
+ # This prevents stale cache when package is upgraded locally
641
+ if cached_info.get("current") == current_version:
642
+ # Ensure new fields exist for backward compatibility
643
+ if "release_notes_url" not in cached_info:
644
+ # Add missing fields to old cached data
645
+ cached_info.setdefault("release_notes_url", None)
646
+ cached_info.setdefault("is_major_update", False)
647
+ return cached_info
648
+ # else: cache is stale (version changed), fall through to re-check
649
+
650
+ # 4. Cache miss or stale - need to query PyPI
651
+ result = {
652
+ "current": current_version,
653
+ "latest": "unknown",
654
+ "update_available": False,
655
+ "upgrade_command": "",
656
+ }
657
+
658
+ # 5. Check if version check is enabled in config
659
+ config = get_version_check_config(cwd)
660
+ if not config["enabled"]:
661
+ # Version check disabled - return only current version
662
+ return result
663
+
664
+ # 6. Check network before PyPI query
665
+ if not is_network_available():
666
+ # Offline mode - return current version only
667
+ return result
668
+
669
+ # 7. Network available - query PyPI
670
+ pypi_data = None
671
+ try:
672
+ with timeout_handler(1):
673
+ url = "https://pypi.org/pypi/moai-adk/json"
674
+ headers = {"Accept": "application/json"}
675
+ req = urllib.request.Request(url, headers=headers)
676
+ with urllib.request.urlopen(req, timeout=0.8) as response:
677
+ pypi_data = json.load(response)
678
+ result["latest"] = pypi_data.get("info", {}).get("version", "unknown")
679
+
680
+ # Extract release notes URL from project_urls
681
+ try:
682
+ project_urls = pypi_data.get("info", {}).get("project_urls", {})
683
+ release_url = project_urls.get("Changelog", "")
684
+ if not release_url:
685
+ # Fallback to GitHub releases URL pattern
686
+ release_url = (
687
+ f"https://github.com/modu-ai/moai-adk/releases/tag/v{result['latest']}"
688
+ )
689
+ result["release_notes_url"] = release_url
690
+ except (KeyError, AttributeError, TypeError):
691
+ result["release_notes_url"] = None
692
+
693
+ except (urllib.error.URLError, TimeoutError, Exception):
694
+ # PyPI query failed - return current version
695
+ result["release_notes_url"] = None
696
+ pass
697
+
698
+ # 7. Compare versions (simple comparison)
699
+ if result["current"] != "unknown" and result["latest"] != "unknown":
700
+ try:
701
+ # Parse versions for comparison
702
+ current_parts = [int(x) for x in result["current"].split(".")]
703
+ latest_parts = [int(x) for x in result["latest"].split(".")]
704
+
705
+ # Pad shorter version with zeros
706
+ max_len = max(len(current_parts), len(latest_parts))
707
+ current_parts.extend([0] * (max_len - len(current_parts)))
708
+ latest_parts.extend([0] * (max_len - len(latest_parts)))
709
+
710
+ if latest_parts > current_parts:
711
+ result["update_available"] = True
712
+ result["upgrade_command"] = f"uv pip install --upgrade moai-adk>={result['latest']}"
713
+
714
+ # Detect major version change
715
+ result["is_major_update"] = is_major_version_change(
716
+ result["current"], result["latest"]
717
+ )
718
+ else:
719
+ result["is_major_update"] = False
720
+ except (ValueError, AttributeError):
721
+ # Version parsing failed - skip comparison
722
+ result["is_major_update"] = False
723
+ pass
724
+
725
+ # 8. Save result to cache (if caching is available)
726
+ if version_cache:
727
+ version_cache.save(result)
728
+
729
+ return result
730
+
731
+
732
+ __all__ = [
733
+ "find_project_root",
734
+ "detect_language",
735
+ "get_git_info",
736
+ "count_specs",
737
+ "get_project_language",
738
+ "get_version_check_config",
739
+ "is_network_available",
740
+ "is_major_version_change",
741
+ "get_package_version_info",
742
+ ]