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