moai-adk 0.34.0__py3-none-any.whl → 1.1.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.
Files changed (524) hide show
  1. moai_adk/__main__.py +136 -5
  2. moai_adk/astgrep/__init__.py +37 -0
  3. moai_adk/astgrep/analyzer.py +522 -0
  4. moai_adk/astgrep/models.py +124 -0
  5. moai_adk/astgrep/rules.py +179 -0
  6. moai_adk/cli/commands/analyze.py +11 -2
  7. moai_adk/cli/commands/doctor.py +7 -1
  8. moai_adk/cli/commands/init.py +321 -11
  9. moai_adk/cli/commands/language.py +7 -1
  10. moai_adk/cli/commands/rank.py +449 -0
  11. moai_adk/cli/commands/status.py +7 -1
  12. moai_adk/cli/commands/switch.py +325 -0
  13. moai_adk/cli/commands/update.py +296 -23
  14. moai_adk/cli/prompts/init_prompts.py +362 -66
  15. moai_adk/cli/prompts/translations/__init__.py +573 -0
  16. moai_adk/cli/ui/prompts.py +61 -2
  17. moai_adk/cli/worktree/cli.py +106 -1
  18. moai_adk/cli/worktree/manager.py +155 -0
  19. moai_adk/core/config/unified.py +244 -63
  20. moai_adk/core/credentials.py +264 -0
  21. moai_adk/core/error_recovery_system.py +22 -4
  22. moai_adk/core/git/conflict_detector.py +10 -1
  23. moai_adk/core/git/event_detector.py +16 -5
  24. moai_adk/core/integration/engine.py +2 -2
  25. moai_adk/core/integration/integration_tester.py +5 -5
  26. moai_adk/core/language_config_resolver.py +9 -3
  27. moai_adk/core/merge/analyzer.py +509 -324
  28. moai_adk/core/migration/alfred_to_moai_migrator.py +7 -1
  29. moai_adk/core/migration/backup_manager.py +54 -4
  30. moai_adk/core/migration/file_migrator.py +174 -2
  31. moai_adk/core/migration/interactive_checkbox_ui.py +42 -31
  32. moai_adk/core/migration/version_detector.py +123 -19
  33. moai_adk/core/migration/version_migrator.py +44 -9
  34. moai_adk/core/model_allocator.py +241 -0
  35. moai_adk/core/project/backup_utils.py +12 -2
  36. moai_adk/core/project/initializer.py +44 -87
  37. moai_adk/core/project/phase_executor.py +95 -33
  38. moai_adk/core/project/validator.py +16 -1
  39. moai_adk/core/quality/trust_checker.py +30 -10
  40. moai_adk/core/rollback_manager.py +60 -25
  41. moai_adk/core/template/backup.py +88 -6
  42. moai_adk/core/template/config.py +33 -9
  43. moai_adk/core/template/merger.py +34 -8
  44. moai_adk/core/template/processor.py +334 -11
  45. moai_adk/core/template_engine.py +10 -1
  46. moai_adk/core/template_variable_synchronizer.py +16 -2
  47. moai_adk/core/version_sync.py +54 -6
  48. moai_adk/foundation/__init__.py +1 -20
  49. moai_adk/foundation/testing.py +1 -1
  50. moai_adk/loop/__init__.py +54 -0
  51. moai_adk/loop/controller.py +305 -0
  52. moai_adk/loop/feedback.py +230 -0
  53. moai_adk/loop/state.py +209 -0
  54. moai_adk/loop/storage.py +220 -0
  55. moai_adk/lsp/__init__.py +70 -0
  56. moai_adk/lsp/client.py +320 -0
  57. moai_adk/lsp/models.py +261 -0
  58. moai_adk/lsp/protocol.py +404 -0
  59. moai_adk/lsp/server_manager.py +248 -0
  60. moai_adk/project/configuration.py +8 -1
  61. moai_adk/py.typed +0 -0
  62. moai_adk/ralph/__init__.py +37 -0
  63. moai_adk/ralph/engine.py +307 -0
  64. moai_adk/rank/__init__.py +21 -0
  65. moai_adk/rank/auth.py +425 -0
  66. moai_adk/rank/client.py +557 -0
  67. moai_adk/rank/config.py +147 -0
  68. moai_adk/rank/hook.py +1503 -0
  69. moai_adk/rank/py.typed +0 -0
  70. moai_adk/statusline/__init__.py +3 -0
  71. moai_adk/statusline/enhanced_output_style_detector.py +5 -5
  72. moai_adk/statusline/main.py +20 -1
  73. moai_adk/statusline/memory_collector.py +268 -0
  74. moai_adk/statusline/renderer.py +54 -38
  75. moai_adk/tag_system/__init__.py +48 -0
  76. moai_adk/tag_system/atomic_ops.py +117 -0
  77. moai_adk/tag_system/linkage.py +335 -0
  78. moai_adk/tag_system/parser.py +176 -0
  79. moai_adk/tag_system/validator.py +200 -0
  80. moai_adk/templates/.claude/agents/moai/builder-agent.md +19 -3
  81. moai_adk/templates/.claude/agents/moai/builder-command.md +62 -16
  82. moai_adk/templates/.claude/agents/moai/builder-plugin.md +763 -0
  83. moai_adk/templates/.claude/agents/moai/builder-skill.md +21 -5
  84. moai_adk/templates/.claude/agents/moai/expert-backend.md +103 -39
  85. moai_adk/templates/.claude/agents/moai/expert-debug.md +9 -3
  86. moai_adk/templates/.claude/agents/moai/expert-devops.md +16 -14
  87. moai_adk/templates/.claude/agents/moai/expert-frontend.md +45 -31
  88. moai_adk/templates/.claude/agents/moai/expert-performance.md +13 -9
  89. moai_adk/templates/.claude/agents/moai/expert-refactoring.md +228 -0
  90. moai_adk/templates/.claude/agents/moai/expert-security.md +19 -3
  91. moai_adk/templates/.claude/agents/moai/expert-testing.md +13 -9
  92. moai_adk/templates/.claude/agents/moai/manager-claude-code.md +8 -2
  93. moai_adk/templates/.claude/agents/moai/manager-docs.md +10 -5
  94. moai_adk/templates/.claude/agents/moai/manager-git.md +99 -27
  95. moai_adk/templates/.claude/agents/moai/manager-project.md +87 -7
  96. moai_adk/templates/.claude/agents/moai/manager-quality.md +22 -5
  97. moai_adk/templates/.claude/agents/moai/manager-spec.md +8 -2
  98. moai_adk/templates/.claude/agents/moai/manager-strategy.md +45 -14
  99. moai_adk/templates/.claude/agents/moai/manager-tdd.md +16 -3
  100. moai_adk/templates/.claude/commands/moai/0-project.md +239 -1185
  101. moai_adk/templates/.claude/commands/moai/1-plan.md +383 -363
  102. moai_adk/templates/.claude/commands/moai/2-run.md +254 -347
  103. moai_adk/templates/.claude/commands/moai/3-sync.md +174 -100
  104. moai_adk/templates/.claude/commands/moai/9-feedback.md +49 -33
  105. moai_adk/templates/.claude/commands/moai/alfred.md +339 -0
  106. moai_adk/templates/.claude/commands/moai/cancel-loop.md +163 -0
  107. moai_adk/templates/.claude/commands/moai/fix.md +264 -0
  108. moai_adk/templates/.claude/commands/moai/loop.md +363 -0
  109. moai_adk/templates/.claude/hooks/moai/lib/README.md +143 -0
  110. moai_adk/templates/.claude/hooks/moai/lib/__init__.py +37 -81
  111. moai_adk/templates/.claude/hooks/moai/lib/alfred_detector.py +105 -0
  112. moai_adk/templates/.claude/hooks/moai/lib/atomic_write.py +122 -0
  113. moai_adk/templates/.claude/hooks/moai/lib/checkpoint.py +4 -1
  114. moai_adk/templates/.claude/hooks/moai/lib/common.py +35 -5
  115. moai_adk/templates/.claude/hooks/moai/lib/config.py +376 -0
  116. moai_adk/templates/.claude/hooks/moai/lib/config_manager.py +24 -28
  117. moai_adk/templates/.claude/hooks/moai/lib/config_validator.py +14 -14
  118. moai_adk/templates/.claude/hooks/moai/lib/enhanced_output_style_detector.py +372 -0
  119. moai_adk/templates/.claude/hooks/moai/lib/exceptions.py +171 -0
  120. moai_adk/templates/.claude/hooks/moai/lib/file_utils.py +95 -0
  121. moai_adk/templates/.claude/hooks/moai/lib/git_collector.py +190 -0
  122. moai_adk/templates/.claude/hooks/moai/lib/git_operations_manager.py +15 -13
  123. moai_adk/templates/.claude/hooks/moai/lib/language_detector.py +298 -0
  124. moai_adk/templates/.claude/hooks/moai/lib/language_validator.py +125 -25
  125. moai_adk/templates/.claude/hooks/moai/lib/main.py +341 -0
  126. moai_adk/templates/.claude/hooks/moai/lib/memory_collector.py +268 -0
  127. moai_adk/templates/.claude/hooks/moai/lib/metrics_tracker.py +78 -0
  128. moai_adk/templates/.claude/hooks/moai/lib/models.py +9 -7
  129. moai_adk/templates/.claude/hooks/moai/lib/path_utils.py +204 -13
  130. moai_adk/templates/.claude/hooks/moai/lib/project.py +23 -14
  131. moai_adk/templates/.claude/hooks/moai/lib/renderer.py +359 -0
  132. moai_adk/templates/.claude/hooks/moai/lib/tag_linkage.py +333 -0
  133. moai_adk/templates/.claude/hooks/moai/lib/tag_parser.py +176 -0
  134. moai_adk/templates/.claude/hooks/moai/lib/tag_validator.py +200 -0
  135. moai_adk/templates/.claude/hooks/moai/lib/timeout.py +5 -5
  136. moai_adk/templates/.claude/hooks/moai/lib/tool_registry.py +896 -0
  137. moai_adk/templates/.claude/hooks/moai/lib/unified_timeout_manager.py +30 -18
  138. moai_adk/templates/.claude/hooks/moai/lib/update_checker.py +129 -0
  139. moai_adk/templates/.claude/hooks/moai/lib/version_reader.py +741 -0
  140. moai_adk/templates/.claude/hooks/moai/post_tool__ast_grep_scan.py +276 -0
  141. moai_adk/templates/.claude/hooks/moai/post_tool__code_formatter.py +255 -0
  142. moai_adk/templates/.claude/hooks/moai/post_tool__coverage_guard.py +325 -0
  143. moai_adk/templates/.claude/hooks/moai/post_tool__linter.py +315 -0
  144. moai_adk/templates/.claude/hooks/moai/post_tool__lsp_diagnostic.py +508 -0
  145. moai_adk/templates/.claude/hooks/moai/pre_commit__tag_validator.py +287 -0
  146. moai_adk/templates/.claude/hooks/moai/pre_tool__security_guard.py +268 -0
  147. moai_adk/templates/.claude/hooks/moai/pre_tool__tdd_enforcer.py +208 -0
  148. moai_adk/templates/.claude/hooks/moai/session_end__auto_cleanup.py +93 -61
  149. moai_adk/templates/.claude/hooks/moai/session_end__rank_submit.py +69 -0
  150. moai_adk/templates/.claude/hooks/moai/session_start__show_project_info.py +165 -70
  151. moai_adk/templates/.claude/hooks/moai/shared/utils/announcement_translator.py +206 -0
  152. moai_adk/templates/.claude/hooks/moai/stop__loop_controller.py +621 -0
  153. moai_adk/templates/.claude/output-styles/moai/alfred.md +758 -0
  154. moai_adk/templates/.claude/output-styles/moai/r2d2.md +86 -3
  155. moai_adk/templates/.claude/output-styles/moai/yoda.md +2 -2
  156. moai_adk/templates/.claude/settings.json +154 -77
  157. moai_adk/templates/.claude/skills/moai-docs-generation/SKILL.md +252 -198
  158. moai_adk/templates/.claude/skills/moai-docs-generation/examples.md +169 -323
  159. moai_adk/templates/.claude/skills/moai-docs-generation/modules/README.md +39 -27
  160. moai_adk/templates/.claude/skills/moai-docs-generation/modules/api-documentation.md +115 -125
  161. moai_adk/templates/.claude/skills/moai-docs-generation/modules/code-documentation.md +150 -150
  162. moai_adk/templates/.claude/skills/moai-docs-generation/modules/multi-format-output.md +182 -175
  163. moai_adk/templates/.claude/skills/moai-docs-generation/modules/user-guides.md +198 -138
  164. moai_adk/templates/.claude/skills/moai-docs-generation/reference.md +226 -320
  165. moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +43 -222
  166. moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +75 -219
  167. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +103 -463
  168. moai_adk/templates/.claude/skills/moai-domain-frontend/modules/component-architecture.md +723 -0
  169. moai_adk/templates/.claude/skills/moai-domain-frontend/modules/nextjs16-patterns.md +713 -0
  170. moai_adk/templates/.claude/skills/moai-domain-frontend/modules/performance-optimization.md +694 -0
  171. moai_adk/templates/.claude/skills/moai-domain-frontend/modules/react19-patterns.md +591 -0
  172. moai_adk/templates/.claude/skills/moai-domain-frontend/modules/state-management.md +680 -0
  173. moai_adk/templates/.claude/skills/moai-domain-frontend/modules/vue35-patterns.md +802 -0
  174. moai_adk/templates/.claude/skills/moai-domain-uiux/SKILL.md +118 -339
  175. moai_adk/templates/.claude/skills/moai-formats-data/SKILL.md +74 -377
  176. moai_adk/templates/.claude/skills/moai-formats-data/modules/README.md +299 -70
  177. moai_adk/templates/.claude/skills/moai-foundation-claude/SKILL.md +205 -182
  178. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/advanced-agent-patterns.md +370 -0
  179. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-cli-reference-official.md +420 -0
  180. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-custom-slash-commands-official.md +32 -22
  181. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-devcontainers-official.md +381 -0
  182. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-discover-plugins-official.md +379 -0
  183. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-headless-official.md +378 -0
  184. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-hooks-official.md +110 -0
  185. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-plugin-marketplaces-official.md +308 -0
  186. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-plugins-official.md +640 -0
  187. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sandboxing-official.md +282 -0
  188. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-skills-official.md +425 -71
  189. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-statusline-official.md +293 -0
  190. moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sub-agents-official.md +325 -143
  191. moai_adk/templates/.claude/skills/moai-foundation-context/SKILL.md +96 -316
  192. moai_adk/templates/.claude/skills/moai-foundation-core/SKILL.md +116 -294
  193. moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-advanced.md +279 -0
  194. moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-implementation.md +267 -0
  195. moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-patterns.md +121 -650
  196. moai_adk/templates/.claude/skills/moai-foundation-core/modules/patterns.md +22 -0
  197. moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-ears-format.md +200 -0
  198. moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-first-tdd.md +37 -730
  199. moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-tdd-implementation.md +275 -0
  200. moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-framework.md +77 -819
  201. moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-implementation.md +244 -0
  202. moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-validation.md +219 -0
  203. moai_adk/templates/.claude/skills/moai-foundation-philosopher/SKILL.md +14 -18
  204. moai_adk/templates/.claude/skills/moai-foundation-quality/SKILL.md +86 -270
  205. moai_adk/templates/.claude/skills/moai-framework-electron/SKILL.md +288 -0
  206. moai_adk/templates/.claude/skills/moai-framework-electron/examples.md +2082 -0
  207. moai_adk/templates/.claude/skills/moai-framework-electron/reference.md +1649 -0
  208. moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +76 -582
  209. moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +1239 -0
  210. moai_adk/templates/.claude/skills/moai-lang-cpp/modules/advanced-patterns.md +401 -0
  211. moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +1136 -0
  212. moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +82 -436
  213. moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +585 -0
  214. moai_adk/templates/.claude/skills/moai-lang-csharp/modules/aspnet-core.md +627 -0
  215. moai_adk/templates/.claude/skills/moai-lang-csharp/modules/blazor-components.md +767 -0
  216. moai_adk/templates/.claude/skills/moai-lang-csharp/modules/cqrs-validation.md +626 -0
  217. moai_adk/templates/.claude/skills/moai-lang-csharp/modules/csharp12-features.md +580 -0
  218. moai_adk/templates/.claude/skills/moai-lang-csharp/modules/efcore-patterns.md +622 -0
  219. moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +403 -0
  220. moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +65 -542
  221. moai_adk/templates/.claude/skills/moai-lang-elixir/examples.md +1171 -0
  222. moai_adk/templates/.claude/skills/moai-lang-elixir/modules/advanced-patterns.md +531 -0
  223. moai_adk/templates/.claude/skills/moai-lang-elixir/reference.md +889 -0
  224. moai_adk/templates/.claude/skills/moai-lang-flutter/SKILL.md +32 -405
  225. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +114 -293
  226. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +83 -307
  227. moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +179 -0
  228. moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +973 -0
  229. moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +1543 -0
  230. moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +42 -279
  231. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +94 -556
  232. moai_adk/templates/.claude/skills/moai-lang-php/examples.md +1608 -0
  233. moai_adk/templates/.claude/skills/moai-lang-php/modules/advanced-patterns.md +538 -0
  234. moai_adk/templates/.claude/skills/moai-lang-php/reference.md +1323 -0
  235. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +108 -358
  236. moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +84 -482
  237. moai_adk/templates/.claude/skills/moai-lang-r/examples.md +1154 -0
  238. moai_adk/templates/.claude/skills/moai-lang-r/modules/advanced-patterns.md +489 -0
  239. moai_adk/templates/.claude/skills/moai-lang-r/reference.md +1087 -0
  240. moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +106 -610
  241. moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +1106 -0
  242. moai_adk/templates/.claude/skills/moai-lang-ruby/modules/advanced-patterns.md +309 -0
  243. moai_adk/templates/.claude/skills/moai-lang-ruby/modules/testing-patterns.md +306 -0
  244. moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +1024 -0
  245. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +51 -265
  246. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +106 -442
  247. moai_adk/templates/.claude/skills/moai-lang-scala/modules/akka-actors.md +479 -0
  248. moai_adk/templates/.claude/skills/moai-lang-scala/modules/cats-effect.md +489 -0
  249. moai_adk/templates/.claude/skills/moai-lang-scala/modules/functional-programming.md +460 -0
  250. moai_adk/templates/.claude/skills/moai-lang-scala/modules/spark-data.md +498 -0
  251. moai_adk/templates/.claude/skills/moai-lang-scala/modules/zio-patterns.md +541 -0
  252. moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +88 -457
  253. moai_adk/templates/.claude/skills/moai-lang-swift/modules/combine-reactive.md +256 -0
  254. moai_adk/templates/.claude/skills/moai-lang-swift/modules/concurrency.md +270 -0
  255. moai_adk/templates/.claude/skills/moai-lang-swift/modules/swift6-features.md +265 -0
  256. moai_adk/templates/.claude/skills/moai-lang-swift/modules/swiftui-patterns.md +314 -0
  257. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +75 -283
  258. moai_adk/templates/.claude/skills/moai-library-mermaid/SKILL.md +97 -252
  259. moai_adk/templates/.claude/skills/moai-library-nextra/SKILL.md +64 -240
  260. moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-patterns.md +331 -12
  261. moai_adk/templates/.claude/skills/moai-library-nextra/modules/configuration.md +330 -37
  262. moai_adk/templates/.claude/skills/moai-library-shadcn/SKILL.md +90 -287
  263. moai_adk/templates/.claude/skills/moai-platform-auth0/SKILL.md +200 -206
  264. moai_adk/templates/.claude/skills/moai-platform-auth0/examples.md +2446 -0
  265. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/adaptive-mfa.md +233 -0
  266. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/akamai-integration.md +214 -0
  267. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/application-credentials.md +280 -0
  268. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/attack-protection-log-events.md +224 -0
  269. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/attack-protection-overview.md +140 -0
  270. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/bot-detection.md +144 -0
  271. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/breached-password-detection.md +187 -0
  272. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/brute-force-protection.md +189 -0
  273. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/certifications.md +282 -0
  274. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/compliance-overview.md +263 -0
  275. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/continuous-session-protection.md +307 -0
  276. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/customize-mfa.md +177 -0
  277. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/dpop-implementation.md +283 -0
  278. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/fapi-implementation.md +259 -0
  279. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/gdpr-compliance.md +313 -0
  280. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/guardian-configuration.md +269 -0
  281. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/highly-regulated-identity.md +272 -0
  282. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/jwt-fundamentals.md +248 -0
  283. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mdl-verification.md +210 -0
  284. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mfa-api-management.md +278 -0
  285. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mfa-factors.md +226 -0
  286. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mfa-overview.md +174 -0
  287. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mtls-sender-constraining.md +316 -0
  288. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/ropg-flow-mfa.md +216 -0
  289. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/security-center.md +325 -0
  290. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/security-guidance.md +277 -0
  291. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/state-parameters.md +177 -0
  292. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/step-up-authentication.md +251 -0
  293. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/suspicious-ip-throttling.md +240 -0
  294. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/tenant-access-control.md +179 -0
  295. moai_adk/templates/.claude/skills/moai-platform-auth0/modules/webauthn-fido.md +235 -0
  296. moai_adk/templates/.claude/skills/moai-platform-auth0/reference.md +224 -0
  297. moai_adk/templates/.claude/skills/moai-platform-clerk/SKILL.md +75 -330
  298. moai_adk/templates/.claude/skills/moai-platform-clerk/examples.md +1426 -0
  299. moai_adk/templates/.claude/skills/moai-platform-clerk/modules/advanced-patterns.md +417 -0
  300. moai_adk/templates/.claude/skills/moai-platform-clerk/reference.md +273 -0
  301. moai_adk/templates/.claude/skills/moai-platform-convex/SKILL.md +100 -340
  302. moai_adk/templates/.claude/skills/moai-platform-convex/examples.md +506 -0
  303. moai_adk/templates/.claude/skills/moai-platform-convex/modules/auth-integration.md +421 -0
  304. moai_adk/templates/.claude/skills/moai-platform-convex/modules/file-storage.md +474 -0
  305. moai_adk/templates/.claude/skills/moai-platform-convex/modules/reactive-queries.md +302 -0
  306. moai_adk/templates/.claude/skills/moai-platform-convex/modules/server-functions.md +452 -0
  307. moai_adk/templates/.claude/skills/moai-platform-convex/reference.md +385 -0
  308. moai_adk/templates/.claude/skills/moai-platform-firebase-auth/SKILL.md +113 -326
  309. moai_adk/templates/.claude/skills/moai-platform-firebase-auth/examples.md +514 -0
  310. moai_adk/templates/.claude/skills/moai-platform-firebase-auth/modules/custom-claims.md +374 -0
  311. moai_adk/templates/.claude/skills/moai-platform-firebase-auth/modules/phone-auth.md +372 -0
  312. moai_adk/templates/.claude/skills/moai-platform-firebase-auth/modules/social-auth.md +339 -0
  313. moai_adk/templates/.claude/skills/moai-platform-firebase-auth/reference.md +382 -0
  314. moai_adk/templates/.claude/skills/moai-platform-firestore/SKILL.md +71 -302
  315. moai_adk/templates/.claude/skills/moai-platform-firestore/examples.md +445 -0
  316. moai_adk/templates/.claude/skills/moai-platform-firestore/modules/offline-cache.md +392 -0
  317. moai_adk/templates/.claude/skills/moai-platform-firestore/modules/realtime-listeners.md +441 -0
  318. moai_adk/templates/.claude/skills/moai-platform-firestore/modules/security-rules.md +352 -0
  319. moai_adk/templates/.claude/skills/moai-platform-firestore/modules/transactions.md +452 -0
  320. moai_adk/templates/.claude/skills/moai-platform-firestore/reference.md +322 -0
  321. moai_adk/templates/.claude/skills/moai-platform-neon/SKILL.md +101 -412
  322. moai_adk/templates/.claude/skills/moai-platform-neon/examples.md +470 -0
  323. moai_adk/templates/.claude/skills/moai-platform-neon/modules/auto-scaling.md +349 -0
  324. moai_adk/templates/.claude/skills/moai-platform-neon/modules/branching-workflows.md +354 -0
  325. moai_adk/templates/.claude/skills/moai-platform-neon/modules/connection-pooling.md +412 -0
  326. moai_adk/templates/.claude/skills/moai-platform-neon/modules/pitr-backups.md +458 -0
  327. moai_adk/templates/.claude/skills/moai-platform-neon/reference.md +272 -0
  328. moai_adk/templates/.claude/skills/moai-platform-railway/SKILL.md +96 -327
  329. moai_adk/templates/.claude/skills/moai-platform-railway/examples.md +539 -0
  330. moai_adk/templates/.claude/skills/moai-platform-railway/modules/docker-deployment.md +261 -0
  331. moai_adk/templates/.claude/skills/moai-platform-railway/modules/multi-service.md +291 -0
  332. moai_adk/templates/.claude/skills/moai-platform-railway/modules/networking-domains.md +338 -0
  333. moai_adk/templates/.claude/skills/moai-platform-railway/modules/volumes-storage.md +353 -0
  334. moai_adk/templates/.claude/skills/moai-platform-railway/reference.md +374 -0
  335. moai_adk/templates/.claude/skills/moai-platform-supabase/SKILL.md +103 -428
  336. moai_adk/templates/.claude/skills/moai-platform-supabase/examples.md +502 -0
  337. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/auth-integration.md +384 -0
  338. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/edge-functions.md +371 -0
  339. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/postgresql-pgvector.md +231 -0
  340. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/realtime-presence.md +354 -0
  341. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/row-level-security.md +286 -0
  342. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/storage-cdn.md +319 -0
  343. moai_adk/templates/.claude/skills/moai-platform-supabase/modules/typescript-patterns.md +453 -0
  344. moai_adk/templates/.claude/skills/moai-platform-supabase/reference.md +284 -0
  345. moai_adk/templates/.claude/skills/moai-platform-vercel/SKILL.md +96 -446
  346. moai_adk/templates/.claude/skills/moai-platform-vercel/examples.md +502 -0
  347. moai_adk/templates/.claude/skills/moai-platform-vercel/modules/analytics-speed.md +348 -0
  348. moai_adk/templates/.claude/skills/moai-platform-vercel/modules/deployment-config.md +344 -0
  349. moai_adk/templates/.claude/skills/moai-platform-vercel/modules/edge-functions.md +222 -0
  350. moai_adk/templates/.claude/skills/moai-platform-vercel/modules/isr-caching.md +306 -0
  351. moai_adk/templates/.claude/skills/moai-platform-vercel/modules/kv-storage.md +399 -0
  352. moai_adk/templates/.claude/skills/moai-platform-vercel/reference.md +360 -0
  353. moai_adk/templates/.claude/skills/moai-tool-ast-grep/SKILL.md +193 -0
  354. moai_adk/templates/.claude/skills/moai-tool-ast-grep/examples.md +1099 -0
  355. moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/language-specific.md +307 -0
  356. moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/pattern-syntax.md +237 -0
  357. moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/refactoring-patterns.md +260 -0
  358. moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/security-rules.md +239 -0
  359. moai_adk/templates/.claude/skills/moai-tool-ast-grep/reference.md +288 -0
  360. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/languages/go.yml +90 -0
  361. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/languages/python.yml +101 -0
  362. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/languages/typescript.yml +83 -0
  363. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/quality/complexity-check.yml +94 -0
  364. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/quality/deprecated-apis.yml +84 -0
  365. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/security/secrets-detection.yml +89 -0
  366. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/security/sql-injection.yml +45 -0
  367. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/security/xss-prevention.yml +50 -0
  368. moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/sgconfig.yml +54 -0
  369. moai_adk/templates/.claude/skills/moai-workflow-jit-docs/SKILL.md +225 -423
  370. moai_adk/templates/.claude/skills/moai-workflow-loop/SKILL.md +197 -0
  371. moai_adk/templates/.claude/skills/moai-workflow-loop/examples.md +1063 -0
  372. moai_adk/templates/.claude/skills/moai-workflow-loop/reference.md +1414 -0
  373. moai_adk/templates/.claude/skills/moai-workflow-project/SKILL.md +211 -314
  374. moai_adk/templates/.claude/skills/moai-workflow-project/schemas/tab_schema.json +15 -43
  375. moai_adk/templates/.claude/skills/moai-workflow-spec/SKILL.md +119 -316
  376. moai_adk/templates/.claude/skills/moai-workflow-spec/modules/advanced-patterns.md +237 -0
  377. moai_adk/templates/.claude/skills/moai-workflow-templates/SKILL.md +96 -203
  378. moai_adk/templates/.claude/skills/moai-workflow-testing/SKILL.md +201 -388
  379. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/README.md +52 -3
  380. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/ai-debugging.md +263 -806
  381. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/context7-integration.md +286 -0
  382. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/review-workflows.md +500 -0
  383. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/relevance-analysis.md +154 -0
  384. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/safety-analysis.md +148 -0
  385. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/scoring-algorithms.md +196 -0
  386. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/timeliness-analysis.md +168 -0
  387. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/truthfulness-analysis.md +136 -0
  388. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/usability-analysis.md +153 -0
  389. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework.md +257 -0
  390. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review.md +191 -1344
  391. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/code-review/analysis-patterns.md +340 -0
  392. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/code-review/core-classes.md +299 -0
  393. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/code-review/tool-integration.md +380 -0
  394. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/debugging/debugging-workflows.md +451 -0
  395. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/debugging/error-analysis.md +442 -0
  396. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance/optimization-patterns.md +473 -0
  397. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance/profiling-techniques.md +481 -0
  398. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/ai-optimization.md +241 -0
  399. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/bottleneck-detection.md +397 -0
  400. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/optimization-plan.md +315 -0
  401. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/profiler-core.md +277 -0
  402. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/real-time-monitoring.md +187 -0
  403. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization.md +287 -1194
  404. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/quality-metrics.md +415 -0
  405. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/refactoring/ai-workflows.md +620 -0
  406. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/refactoring/patterns.md +692 -0
  407. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/security-analysis.md +429 -0
  408. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/smart-refactoring.md +262 -1192
  409. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/static-analysis.md +438 -0
  410. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd/core-classes.md +397 -0
  411. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/advanced-features.md +494 -0
  412. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/red-green-refactor.md +316 -0
  413. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/test-generation.md +471 -0
  414. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/test-patterns.md +371 -0
  415. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7.md +227 -1222
  416. moai_adk/templates/.claude/skills/moai-workflow-testing/modules/trust5-validation.md +428 -0
  417. moai_adk/templates/.claude/skills/moai-workflow-worktree/SKILL.md +228 -0
  418. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/integration-patterns.md +149 -0
  419. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/moai-adk-integration.md +245 -0
  420. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/parallel-advanced.md +310 -0
  421. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/parallel-development.md +202 -0
  422. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/parallel-workflows.md +302 -0
  423. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/registry-architecture.md +271 -0
  424. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/resource-optimization.md +300 -0
  425. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/tools-integration.md +280 -0
  426. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/troubleshooting.md +397 -0
  427. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/worktree-commands.md +296 -0
  428. moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/worktree-management.md +217 -0
  429. moai_adk/templates/.git-hooks/pre-push +162 -59
  430. moai_adk/templates/.github/workflows/ci-universal.yml +934 -133
  431. moai_adk/templates/.gitignore +65 -107
  432. moai_adk/templates/.lsp.json +152 -0
  433. moai_adk/templates/.mcp.json +2 -20
  434. moai_adk/templates/.moai/announcements/en.json +18 -0
  435. moai_adk/templates/.moai/announcements/ja.json +18 -0
  436. moai_adk/templates/.moai/announcements/ko.json +18 -0
  437. moai_adk/templates/.moai/announcements/zh.json +18 -0
  438. moai_adk/templates/.moai/config/config.yaml +8 -2
  439. moai_adk/templates/.moai/config/multilingual-triggers.yaml +213 -0
  440. moai_adk/templates/.moai/config/sections/language.yaml +2 -2
  441. moai_adk/templates/.moai/config/sections/llm.yaml +41 -0
  442. moai_adk/templates/.moai/config/sections/pricing.yaml +30 -0
  443. moai_adk/templates/.moai/config/sections/project.yaml +2 -2
  444. moai_adk/templates/.moai/config/sections/quality.yaml +43 -5
  445. moai_adk/templates/.moai/config/sections/ralph.yaml +55 -0
  446. moai_adk/templates/.moai/config/sections/system.yaml +46 -1
  447. moai_adk/templates/.moai/config/sections/user.yaml +1 -1
  448. moai_adk/templates/.moai/config/statusline-config.yaml +2 -2
  449. moai_adk/templates/.moai/llm-configs/glm.json +22 -0
  450. moai_adk/templates/CLAUDE.ja.md +343 -0
  451. moai_adk/templates/CLAUDE.ko.md +343 -0
  452. moai_adk/templates/CLAUDE.md +200 -499
  453. moai_adk/templates/CLAUDE.zh.md +343 -0
  454. moai_adk/utils/common.py +37 -0
  455. moai_adk/version.py +1 -1
  456. moai_adk-1.1.0.dist-info/METADATA +2443 -0
  457. moai_adk-1.1.0.dist-info/RECORD +701 -0
  458. {moai_adk-0.34.0.dist-info → moai_adk-1.1.0.dist-info}/entry_points.txt +2 -0
  459. moai_adk-1.1.0.dist-info/licenses/LICENSE +99 -0
  460. moai_adk/core/config/auto_spec_config.py +0 -340
  461. moai_adk/core/hooks/post_tool_auto_spec_completion.py +0 -901
  462. moai_adk/core/spec/confidence_scoring.py +0 -680
  463. moai_adk/core/spec/ears_template_engine.py +0 -1247
  464. moai_adk/core/spec/quality_validator.py +0 -687
  465. moai_adk/templates/.claude/agents/moai/ai-nano-banana.md +0 -670
  466. moai_adk/templates/.claude/agents/moai/expert-database.md +0 -777
  467. moai_adk/templates/.claude/agents/moai/expert-uiux.md +0 -1041
  468. moai_adk/templates/.claude/agents/moai/mcp-context7.md +0 -458
  469. moai_adk/templates/.claude/agents/moai/mcp-figma.md +0 -1607
  470. moai_adk/templates/.claude/agents/moai/mcp-notion.md +0 -789
  471. moai_adk/templates/.claude/agents/moai/mcp-playwright.md +0 -469
  472. moai_adk/templates/.claude/agents/moai/mcp-sequential-thinking.md +0 -1032
  473. moai_adk/templates/.claude/skills/moai-ai-nano-banana/SKILL.md +0 -438
  474. moai_adk/templates/.claude/skills/moai-ai-nano-banana/examples.md +0 -431
  475. moai_adk/templates/.claude/skills/moai-domain-uiux/modules/design-system-tokens.md +0 -405
  476. moai_adk/templates/.claude/skills/moai-library-nextra/advanced-patterns.md +0 -336
  477. moai_adk/templates/.claude/skills/moai-mcp-figma/SKILL.md +0 -402
  478. moai_adk/templates/.claude/skills/moai-mcp-figma/advanced-patterns.md +0 -607
  479. moai_adk/templates/.claude/skills/moai-mcp-notion/SKILL.md +0 -300
  480. moai_adk/templates/.claude/skills/moai-mcp-notion/advanced-patterns.md +0 -537
  481. moai_adk/templates/.claude/skills/moai-workflow-project/__init__.py +0 -520
  482. moai_adk/templates/.claude/skills/moai-workflow-project/complete_workflow_demo_fixed.py +0 -574
  483. moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_project_setup.py +0 -317
  484. moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_workflow_demo.py +0 -663
  485. moai_adk/templates/.claude/skills/moai-workflow-project/examples/config-migration-example.json +0 -190
  486. moai_adk/templates/.claude/skills/moai-workflow-project/examples/question-examples.json +0 -175
  487. moai_adk/templates/.claude/skills/moai-workflow-project/examples/quick_start.py +0 -196
  488. moai_adk/templates/.claude/skills/moai-workflow-project/modules/__init__.py +0 -17
  489. moai_adk/templates/.claude/skills/moai-workflow-project/modules/advanced-patterns.md +0 -158
  490. moai_adk/templates/.claude/skills/moai-workflow-project/modules/ask_user_integration.py +0 -340
  491. moai_adk/templates/.claude/skills/moai-workflow-project/modules/batch_questions.py +0 -713
  492. moai_adk/templates/.claude/skills/moai-workflow-project/modules/config_manager.py +0 -538
  493. moai_adk/templates/.claude/skills/moai-workflow-project/modules/documentation_manager.py +0 -1336
  494. moai_adk/templates/.claude/skills/moai-workflow-project/modules/language_initializer.py +0 -730
  495. moai_adk/templates/.claude/skills/moai-workflow-project/modules/migration_manager.py +0 -608
  496. moai_adk/templates/.claude/skills/moai-workflow-project/modules/template_optimizer.py +0 -1005
  497. moai_adk/templates/.claude/skills/moai-workflow-project/test_integration_simple.py +0 -436
  498. moai_adk/templates/.claude/skills/moai-worktree/SKILL.md +0 -411
  499. moai_adk/templates/.claude/skills/moai-worktree/modules/integration-patterns.md +0 -982
  500. moai_adk/templates/.claude/skills/moai-worktree/modules/parallel-development.md +0 -778
  501. moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-commands.md +0 -646
  502. moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-management.md +0 -782
  503. moai_adk/templates/.moai/config/questions/_schema.yaml +0 -151
  504. moai_adk/templates/.moai/config/questions/tab0-init.yaml +0 -251
  505. moai_adk/templates/.moai/config/questions/tab1-user.yaml +0 -108
  506. moai_adk/templates/.moai/config/questions/tab2-project.yaml +0 -81
  507. moai_adk/templates/.moai/config/questions/tab3-git.yaml +0 -634
  508. moai_adk/templates/.moai/config/questions/tab4-quality.yaml +0 -170
  509. moai_adk/templates/.moai/config/questions/tab5-system.yaml +0 -87
  510. moai_adk/templates/.moai/scripts/setup-glm.py +0 -136
  511. moai_adk-0.34.0.dist-info/METADATA +0 -2999
  512. moai_adk-0.34.0.dist-info/RECORD +0 -463
  513. moai_adk-0.34.0.dist-info/licenses/LICENSE +0 -21
  514. /moai_adk/foundation/{git.py → git/__init__.py} +0 -0
  515. /moai_adk/templates/.claude/skills/moai-library-mermaid/{advanced-patterns.md → modules/advanced-patterns.md} +0 -0
  516. /moai_adk/templates/.claude/skills/moai-library-mermaid/{optimization.md → modules/optimization.md} +0 -0
  517. /moai_adk/templates/.claude/skills/moai-library-nextra/{optimization.md → modules/optimization.md} +0 -0
  518. /moai_adk/templates/.claude/skills/moai-workflow-jit-docs/{advanced-patterns.md → modules/advanced-patterns.md} +0 -0
  519. /moai_adk/templates/.claude/skills/moai-workflow-jit-docs/{optimization.md → modules/optimization.md} +0 -0
  520. /moai_adk/templates/.claude/skills/moai-workflow-testing/{advanced-patterns.md → modules/advanced-patterns.md} +0 -0
  521. /moai_adk/templates/.claude/skills/moai-workflow-testing/{optimization.md → modules/optimization.md} +0 -0
  522. /moai_adk/templates/.claude/skills/{moai-worktree → moai-workflow-worktree}/examples.md +0 -0
  523. /moai_adk/templates/.claude/skills/{moai-worktree → moai-workflow-worktree}/reference.md +0 -0
  524. {moai_adk-0.34.0.dist-info → moai_adk-1.1.0.dist-info}/WHEEL +0 -0
moai_adk/rank/hook.py ADDED
@@ -0,0 +1,1503 @@
1
+ """Session end hook for automatic token usage submission.
2
+
3
+ This module provides a hook that is called when a Claude Code session ends,
4
+ automatically submitting the session's token usage to the MoAI Rank service.
5
+
6
+ The hook is installed globally at ~/.claude/hooks/moai/ by default to collect
7
+ session data from all projects. Users can opt-out specific projects via
8
+ ~/.moai/rank/config.yaml configuration.
9
+
10
+ Token Usage Collection:
11
+ Claude Code's SessionEnd hook only provides metadata (session_id, transcript_path,
12
+ cwd, etc.) - NOT token counts. Token usage must be extracted from the transcript
13
+ JSONL file by parsing message.usage fields.
14
+
15
+ Cost Calculation:
16
+ Based on Anthropic's pricing (as of 2024):
17
+ - Claude 3.5 Sonnet: Input $3/MTok, Output $15/MTok
18
+ - Claude 3 Opus: Input $15/MTok, Output $75/MTok
19
+ - Cache Creation: $3.75/MTok (Sonnet), $18.75/MTok (Opus)
20
+ - Cache Read: $0.30/MTok (Sonnet), $1.50/MTok (Opus)
21
+ """
22
+
23
+ import hashlib
24
+ import os
25
+ from dataclasses import dataclass
26
+ from pathlib import Path
27
+ from typing import Any, Optional
28
+
29
+ from moai_adk.rank.client import RankClient, RankClientError, SessionSubmission
30
+ from moai_adk.rank.config import RankConfig
31
+
32
+ # Token pricing per million tokens (USD)
33
+ # Source: https://platform.claude.com/docs/en/about-claude/pricing
34
+ # Last updated: 2025-01
35
+ MODEL_PRICING: dict[str, dict[str, float]] = {
36
+ # Claude Opus 4.5 - Latest flagship model
37
+ "claude-opus-4-5-20251101": {
38
+ "input": 5.00,
39
+ "output": 25.00,
40
+ "cache_creation": 6.25, # 5m cache writes
41
+ "cache_read": 0.50,
42
+ },
43
+ # Claude Opus 4.1
44
+ "claude-opus-4-1-20250414": {
45
+ "input": 15.00,
46
+ "output": 75.00,
47
+ "cache_creation": 18.75,
48
+ "cache_read": 1.50,
49
+ },
50
+ # Claude Opus 4
51
+ "claude-opus-4-20250514": {
52
+ "input": 15.00,
53
+ "output": 75.00,
54
+ "cache_creation": 18.75,
55
+ "cache_read": 1.50,
56
+ },
57
+ # Claude Sonnet 4.5
58
+ "claude-sonnet-4-5-20251022": {
59
+ "input": 3.00,
60
+ "output": 15.00,
61
+ "cache_creation": 3.75,
62
+ "cache_read": 0.30,
63
+ },
64
+ # Claude Sonnet 4
65
+ "claude-sonnet-4-20250514": {
66
+ "input": 3.00,
67
+ "output": 15.00,
68
+ "cache_creation": 3.75,
69
+ "cache_read": 0.30,
70
+ },
71
+ # Claude Sonnet 3.7 (deprecated but still supported)
72
+ "claude-3-7-sonnet-20250219": {
73
+ "input": 3.00,
74
+ "output": 15.00,
75
+ "cache_creation": 3.75,
76
+ "cache_read": 0.30,
77
+ },
78
+ # Claude Haiku 4.5
79
+ "claude-haiku-4-5-20251022": {
80
+ "input": 1.00,
81
+ "output": 5.00,
82
+ "cache_creation": 1.25,
83
+ "cache_read": 0.10,
84
+ },
85
+ # Claude Haiku 3.5
86
+ "claude-3-5-haiku-20241022": {
87
+ "input": 0.80,
88
+ "output": 4.00,
89
+ "cache_creation": 1.00,
90
+ "cache_read": 0.08,
91
+ },
92
+ # Claude Opus 3 (deprecated)
93
+ "claude-3-opus-20240229": {
94
+ "input": 15.00,
95
+ "output": 75.00,
96
+ "cache_creation": 18.75,
97
+ "cache_read": 1.50,
98
+ },
99
+ # Claude Haiku 3
100
+ "claude-3-haiku-20240307": {
101
+ "input": 0.25,
102
+ "output": 1.25,
103
+ "cache_creation": 0.30,
104
+ "cache_read": 0.03,
105
+ },
106
+ # Legacy Claude 3.5 Sonnet versions
107
+ "claude-3-5-sonnet-20241022": {
108
+ "input": 3.00,
109
+ "output": 15.00,
110
+ "cache_creation": 3.75,
111
+ "cache_read": 0.30,
112
+ },
113
+ "claude-3-5-sonnet-20240620": {
114
+ "input": 3.00,
115
+ "output": 15.00,
116
+ "cache_creation": 3.75,
117
+ "cache_read": 0.30,
118
+ },
119
+ # Default fallback (Sonnet 4 pricing)
120
+ "default": {
121
+ "input": 3.00,
122
+ "output": 15.00,
123
+ "cache_creation": 3.75,
124
+ "cache_read": 0.30,
125
+ },
126
+ }
127
+
128
+
129
+ @dataclass
130
+ class TokenUsage:
131
+ """Token usage extracted from a session transcript.
132
+
133
+ Attributes:
134
+ input_tokens: Total input tokens consumed
135
+ output_tokens: Total output tokens generated
136
+ cache_creation_tokens: Tokens used for cache creation
137
+ cache_read_tokens: Tokens read from cache
138
+ model_name: Primary model used in the session
139
+ cost_usd: Calculated cost in USD
140
+
141
+ # Dashboard fields (for activity visualization)
142
+ started_at: Session start timestamp (UTC ISO format)
143
+ duration_seconds: Total session duration in seconds
144
+ turn_count: Number of user turns (messages)
145
+ tool_usage: Tool usage counts (e.g., {"Read": 5, "Write": 3})
146
+ model_usage: Per-model token usage (e.g., {"claude-opus-4-5": {"input": 5000, "output": 2000}})
147
+ code_metrics: Code change metrics (linesAdded, linesDeleted, filesModified, filesCreated)
148
+ """
149
+
150
+ input_tokens: int = 0
151
+ output_tokens: int = 0
152
+ cache_creation_tokens: int = 0
153
+ cache_read_tokens: int = 0
154
+ model_name: Optional[str] = None
155
+ cost_usd: float = 0.0
156
+
157
+ # Dashboard fields for activity visualization
158
+ started_at: Optional[str] = None
159
+ duration_seconds: int = 0
160
+ turn_count: int = 0
161
+ tool_usage: Optional[dict[str, int]] = None
162
+ model_usage: Optional[dict[str, dict[str, int]]] = None
163
+ code_metrics: Optional[dict[str, int]] = None
164
+
165
+
166
+ def compute_anonymous_project_id(project_path: str) -> str:
167
+ """Compute an anonymized project identifier.
168
+
169
+ Uses a hash of the project path to track sessions by project
170
+ without revealing the actual project name or path.
171
+
172
+ Args:
173
+ project_path: Full path to the project directory
174
+
175
+ Returns:
176
+ First 16 characters of the SHA-256 hash
177
+ """
178
+ # Normalize the path
179
+ normalized = os.path.normpath(os.path.expanduser(project_path))
180
+ # Hash it
181
+ hash_value = hashlib.sha256(normalized.encode()).hexdigest()
182
+ return hash_value[:16]
183
+
184
+
185
+ def get_model_pricing(model_name: Optional[str]) -> dict[str, float]:
186
+ """Get pricing for a specific model.
187
+
188
+ Uses exact match first, then falls back to pattern matching
189
+ for model families (opus, sonnet, haiku).
190
+
191
+ Args:
192
+ model_name: Model identifier string (e.g., 'claude-sonnet-4-20250514')
193
+
194
+ Returns:
195
+ Dictionary with pricing per million tokens:
196
+ - input: Base input token price
197
+ - output: Output token price
198
+ - cache_creation: 5-minute cache write price
199
+ - cache_read: Cache hit/refresh price
200
+ """
201
+ if not model_name:
202
+ return MODEL_PRICING["default"]
203
+
204
+ # Try exact match first
205
+ if model_name in MODEL_PRICING:
206
+ return MODEL_PRICING[model_name]
207
+
208
+ # Try pattern matching for model families
209
+ model_lower = model_name.lower()
210
+
211
+ # Opus family - check version specifics
212
+ if "opus" in model_lower:
213
+ if "4-5" in model_lower or "4.5" in model_lower:
214
+ return MODEL_PRICING["claude-opus-4-5-20251101"]
215
+ elif "4-1" in model_lower or "4.1" in model_lower:
216
+ return MODEL_PRICING["claude-opus-4-1-20250414"]
217
+ elif "opus-4" in model_lower or "opus4" in model_lower:
218
+ return MODEL_PRICING["claude-opus-4-20250514"]
219
+ elif "opus-3" in model_lower or "opus3" in model_lower:
220
+ return MODEL_PRICING["claude-3-opus-20240229"]
221
+ # Default to Opus 4 pricing for unknown opus versions
222
+ return MODEL_PRICING["claude-opus-4-20250514"]
223
+
224
+ # Haiku family
225
+ elif "haiku" in model_lower:
226
+ if "4-5" in model_lower or "4.5" in model_lower:
227
+ return MODEL_PRICING["claude-haiku-4-5-20251022"]
228
+ elif "3-5" in model_lower or "3.5" in model_lower:
229
+ return MODEL_PRICING["claude-3-5-haiku-20241022"]
230
+ elif "haiku-3" in model_lower or "haiku3" in model_lower:
231
+ return MODEL_PRICING["claude-3-haiku-20240307"]
232
+ # Default to Haiku 3.5 pricing
233
+ return MODEL_PRICING["claude-3-5-haiku-20241022"]
234
+
235
+ # Sonnet family
236
+ elif "sonnet" in model_lower:
237
+ if "4-5" in model_lower or "4.5" in model_lower:
238
+ return MODEL_PRICING["claude-sonnet-4-5-20251022"]
239
+ elif "sonnet-4" in model_lower or "sonnet4" in model_lower:
240
+ return MODEL_PRICING["claude-sonnet-4-20250514"]
241
+ elif "3-7" in model_lower or "3.7" in model_lower:
242
+ return MODEL_PRICING["claude-3-7-sonnet-20250219"]
243
+ elif "3-5" in model_lower or "3.5" in model_lower:
244
+ return MODEL_PRICING["claude-3-5-sonnet-20241022"]
245
+ # Default to Sonnet 4 pricing
246
+ return MODEL_PRICING["claude-sonnet-4-20250514"]
247
+
248
+ return MODEL_PRICING["default"]
249
+
250
+
251
+ def calculate_cost(usage: TokenUsage) -> float:
252
+ """Calculate the cost in USD for token usage.
253
+
254
+ Args:
255
+ usage: TokenUsage dataclass with token counts and model
256
+
257
+ Returns:
258
+ Cost in USD
259
+ """
260
+ pricing = get_model_pricing(usage.model_name)
261
+
262
+ # Cost per token (pricing is per million tokens)
263
+ input_cost = (usage.input_tokens / 1_000_000) * pricing["input"]
264
+ output_cost = (usage.output_tokens / 1_000_000) * pricing["output"]
265
+ cache_creation_cost = (usage.cache_creation_tokens / 1_000_000) * pricing["cache_creation"]
266
+ cache_read_cost = (usage.cache_read_tokens / 1_000_000) * pricing["cache_read"]
267
+
268
+ return input_cost + output_cost + cache_creation_cost + cache_read_cost
269
+
270
+
271
+ def parse_transcript_for_usage(transcript_path: str) -> Optional[TokenUsage]:
272
+ """Parse a JSONL transcript file to extract total token usage and session metadata.
273
+
274
+ Claude Code stores conversation transcripts as JSONL files where each line
275
+ contains a message object. Token usage is in the message.usage field.
276
+
277
+ Extracts:
278
+ - Token counts: input, output, cache creation, cache read
279
+ - Model name: Primary model used
280
+ - Session timing: started_at, duration_seconds
281
+ - User engagement: turn_count (number of user messages)
282
+ - Tool usage: Count of each tool used (e.g., {"Read": 5, "Write": 3})
283
+
284
+ Args:
285
+ transcript_path: Path to the transcript JSONL file
286
+
287
+ Returns:
288
+ TokenUsage dataclass with aggregated data, or None if parsing fails
289
+ """
290
+ import json
291
+ from datetime import datetime as dt
292
+
293
+ usage = TokenUsage()
294
+ transcript_file = Path(transcript_path)
295
+
296
+ if not transcript_file.exists():
297
+ return None
298
+
299
+ first_timestamp: Optional[str] = None
300
+ last_timestamp: Optional[str] = None
301
+ turn_count = 0
302
+ tool_counts: dict[str, int] = {}
303
+ model_counts: dict[str, dict[str, int]] = {}
304
+ current_model: Optional[str] = None
305
+
306
+ # Code metrics tracking
307
+ lines_added = 0
308
+ lines_deleted = 0
309
+ files_modified: set[str] = set()
310
+ files_created: set[str] = set()
311
+
312
+ def count_lines(text: str) -> int:
313
+ """Count non-empty lines in text."""
314
+ if not text:
315
+ return 0
316
+ return len([line for line in text.split("\n") if line.strip()])
317
+
318
+ try:
319
+ with open(transcript_file, encoding="utf-8") as f:
320
+ for line in f:
321
+ line = line.strip()
322
+ if not line:
323
+ continue
324
+
325
+ try:
326
+ data = json.loads(line)
327
+ except json.JSONDecodeError:
328
+ continue
329
+
330
+ # Extract timestamp for session timing
331
+ timestamp = data.get("timestamp")
332
+ if timestamp:
333
+ if first_timestamp is None:
334
+ first_timestamp = timestamp
335
+ last_timestamp = timestamp
336
+
337
+ # Count user turns
338
+ msg_type = data.get("type")
339
+ if msg_type == "user":
340
+ turn_count += 1
341
+
342
+ # Extract usage from message.usage field
343
+ message = data.get("message", {})
344
+ msg_usage = message.get("usage", {})
345
+
346
+ # Extract model name first (needed for per-model tracking)
347
+ model = message.get("model") or data.get("model")
348
+ if model:
349
+ current_model = model
350
+ usage.model_name = model
351
+
352
+ if msg_usage:
353
+ input_toks = msg_usage.get("input_tokens", 0)
354
+ output_toks = msg_usage.get("output_tokens", 0)
355
+
356
+ # Aggregate total tokens
357
+ usage.input_tokens += input_toks
358
+ usage.output_tokens += output_toks
359
+ usage.cache_creation_tokens += msg_usage.get("cache_creation_input_tokens", 0)
360
+ usage.cache_read_tokens += msg_usage.get("cache_read_input_tokens", 0)
361
+
362
+ # Track per-model token usage
363
+ if current_model:
364
+ if current_model not in model_counts:
365
+ model_counts[current_model] = {"input": 0, "output": 0}
366
+ model_counts[current_model]["input"] += input_toks
367
+ model_counts[current_model]["output"] += output_toks
368
+
369
+ # Count tool usage and extract code metrics from message.content
370
+ content = message.get("content", [])
371
+ if isinstance(content, list):
372
+ for block in content:
373
+ if isinstance(block, dict) and block.get("type") == "tool_use":
374
+ tool_name = block.get("name", "unknown")
375
+ tool_counts[tool_name] = tool_counts.get(tool_name, 0) + 1
376
+
377
+ # Extract code metrics from Edit/Write tools
378
+ tool_input = block.get("input", {})
379
+ if isinstance(tool_input, dict):
380
+ file_path = tool_input.get("file_path", "")
381
+
382
+ if tool_name == "Edit":
383
+ # Edit: count old_string (deleted) and new_string (added)
384
+ old_str = tool_input.get("old_string", "")
385
+ new_str = tool_input.get("new_string", "")
386
+ lines_deleted += count_lines(old_str)
387
+ lines_added += count_lines(new_str)
388
+ if file_path:
389
+ files_modified.add(file_path)
390
+
391
+ elif tool_name == "Write":
392
+ # Write: count content lines as added
393
+ content_str = tool_input.get("content", "")
394
+ lines_added += count_lines(content_str)
395
+ if file_path:
396
+ files_created.add(file_path)
397
+
398
+ elif tool_name == "MultiEdit":
399
+ # MultiEdit: process multiple edits
400
+ edits = tool_input.get("edits", [])
401
+ if isinstance(edits, list):
402
+ for edit in edits:
403
+ if isinstance(edit, dict):
404
+ old_str = edit.get("old_string", "")
405
+ new_str = edit.get("new_string", "")
406
+ lines_deleted += count_lines(old_str)
407
+ lines_added += count_lines(new_str)
408
+ if file_path:
409
+ files_modified.add(file_path)
410
+
411
+ # Set dashboard fields
412
+ usage.started_at = first_timestamp
413
+ usage.turn_count = turn_count
414
+ usage.tool_usage = tool_counts if tool_counts else None
415
+ usage.model_usage = model_counts if model_counts else None
416
+
417
+ # Set code metrics (only if there was any code activity)
418
+ if lines_added > 0 or lines_deleted > 0 or files_modified or files_created:
419
+ usage.code_metrics = {
420
+ "linesAdded": lines_added,
421
+ "linesDeleted": lines_deleted,
422
+ "filesModified": len(files_modified),
423
+ "filesCreated": len(files_created),
424
+ }
425
+
426
+ # Calculate duration in seconds
427
+ if first_timestamp and last_timestamp:
428
+ try:
429
+ # Parse ISO format timestamps
430
+ start_dt = dt.fromisoformat(first_timestamp.replace("Z", "+00:00"))
431
+ end_dt = dt.fromisoformat(last_timestamp.replace("Z", "+00:00"))
432
+ usage.duration_seconds = int((end_dt - start_dt).total_seconds())
433
+ except (ValueError, TypeError):
434
+ usage.duration_seconds = 0
435
+
436
+ # Calculate cost
437
+ if usage.input_tokens > 0 or usage.output_tokens > 0:
438
+ usage.cost_usd = calculate_cost(usage)
439
+ return usage
440
+
441
+ return None
442
+
443
+ except (OSError, IOError):
444
+ return None
445
+
446
+
447
+ def parse_session_data(session_data: dict[str, Any]) -> Optional[dict[str, Any]]:
448
+ """Parse and validate session data from Claude Code SessionEnd hook.
449
+
450
+ IMPORTANT: Claude Code's SessionEnd hook does NOT provide token counts directly.
451
+ Token usage must be extracted from the transcript_path JSONL file.
452
+
453
+ Args:
454
+ session_data: Raw session data from Claude Code containing:
455
+ - session_id: Unique session identifier
456
+ - transcript_path: Path to the JSONL transcript file
457
+ - cwd: Current working directory (project path)
458
+ - permission_mode: Permission mode used
459
+ - hook_event_name: Name of the hook event
460
+ - reason: Reason for session end
461
+
462
+ Returns:
463
+ Parsed session data dictionary with token usage, or None if invalid
464
+ """
465
+ try:
466
+ # Get transcript path - this is where token data lives
467
+ transcript_path = session_data.get("transcript_path")
468
+ if not transcript_path:
469
+ return None
470
+
471
+ # Parse the transcript file for token usage
472
+ usage = parse_transcript_for_usage(transcript_path)
473
+ if not usage:
474
+ return None
475
+
476
+ # Skip sessions with no token usage
477
+ if usage.input_tokens == 0 and usage.output_tokens == 0:
478
+ return None
479
+
480
+ # Get project path from cwd
481
+ project_path = session_data.get("cwd")
482
+
483
+ # Generate ended_at timestamp
484
+ from datetime import datetime as dt
485
+ from datetime import timezone
486
+
487
+ ended_at = dt.now(timezone.utc).isoformat().replace("+00:00", "Z")
488
+
489
+ return {
490
+ "input_tokens": usage.input_tokens,
491
+ "output_tokens": usage.output_tokens,
492
+ "cache_creation_tokens": usage.cache_creation_tokens,
493
+ "cache_read_tokens": usage.cache_read_tokens,
494
+ "model_name": usage.model_name,
495
+ "project_path": project_path,
496
+ "ended_at": ended_at,
497
+ "cost_usd": usage.cost_usd,
498
+ "session_id": session_data.get("session_id"),
499
+ # Dashboard fields
500
+ "started_at": usage.started_at,
501
+ "duration_seconds": usage.duration_seconds,
502
+ "turn_count": usage.turn_count,
503
+ "tool_usage": usage.tool_usage,
504
+ "model_usage": usage.model_usage,
505
+ "code_metrics": usage.code_metrics,
506
+ }
507
+ except (KeyError, ValueError, TypeError):
508
+ return None
509
+
510
+
511
+ def parse_transcript_to_submission(
512
+ transcript_path: Path,
513
+ ) -> Optional[SessionSubmission]:
514
+ """Parse a transcript file and return a SessionSubmission object.
515
+
516
+ This function is used for batch processing - it parses the transcript
517
+ without submitting, allowing multiple sessions to be batched together.
518
+
519
+ Args:
520
+ transcript_path: Path to the JSONL transcript file
521
+
522
+ Returns:
523
+ SessionSubmission object if valid, None if no token usage or error
524
+ """
525
+ try:
526
+ session_data = {
527
+ "session_id": transcript_path.stem,
528
+ "transcript_path": str(transcript_path),
529
+ "cwd": str(transcript_path.parent.parent),
530
+ }
531
+
532
+ parsed = parse_session_data(session_data)
533
+ if not parsed:
534
+ return None
535
+
536
+ client = RankClient()
537
+
538
+ # Compute session hash
539
+ session_hash = client.compute_session_hash(
540
+ input_tokens=parsed["input_tokens"],
541
+ output_tokens=parsed["output_tokens"],
542
+ cache_creation_tokens=parsed["cache_creation_tokens"],
543
+ cache_read_tokens=parsed["cache_read_tokens"],
544
+ model_name=parsed["model_name"],
545
+ ended_at=parsed["ended_at"],
546
+ )
547
+
548
+ return SessionSubmission(
549
+ session_hash=session_hash,
550
+ ended_at=parsed["ended_at"],
551
+ input_tokens=parsed["input_tokens"],
552
+ output_tokens=parsed["output_tokens"],
553
+ cache_creation_tokens=parsed["cache_creation_tokens"],
554
+ cache_read_tokens=parsed["cache_read_tokens"],
555
+ model_name=parsed["model_name"],
556
+ started_at=parsed.get("started_at"),
557
+ duration_seconds=parsed.get("duration_seconds", 0),
558
+ turn_count=parsed.get("turn_count", 0),
559
+ tool_usage=parsed.get("tool_usage"),
560
+ model_usage=parsed.get("model_usage"),
561
+ code_metrics=parsed.get("code_metrics"),
562
+ )
563
+ except Exception:
564
+ return None
565
+
566
+
567
+ def submit_session_hook(session_data: dict[str, Any]) -> dict[str, Any]:
568
+ """Hook function to submit session data to MoAI Rank.
569
+
570
+ This function is called by the Claude Code hook system when a session ends.
571
+ It parses the transcript JSONL file to extract token usage and submits it
572
+ to the rank service.
573
+
574
+ Args:
575
+ session_data: Session data dictionary from Claude Code SessionEnd hook:
576
+ - session_id: Unique session identifier
577
+ - transcript_path: Path to the JSONL transcript file (contains token data)
578
+ - cwd: Current working directory (project path)
579
+ - permission_mode: Permission mode used
580
+ - hook_event_name: Name of the hook event
581
+ - reason: Reason for session end
582
+
583
+ Returns:
584
+ Result dictionary with:
585
+ - success: Whether submission succeeded
586
+ - message: Status message
587
+ - session_id: Session ID from server (if successful)
588
+ - cost_usd: Calculated cost in USD (if successful)
589
+ """
590
+ result = {
591
+ "success": False,
592
+ "message": "",
593
+ "session_id": None,
594
+ "cost_usd": 0.0,
595
+ }
596
+
597
+ # Check if user has registered
598
+ if not RankConfig.has_credentials():
599
+ result["message"] = "Not registered with MoAI Rank"
600
+ return result
601
+
602
+ # Parse session data
603
+ parsed = parse_session_data(session_data)
604
+ if not parsed:
605
+ result["message"] = "No token usage to submit"
606
+ return result
607
+
608
+ try:
609
+ client = RankClient()
610
+
611
+ # Compute session hash
612
+ session_hash = client.compute_session_hash(
613
+ input_tokens=parsed["input_tokens"],
614
+ output_tokens=parsed["output_tokens"],
615
+ cache_creation_tokens=parsed["cache_creation_tokens"],
616
+ cache_read_tokens=parsed["cache_read_tokens"],
617
+ model_name=parsed["model_name"],
618
+ ended_at=parsed["ended_at"],
619
+ )
620
+
621
+ # Create submission
622
+ submission = SessionSubmission(
623
+ session_hash=session_hash,
624
+ ended_at=parsed["ended_at"],
625
+ input_tokens=parsed["input_tokens"],
626
+ output_tokens=parsed["output_tokens"],
627
+ cache_creation_tokens=parsed["cache_creation_tokens"],
628
+ cache_read_tokens=parsed["cache_read_tokens"],
629
+ model_name=parsed["model_name"],
630
+ # Dashboard fields
631
+ started_at=parsed.get("started_at"),
632
+ duration_seconds=parsed.get("duration_seconds", 0),
633
+ turn_count=parsed.get("turn_count", 0),
634
+ tool_usage=parsed.get("tool_usage"),
635
+ model_usage=parsed.get("model_usage"),
636
+ code_metrics=parsed.get("code_metrics"),
637
+ )
638
+
639
+ # Submit to API
640
+ response = client.submit_session(submission)
641
+
642
+ result["success"] = True
643
+ result["message"] = response.get("message", "Session submitted")
644
+ result["session_id"] = response.get("sessionId")
645
+ result["cost_usd"] = parsed.get("cost_usd", 0.0)
646
+
647
+ except RankClientError as e:
648
+ result["message"] = f"Submission failed: {e}"
649
+
650
+ return result
651
+
652
+
653
+ def create_hook_script() -> str:
654
+ """Generate a hook script for Claude Code session end.
655
+
656
+ Returns:
657
+ Python script content for the hook
658
+ """
659
+ return '''#!/usr/bin/env python3
660
+ """MoAI Rank Session Hook
661
+
662
+ This hook submits Claude Code session token usage to the MoAI Rank service.
663
+ It is triggered automatically when a session ends.
664
+ """
665
+
666
+ import json
667
+ import sys
668
+
669
+ def main():
670
+ # Read session data from stdin
671
+ try:
672
+ input_data = sys.stdin.read()
673
+ if not input_data:
674
+ return
675
+
676
+ session_data = json.loads(input_data)
677
+
678
+ # Lazy import to avoid startup delay
679
+ from moai_adk.rank.hook import submit_session_hook
680
+
681
+ result = submit_session_hook(session_data)
682
+
683
+ if result["success"]:
684
+ print(f"Session submitted to MoAI Rank", file=sys.stderr)
685
+ elif result["message"] != "Not registered with MoAI Rank":
686
+ print(f"MoAI Rank: {result['message']}", file=sys.stderr)
687
+
688
+ except json.JSONDecodeError:
689
+ pass
690
+ except ImportError:
691
+ # moai-adk not installed, silently skip
692
+ pass
693
+ except Exception as e:
694
+ print(f"MoAI Rank hook error: {e}", file=sys.stderr)
695
+
696
+ if __name__ == "__main__":
697
+ main()
698
+ '''
699
+
700
+
701
+ def load_rank_config() -> dict[str, Any]:
702
+ """Load rank configuration from ~/.moai/rank/config.yaml.
703
+
704
+ Returns:
705
+ Configuration dictionary with rank settings
706
+ """
707
+ import yaml
708
+
709
+ config_file = Path.home() / ".moai" / "rank" / "config.yaml"
710
+ if not config_file.exists():
711
+ return {"enabled": True, "exclude_projects": []}
712
+
713
+ try:
714
+ with open(config_file) as f:
715
+ data = yaml.safe_load(f) or {}
716
+ rank_config = data.get("rank", {})
717
+ return {
718
+ "enabled": rank_config.get("enabled", True),
719
+ "exclude_projects": rank_config.get("exclude_projects", []),
720
+ }
721
+ except (OSError, yaml.YAMLError):
722
+ return {"enabled": True, "exclude_projects": []}
723
+
724
+
725
+ def is_project_excluded(project_path: Optional[str]) -> bool:
726
+ """Check if a project is excluded from rank submission.
727
+
728
+ Args:
729
+ project_path: Path to the project directory
730
+
731
+ Returns:
732
+ True if project should be excluded from submission
733
+ """
734
+ import fnmatch
735
+
736
+ if not project_path:
737
+ return False
738
+
739
+ config = load_rank_config()
740
+
741
+ # Check if rank is globally disabled
742
+ if not config.get("enabled", True):
743
+ return True
744
+
745
+ # Normalize the project path
746
+ normalized_path = os.path.normpath(os.path.expanduser(project_path))
747
+
748
+ # Check against exclusion patterns
749
+ for pattern in config.get("exclude_projects", []):
750
+ pattern = os.path.expanduser(pattern)
751
+ # Support both exact paths and wildcard patterns
752
+ if fnmatch.fnmatch(normalized_path, pattern):
753
+ return True
754
+ # Also check if pattern is a prefix (directory match)
755
+ if normalized_path.startswith(os.path.normpath(pattern)):
756
+ return True
757
+
758
+ return False
759
+
760
+
761
+ def save_rank_config(config: dict[str, Any]) -> bool:
762
+ """Save rank configuration to ~/.moai/rank/config.yaml.
763
+
764
+ Args:
765
+ config: Configuration dictionary with rank settings
766
+
767
+ Returns:
768
+ True if saved successfully
769
+ """
770
+ import yaml
771
+
772
+ config_dir = Path.home() / ".moai" / "rank"
773
+ config_file = config_dir / "config.yaml"
774
+
775
+ try:
776
+ config_dir.mkdir(parents=True, exist_ok=True)
777
+
778
+ # Load existing config to preserve other settings
779
+ existing: dict[str, Any] = {}
780
+ if config_file.exists():
781
+ with open(config_file) as f:
782
+ existing = yaml.safe_load(f) or {}
783
+
784
+ existing["rank"] = config
785
+ with open(config_file, "w") as f:
786
+ yaml.safe_dump(existing, f, default_flow_style=False)
787
+ return True
788
+ except (OSError, yaml.YAMLError):
789
+ return False
790
+
791
+
792
+ def add_project_exclusion(project_path: str) -> bool:
793
+ """Add a project to the exclusion list.
794
+
795
+ Args:
796
+ project_path: Path or pattern to exclude
797
+
798
+ Returns:
799
+ True if added successfully
800
+ """
801
+ config = load_rank_config()
802
+ exclude_list = config.get("exclude_projects", [])
803
+
804
+ # Normalize the path
805
+ normalized = os.path.normpath(os.path.expanduser(project_path))
806
+
807
+ if normalized not in exclude_list:
808
+ exclude_list.append(normalized)
809
+ config["exclude_projects"] = exclude_list
810
+ return save_rank_config(config)
811
+
812
+ return True # Already excluded
813
+
814
+
815
+ def remove_project_exclusion(project_path: str) -> bool:
816
+ """Remove a project from the exclusion list.
817
+
818
+ Args:
819
+ project_path: Path or pattern to remove from exclusion
820
+
821
+ Returns:
822
+ True if removed successfully
823
+ """
824
+ config = load_rank_config()
825
+ exclude_list = config.get("exclude_projects", [])
826
+
827
+ normalized = os.path.normpath(os.path.expanduser(project_path))
828
+
829
+ if normalized in exclude_list:
830
+ exclude_list.remove(normalized)
831
+ config["exclude_projects"] = exclude_list
832
+ return save_rank_config(config)
833
+
834
+ return True # Not in list anyway
835
+
836
+
837
+ def create_global_hook_script() -> str:
838
+ """Generate a global hook script with opt-out support.
839
+
840
+ Returns:
841
+ Python script content for the global hook
842
+ """
843
+ return '''#!/usr/bin/env python3
844
+ """MoAI Rank Session Hook (Global)
845
+
846
+ This hook submits Claude Code session token usage to the MoAI Rank service.
847
+ It is installed globally at ~/.claude/hooks/moai/ and runs for all projects.
848
+
849
+ Opt-out: Configure ~/.moai/rank/config.yaml to exclude specific projects:
850
+ rank:
851
+ enabled: true
852
+ exclude_projects:
853
+ - "/path/to/private-project"
854
+ - "*/confidential/*"
855
+ """
856
+
857
+ import json
858
+ import sys
859
+
860
+ def main():
861
+ try:
862
+ input_data = sys.stdin.read()
863
+ if not input_data:
864
+ return
865
+
866
+ session_data = json.loads(input_data)
867
+
868
+ # Lazy import to avoid startup delay
869
+ from moai_adk.rank.hook import is_project_excluded, submit_session_hook
870
+
871
+ # Check if this project is excluded
872
+ project_path = session_data.get("projectPath") or session_data.get("cwd")
873
+ if is_project_excluded(project_path):
874
+ return # Silently skip excluded projects
875
+
876
+ result = submit_session_hook(session_data)
877
+
878
+ if result["success"]:
879
+ print("Session submitted to MoAI Rank", file=sys.stderr)
880
+ elif result["message"] != "Not registered with MoAI Rank":
881
+ print(f"MoAI Rank: {result['message']}", file=sys.stderr)
882
+
883
+ except json.JSONDecodeError:
884
+ pass
885
+ except ImportError:
886
+ # moai-adk not installed, silently skip
887
+ pass
888
+ except Exception as e:
889
+ print(f"MoAI Rank hook error: {e}", file=sys.stderr)
890
+
891
+ if __name__ == "__main__":
892
+ main()
893
+ '''
894
+
895
+
896
+ def _register_hook_in_settings() -> bool:
897
+ """Register SessionEnd hook in ~/.claude/settings.json.
898
+
899
+ Claude Code requires hooks to be explicitly registered in settings.json
900
+ to be executed. This function adds the SessionEnd hook configuration.
901
+
902
+ Returns:
903
+ True if successfully registered, False otherwise
904
+ """
905
+ import json
906
+
907
+ settings_file = Path.home() / ".claude" / "settings.json"
908
+ hook_command = f"python3 {Path.home()}/.claude/hooks/moai/session_end__rank_submit.py"
909
+
910
+ try:
911
+ # Load existing settings or create new
912
+ if settings_file.exists():
913
+ with open(settings_file, encoding="utf-8") as f:
914
+ settings = json.load(f)
915
+ else:
916
+ settings = {}
917
+
918
+ # Ensure hooks section exists
919
+ if "hooks" not in settings:
920
+ settings["hooks"] = {}
921
+
922
+ # Check if SessionEnd hook already exists
923
+ if "SessionEnd" in settings["hooks"]:
924
+ # Check if our hook is already registered
925
+ for hook_config in settings["hooks"]["SessionEnd"]:
926
+ for hook in hook_config.get("hooks", []):
927
+ if "session_end__rank_submit.py" in hook.get("command", ""):
928
+ return True # Already registered
929
+
930
+ # Add SessionEnd hook
931
+ session_end_config = {
932
+ "matcher": ".*",
933
+ "hooks": [{"type": "command", "command": hook_command}],
934
+ }
935
+
936
+ if "SessionEnd" not in settings["hooks"]:
937
+ settings["hooks"]["SessionEnd"] = []
938
+
939
+ settings["hooks"]["SessionEnd"].append(session_end_config)
940
+
941
+ # Write back
942
+ with open(settings_file, "w", encoding="utf-8") as f:
943
+ json.dump(settings, f, indent=2)
944
+
945
+ return True
946
+
947
+ except (OSError, json.JSONDecodeError):
948
+ return False
949
+
950
+
951
+ def install_hook(project_path: Optional[Path] = None) -> bool:
952
+ """Install the session end hook globally to ~/.claude/hooks/moai/.
953
+
954
+ This installs the hook at the user level so it runs for all projects.
955
+ Users can opt-out specific projects via ~/.moai/rank/config.yaml.
956
+
957
+ Also registers the hook in ~/.claude/settings.json so Claude Code
958
+ knows to execute it on session end.
959
+
960
+ Args:
961
+ project_path: Deprecated, ignored. Hook is always installed globally.
962
+
963
+ Returns:
964
+ True if hook was installed successfully
965
+ """
966
+ _ = project_path # Deprecated parameter, kept for backwards compatibility
967
+
968
+ # Install to global user hooks directory
969
+ hooks_dir = Path.home() / ".claude" / "hooks" / "moai"
970
+ hooks_dir.mkdir(parents=True, exist_ok=True)
971
+
972
+ hook_file = hooks_dir / "session_end__rank_submit.py"
973
+
974
+ try:
975
+ hook_file.write_text(create_global_hook_script())
976
+ hook_file.chmod(0o755) # Make executable
977
+
978
+ # Register hook in settings.json
979
+ if not _register_hook_in_settings():
980
+ return False
981
+
982
+ return True
983
+ except OSError:
984
+ return False
985
+
986
+
987
+ def _unregister_hook_from_settings() -> bool:
988
+ """Remove SessionEnd hook from ~/.claude/settings.json.
989
+
990
+ Returns:
991
+ True if successfully unregistered, False otherwise
992
+ """
993
+ import json
994
+
995
+ settings_file = Path.home() / ".claude" / "settings.json"
996
+
997
+ try:
998
+ if not settings_file.exists():
999
+ return True # Nothing to remove
1000
+
1001
+ with open(settings_file, encoding="utf-8") as f:
1002
+ settings = json.load(f)
1003
+
1004
+ if "hooks" not in settings or "SessionEnd" not in settings["hooks"]:
1005
+ return True # Nothing to remove
1006
+
1007
+ # Filter out our hook
1008
+ settings["hooks"]["SessionEnd"] = [
1009
+ hook_config
1010
+ for hook_config in settings["hooks"]["SessionEnd"]
1011
+ if not any(
1012
+ "session_end__rank_submit.py" in hook.get("command", "") for hook in hook_config.get("hooks", [])
1013
+ )
1014
+ ]
1015
+
1016
+ # Remove empty SessionEnd array
1017
+ if not settings["hooks"]["SessionEnd"]:
1018
+ del settings["hooks"]["SessionEnd"]
1019
+
1020
+ # Write back
1021
+ with open(settings_file, "w", encoding="utf-8") as f:
1022
+ json.dump(settings, f, indent=2)
1023
+
1024
+ return True
1025
+
1026
+ except (OSError, json.JSONDecodeError):
1027
+ return False
1028
+
1029
+
1030
+ def uninstall_hook() -> bool:
1031
+ """Uninstall the global session end hook.
1032
+
1033
+ Removes both the hook file and the registration in settings.json.
1034
+
1035
+ Returns:
1036
+ True if hook was uninstalled successfully
1037
+ """
1038
+ hook_file = Path.home() / ".claude" / "hooks" / "moai" / "session_end__rank_submit.py"
1039
+
1040
+ try:
1041
+ # Remove hook file
1042
+ if hook_file.exists():
1043
+ hook_file.unlink()
1044
+
1045
+ # Unregister from settings.json
1046
+ _unregister_hook_from_settings()
1047
+
1048
+ return True
1049
+ except OSError:
1050
+ return False
1051
+
1052
+
1053
+ def is_hook_installed() -> bool:
1054
+ """Check if the global hook is installed.
1055
+
1056
+ Checks both the hook file existence and registration in settings.json.
1057
+
1058
+ Returns:
1059
+ True if the hook file exists and is registered
1060
+ """
1061
+ import json
1062
+
1063
+ hook_file = Path.home() / ".claude" / "hooks" / "moai" / "session_end__rank_submit.py"
1064
+ if not hook_file.exists():
1065
+ return False
1066
+
1067
+ # Also check if registered in settings.json
1068
+ settings_file = Path.home() / ".claude" / "settings.json"
1069
+ try:
1070
+ if not settings_file.exists():
1071
+ return False
1072
+
1073
+ with open(settings_file, encoding="utf-8") as f:
1074
+ settings = json.load(f)
1075
+
1076
+ if "hooks" not in settings or "SessionEnd" not in settings["hooks"]:
1077
+ return False
1078
+
1079
+ # Check if our hook is registered
1080
+ for hook_config in settings["hooks"]["SessionEnd"]:
1081
+ for hook in hook_config.get("hooks", []):
1082
+ if "session_end__rank_submit.py" in hook.get("command", ""):
1083
+ return True
1084
+
1085
+ return False
1086
+
1087
+ except (OSError, json.JSONDecodeError):
1088
+ return False
1089
+
1090
+
1091
+ def prompt_hook_installation(console: Any = None, confirm_func: Any = None) -> bool:
1092
+ """Prompt user to install MoAI Rank hook if eligible.
1093
+
1094
+ This function checks if the user is registered with MoAI Rank
1095
+ and if the global hook is not yet installed. If both conditions
1096
+ are met, it prompts the user to install the hook.
1097
+
1098
+ Args:
1099
+ console: Rich console instance for output (optional)
1100
+ confirm_func: Confirmation function like click.confirm (optional)
1101
+
1102
+ Returns:
1103
+ True if hook was installed, False otherwise
1104
+ """
1105
+ # Check if user is registered with MoAI Rank
1106
+ if not RankConfig.has_credentials():
1107
+ return False
1108
+
1109
+ # Check if hook is already installed
1110
+ if is_hook_installed():
1111
+ return False
1112
+
1113
+ # Lazy import to avoid circular dependencies
1114
+ if console is None:
1115
+ from rich.console import Console
1116
+
1117
+ console = Console()
1118
+
1119
+ if confirm_func is None:
1120
+ import click
1121
+
1122
+ confirm_func = click.confirm
1123
+
1124
+ # Prompt user for hook installation
1125
+ console.print()
1126
+ console.print("[cyan]🏆 MoAI Rank Hook Installation[/cyan]")
1127
+ console.print("[dim]You are registered with MoAI Rank but the session tracking hook is not installed.[/dim]")
1128
+ console.print("[dim]The hook automatically submits your Claude Code session stats to the leaderboard.[/dim]")
1129
+ console.print()
1130
+
1131
+ if confirm_func("Would you like to install the MoAI Rank session hook?", default=True):
1132
+ if install_hook():
1133
+ console.print()
1134
+ console.print("[green]✅ MoAI Rank hook installed successfully![/green]")
1135
+ console.print("[dim]Location: ~/.claude/hooks/moai/session_end__rank_submit.py[/dim]")
1136
+ console.print("[dim]To exclude specific projects: moai rank exclude /path/to/project[/dim]")
1137
+ console.print()
1138
+ return True
1139
+ else:
1140
+ console.print("[yellow]⚠ Failed to install hook. You can try later with: moai rank register[/yellow]")
1141
+ return False
1142
+ else:
1143
+ console.print("[dim]Skipped. You can install later with: moai rank register[/dim]")
1144
+ return False
1145
+
1146
+
1147
+ def find_all_transcripts() -> list[Path]:
1148
+ """Find all Claude Code transcript files.
1149
+
1150
+ Scans ~/.claude/projects/ for all session transcript JSONL files.
1151
+ Excludes subagent transcripts.
1152
+
1153
+ Returns:
1154
+ List of paths to transcript files
1155
+ """
1156
+ claude_dir = Path.home() / ".claude" / "projects"
1157
+ if not claude_dir.exists():
1158
+ return []
1159
+
1160
+ transcripts = []
1161
+ for jsonl_file in claude_dir.rglob("*.jsonl"):
1162
+ # Skip subagent transcripts
1163
+ if "subagents" in str(jsonl_file):
1164
+ continue
1165
+ transcripts.append(jsonl_file)
1166
+
1167
+ return transcripts
1168
+
1169
+
1170
+ def sync_all_sessions(
1171
+ console: Any = None,
1172
+ max_workers: int = 20,
1173
+ batch_size: int = 100,
1174
+ ) -> dict[str, int]:
1175
+ """Sync all existing Claude Code sessions to MoAI Rank.
1176
+
1177
+ Uses a 2-phase approach:
1178
+ 1. Parse all transcripts in parallel (local I/O)
1179
+ 2. Submit in batches to minimize network requests (falls back to individual if batch not available)
1180
+
1181
+ Args:
1182
+ console: Rich console instance for output (optional)
1183
+ max_workers: Maximum number of parallel workers for parsing (default: 20)
1184
+ batch_size: Number of sessions per batch request (default: 100, max: 100)
1185
+
1186
+ Returns:
1187
+ Dictionary with sync statistics:
1188
+ - total: Total transcripts found
1189
+ - submitted: Successfully submitted
1190
+ - skipped: Already submitted or no token usage
1191
+ - failed: Failed to submit
1192
+ """
1193
+ from concurrent.futures import ThreadPoolExecutor, as_completed
1194
+ from threading import Lock
1195
+
1196
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
1197
+
1198
+ if console is None:
1199
+ from rich.console import Console
1200
+
1201
+ console = Console()
1202
+
1203
+ # Check credentials
1204
+ if not RankConfig.has_credentials():
1205
+ console.print("[yellow]Not registered with MoAI Rank.[/yellow]")
1206
+ return {"total": 0, "submitted": 0, "skipped": 0, "failed": 0}
1207
+
1208
+ # Find all transcripts
1209
+ transcripts = find_all_transcripts()
1210
+ if not transcripts:
1211
+ console.print("[dim]No session transcripts found.[/dim]")
1212
+ return {"total": 0, "submitted": 0, "skipped": 0, "failed": 0}
1213
+
1214
+ stats = {"total": len(transcripts), "submitted": 0, "skipped": 0, "failed": 0}
1215
+ stats_lock = Lock()
1216
+
1217
+ console.print()
1218
+ console.print(f"[cyan]Syncing {len(transcripts)} session(s) to MoAI Rank[/cyan]")
1219
+ console.print(f"[dim]Phase 1: Parsing transcripts (parallel: {max_workers} workers)[/dim]")
1220
+ console.print()
1221
+
1222
+ # Phase 1: Parse all transcripts in parallel
1223
+ submissions: list[SessionSubmission] = []
1224
+ skipped_count = 0
1225
+
1226
+ with Progress(
1227
+ SpinnerColumn(),
1228
+ TextColumn("[progress.description]{task.description}"),
1229
+ BarColumn(),
1230
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
1231
+ TextColumn("[dim]({task.completed}/{task.total})[/dim]"),
1232
+ console=console,
1233
+ ) as progress:
1234
+ task = progress.add_task("Parsing transcripts", total=len(transcripts))
1235
+
1236
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
1237
+ futures = {executor.submit(parse_transcript_to_submission, path): path for path in transcripts}
1238
+
1239
+ for future in as_completed(futures):
1240
+ submission = future.result()
1241
+ if submission is not None:
1242
+ submissions.append(submission)
1243
+ with stats_lock:
1244
+ stats["submitted"] += 1
1245
+ else:
1246
+ with stats_lock:
1247
+ stats["skipped"] += 1
1248
+ skipped_count += 1
1249
+
1250
+ progress.update(task, advance=1)
1251
+
1252
+ # Phase 2: Submit
1253
+ if not submissions:
1254
+ console.print()
1255
+ console.print("[dim]No valid sessions to submit.[/dim]")
1256
+ return {
1257
+ "total": len(transcripts),
1258
+ "submitted": 0,
1259
+ "skipped": len(transcripts),
1260
+ "failed": 0,
1261
+ }
1262
+
1263
+ # Reset submitted count for submission tracking
1264
+ stats["submitted"] = 0
1265
+ stats["skipped"] = skipped_count
1266
+
1267
+ client = RankClient()
1268
+
1269
+ # First, try to detect if batch API is available
1270
+ batch_available = False
1271
+ if submissions:
1272
+ try:
1273
+ # Try a small batch first
1274
+ test_batch = submissions[:1]
1275
+ client.submit_sessions_batch(test_batch)
1276
+ batch_available = True
1277
+ except Exception:
1278
+ batch_available = False
1279
+
1280
+ if batch_available:
1281
+ # Use batch submission
1282
+ batch_count = (len(submissions) + batch_size - 1) // batch_size
1283
+ console.print()
1284
+ console.print(f"[cyan]Phase 2: Submitting {len(submissions)} session(s) [dim](batch mode)[/dim][/cyan]")
1285
+ console.print(f"[dim]Batch size: {batch_size} | Batches: {batch_count}[/dim]")
1286
+ console.print()
1287
+
1288
+ batch_submitted = 0
1289
+ batch_skipped = 0
1290
+ batch_failed = 0
1291
+
1292
+ with Progress(
1293
+ SpinnerColumn(),
1294
+ TextColumn("[progress.description]{task.description}"),
1295
+ BarColumn(),
1296
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
1297
+ TextColumn("[dim]({task.completed}/{task.total})[/dim]"),
1298
+ console=console,
1299
+ ) as progress:
1300
+ task = progress.add_task("Submitting batches", total=batch_count)
1301
+
1302
+ for i in range(0, len(submissions), batch_size):
1303
+ batch = submissions[i : i + batch_size]
1304
+
1305
+ try:
1306
+ response = client.submit_sessions_batch(batch)
1307
+
1308
+ if response.get("success"):
1309
+ batch_submitted += response.get("succeeded", 0)
1310
+ batch_failed += response.get("failed", 0)
1311
+
1312
+ # Check individual results for duplicates/skipped
1313
+ for result in response.get("results", []):
1314
+ if not result.get("success"):
1315
+ error_msg = result.get("error", "").lower()
1316
+ if "duplicate" in error_msg or "already" in error_msg:
1317
+ batch_skipped += 1
1318
+ batch_failed -= 1
1319
+ else:
1320
+ batch_failed += len(batch)
1321
+
1322
+ except Exception:
1323
+ batch_failed += len(batch)
1324
+
1325
+ progress.update(task, advance=1)
1326
+
1327
+ stats["submitted"] = batch_submitted
1328
+ stats["skipped"] += batch_skipped
1329
+ stats["failed"] = batch_failed
1330
+ else:
1331
+ # Fall back to individual submissions (sequential)
1332
+ console.print()
1333
+ console.print(f"[cyan]Phase 2: Submitting {len(submissions)} session(s) [dim](individual mode)[/dim][/cyan]")
1334
+ console.print()
1335
+
1336
+ from time import sleep
1337
+
1338
+ from moai_adk.rank.client import ApiError, RankClientError
1339
+
1340
+ # Track errors for debugging
1341
+ error_samples: list[str] = []
1342
+ error_type_counts: dict[str, int] = {}
1343
+ error_lock = Lock()
1344
+
1345
+ # Ensure log directory exists and set up logging
1346
+ RankConfig.ensure_config_dir()
1347
+ log_file = RankConfig.CONFIG_DIR / "sync_errors.log"
1348
+
1349
+ # Clear previous log
1350
+ if log_file.exists():
1351
+ log_file.unlink()
1352
+
1353
+ import logging
1354
+
1355
+ logging.basicConfig(
1356
+ filename=str(log_file),
1357
+ level=logging.ERROR,
1358
+ format="%(asctime)s - %(message)s",
1359
+ force=True,
1360
+ )
1361
+
1362
+ # Use sequential submission with rate limiting to avoid server limits
1363
+ submission_delay = 0.1 # 100ms between submissions
1364
+
1365
+ console.print(f"[dim]Rate limit: {submission_delay}s between submissions[/dim]")
1366
+ console.print()
1367
+
1368
+ # Estimate time
1369
+ estimated_seconds = len(submissions) * submission_delay
1370
+ console.print(f"[dim]Estimated time: ~{int(estimated_seconds / 60)}m {int(estimated_seconds % 60)}s[/dim]")
1371
+ console.print()
1372
+
1373
+ with Progress(
1374
+ SpinnerColumn(),
1375
+ TextColumn("[progress.description]{task.description}"),
1376
+ BarColumn(),
1377
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
1378
+ TextColumn("[dim]({task.completed}/{task.total})[/dim]"),
1379
+ TextColumn("[dim]|[/dim]"),
1380
+ TextColumn("[green]✓ {task.fields[submitted]}[/green]"),
1381
+ TextColumn("[dim]○ {task.fields[skipped]}[/dim]"),
1382
+ TextColumn("[red]✗ {task.fields[failed]}[/red]"),
1383
+ console=console,
1384
+ ) as progress:
1385
+ task = progress.add_task(
1386
+ "Submitting sessions",
1387
+ total=len(submissions),
1388
+ submitted=0,
1389
+ skipped=0,
1390
+ failed=0,
1391
+ )
1392
+
1393
+ for idx, submission in enumerate(submissions):
1394
+ try:
1395
+ response = client.submit_session(submission)
1396
+
1397
+ # Handle nested response structure
1398
+ session_id: str | None = None
1399
+ if isinstance(response, dict):
1400
+ # Try direct access
1401
+ session_id = response.get("sessionId")
1402
+ # If not found, check nested data structure
1403
+ if not session_id and isinstance(response.get("data"), dict):
1404
+ session_id = response["data"].get("sessionId")
1405
+
1406
+ if session_id:
1407
+ stats["submitted"] += 1
1408
+ else:
1409
+ # Check for duplicate/already recorded
1410
+ msg = str(response.get("message", "")).lower()
1411
+ if "duplicate" in msg or "already" in msg:
1412
+ stats["skipped"] += 1
1413
+ else:
1414
+ # Log unexpected failure
1415
+ with error_lock:
1416
+ if len(error_samples) < 5:
1417
+ error_samples.append(f"No sessionId: {response}")
1418
+ stats["failed"] += 1
1419
+
1420
+ except (ApiError, RankClientError) as e:
1421
+ error_msg = str(e).lower()
1422
+ if "duplicate" in error_msg or "already" in error_msg:
1423
+ stats["skipped"] += 1
1424
+ elif "too soon" in error_msg or "rate limit" in error_msg:
1425
+ # Rate limit hit - wait and retry
1426
+ with error_lock:
1427
+ if len(error_samples) < 5:
1428
+ error_samples.append(f"Rate limited: {e}")
1429
+ sleep(2) # Wait before retry
1430
+ try:
1431
+ response = client.submit_session(submission)
1432
+ session_id = None
1433
+ if isinstance(response, dict):
1434
+ session_id = response.get("sessionId")
1435
+ if not session_id and isinstance(response.get("data"), dict):
1436
+ session_id = response["data"].get("sessionId")
1437
+ if session_id:
1438
+ stats["submitted"] += 1
1439
+ else:
1440
+ stats["failed"] += 1
1441
+ except Exception:
1442
+ stats["failed"] += 1
1443
+ else:
1444
+ # Log API errors
1445
+ with error_lock:
1446
+ if len(error_samples) < 20:
1447
+ error_samples.append(f"API Error: {e}")
1448
+ # Categorize error type
1449
+ err_str = str(e)
1450
+ if "too soon" in err_str.lower():
1451
+ error_type_counts["rate_limited"] = error_type_counts.get("rate_limited", 0) + 1
1452
+ elif "validation" in err_str.lower():
1453
+ error_type_counts["validation"] = error_type_counts.get("validation", 0) + 1
1454
+ else:
1455
+ error_type_counts["other_api"] = error_type_counts.get("other_api", 0) + 1
1456
+ logging.error(f"API Error: {e}")
1457
+ stats["failed"] += 1
1458
+ except Exception as e:
1459
+ # Log unexpected errors
1460
+ with error_lock:
1461
+ if len(error_samples) < 20:
1462
+ error_samples.append(f"Unexpected: {e}")
1463
+ error_type_counts["unexpected"] = error_type_counts.get("unexpected", 0) + 1
1464
+ logging.error(f"Unexpected error: {e}")
1465
+ stats["failed"] += 1
1466
+
1467
+ # Update progress bar
1468
+ progress.update(
1469
+ task,
1470
+ advance=1,
1471
+ submitted=stats["submitted"],
1472
+ skipped=stats["skipped"],
1473
+ failed=stats["failed"],
1474
+ )
1475
+
1476
+ # Small delay to avoid rate limiting
1477
+ if idx < len(submissions) - 1:
1478
+ sleep(submission_delay)
1479
+
1480
+ # Show error samples if there were failures
1481
+ if error_samples:
1482
+ console.print()
1483
+ console.print("[yellow]Sample errors:[/yellow]")
1484
+ for i, error in enumerate(error_samples, 1):
1485
+ console.print(f" [dim]{i}. {error}[/dim]")
1486
+
1487
+ # Show error type breakdown
1488
+ if error_type_counts:
1489
+ console.print()
1490
+ console.print("[yellow]Error breakdown:[/yellow]")
1491
+ for error_type, count in sorted(error_type_counts.items(), key=lambda x: -x[1]):
1492
+ console.print(f" [dim]{error_type}: {count}[/dim]")
1493
+
1494
+ # Print summary
1495
+ console.print()
1496
+ console.print("[bold]Sync Complete[/bold]")
1497
+ console.print(f" [green]✓ Submitted:[/green] {stats['submitted']}")
1498
+ console.print(f" [dim]○ Skipped:[/dim] {stats['skipped']} [dim](no usage or duplicate)[/dim]")
1499
+ if stats["failed"] > 0:
1500
+ console.print(f" [red]✗ Failed:[/red] {stats['failed']}")
1501
+ console.print()
1502
+
1503
+ return stats