moai-adk 0.8.0__py3-none-any.whl → 0.15.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (207) hide show
  1. moai_adk/cli/commands/init.py +14 -2
  2. moai_adk/cli/commands/update.py +229 -60
  3. moai_adk/core/config/migration.py +1 -1
  4. moai_adk/core/issue_creator.py +313 -0
  5. moai_adk/core/project/detector.py +201 -12
  6. moai_adk/core/project/initializer.py +62 -1
  7. moai_adk/core/project/phase_executor.py +48 -6
  8. moai_adk/core/tags/__init__.py +86 -0
  9. moai_adk/core/tags/ci_validator.py +463 -0
  10. moai_adk/core/tags/cli.py +283 -0
  11. moai_adk/core/tags/generator.py +109 -0
  12. moai_adk/core/tags/inserter.py +99 -0
  13. moai_adk/core/tags/mapper.py +126 -0
  14. moai_adk/core/tags/parser.py +76 -0
  15. moai_adk/core/tags/pre_commit_validator.py +393 -0
  16. moai_adk/core/tags/reporter.py +956 -0
  17. moai_adk/core/tags/tags.py +149 -0
  18. moai_adk/core/tags/validator.py +897 -0
  19. moai_adk/core/template_engine.py +268 -0
  20. moai_adk/templates/.claude/agents/alfred/backend-expert.md +319 -0
  21. moai_adk/templates/.claude/agents/alfred/cc-manager.md +25 -2
  22. moai_adk/templates/.claude/agents/alfred/debug-helper.md +24 -12
  23. moai_adk/templates/.claude/agents/alfred/devops-expert.md +464 -0
  24. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +20 -13
  25. moai_adk/templates/.claude/agents/alfred/frontend-expert.md +357 -0
  26. moai_adk/templates/.claude/agents/alfred/git-manager.md +47 -16
  27. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +95 -15
  28. moai_adk/templates/.claude/agents/alfred/project-manager.md +78 -12
  29. moai_adk/templates/.claude/agents/alfred/quality-gate.md +28 -5
  30. moai_adk/templates/.claude/agents/alfred/skill-factory.md +30 -2
  31. moai_adk/templates/.claude/agents/alfred/spec-builder.md +133 -13
  32. moai_adk/templates/.claude/agents/alfred/tag-agent.md +104 -8
  33. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +133 -16
  34. moai_adk/templates/.claude/agents/alfred/trust-checker.md +27 -4
  35. moai_adk/templates/.claude/agents/alfred/ui-ux-expert.md +571 -0
  36. moai_adk/templates/.claude/commands/alfred/0-project.md +466 -125
  37. moai_adk/templates/.claude/commands/alfred/1-plan.md +208 -71
  38. moai_adk/templates/.claude/commands/alfred/2-run.md +276 -55
  39. moai_adk/templates/.claude/commands/alfred/3-sync.md +439 -53
  40. moai_adk/templates/.claude/commands/alfred/9-feedback.md +149 -0
  41. moai_adk/templates/.claude/hooks/alfred/core/project.py +361 -29
  42. moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
  43. moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
  44. moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
  45. moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +14 -6
  46. moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +94 -0
  47. moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +100 -0
  48. moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +94 -0
  49. moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +94 -0
  50. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/__init__.py +2 -2
  51. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/checkpoint.py +3 -3
  52. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/context.py +5 -5
  53. moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +749 -0
  54. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/tags.py +55 -23
  55. moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
  56. moai_adk/templates/.claude/hooks/alfred/shared/handlers/__init__.py +21 -0
  57. moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +154 -0
  58. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/session.py +28 -15
  59. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/tool.py +3 -6
  60. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/user.py +19 -0
  61. moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +112 -0
  62. moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
  63. moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +161 -0
  64. moai_adk/templates/.claude/settings.json +5 -5
  65. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/SKILL.md +70 -0
  66. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/examples.md +62 -0
  67. moai_adk/templates/{.moai/memory/CLAUDE-AGENTS-GUIDE.md → .claude/skills/moai-alfred-agent-guide/reference.md} +34 -0
  68. moai_adk/templates/.claude/skills/moai-alfred-config-schema/SKILL.md +56 -0
  69. moai_adk/templates/.claude/skills/moai-alfred-config-schema/examples.md +28 -0
  70. moai_adk/templates/.claude/skills/moai-alfred-context-budget/SKILL.md +62 -0
  71. moai_adk/templates/.claude/skills/moai-alfred-context-budget/examples.md +28 -0
  72. moai_adk/templates/.claude/skills/moai-alfred-context-budget/reference.md +405 -0
  73. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/SKILL.md +51 -0
  74. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/examples.md +355 -0
  75. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/reference.md +239 -0
  76. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/SKILL.md +323 -0
  77. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/examples.md +286 -0
  78. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/reference.md +126 -0
  79. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/SKILL.md +74 -0
  80. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/examples.md +4 -0
  81. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/reference.md +269 -0
  82. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/SKILL.md +19 -0
  83. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/examples.md +4 -0
  84. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/reference.md +150 -0
  85. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/SKILL.md +198 -0
  86. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/examples.md +431 -0
  87. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/reference.md +141 -0
  88. moai_adk/templates/.claude/skills/moai-alfred-practices/SKILL.md +89 -0
  89. moai_adk/templates/.claude/skills/moai-alfred-practices/examples.md +122 -0
  90. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/SKILL.md +508 -0
  91. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/examples.md +481 -0
  92. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/reference.md +100 -0
  93. moai_adk/templates/.claude/skills/moai-alfred-reporting/SKILL.md +273 -0
  94. moai_adk/templates/.claude/skills/moai-alfred-rules/SKILL.md +77 -0
  95. moai_adk/templates/.claude/skills/moai-alfred-rules/examples.md +265 -0
  96. moai_adk/templates/.claude/skills/moai-alfred-session-state/SKILL.md +19 -0
  97. moai_adk/templates/.claude/skills/moai-alfred-session-state/examples.md +4 -0
  98. moai_adk/templates/.claude/skills/moai-alfred-session-state/reference.md +84 -0
  99. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/README.md +137 -0
  100. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/SKILL.md +219 -0
  101. moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/examples/validate-spec.sh +3 -3
  102. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples.md +541 -0
  103. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/reference.md +622 -0
  104. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/SKILL.md +115 -0
  105. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/examples.md +4 -0
  106. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/reference.md +348 -0
  107. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/SKILL.md +19 -0
  108. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/examples.md +4 -0
  109. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/reference.md +211 -0
  110. moai_adk/templates/.claude/skills/moai-alfred-workflow/SKILL.md +288 -0
  111. moai_adk/templates/.claude/skills/moai-cc-skill-descriptions/SKILL.md +19 -0
  112. moai_adk/templates/.claude/skills/moai-cc-skill-descriptions/examples.md +4 -0
  113. moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL.md +3 -3
  114. moai_adk/templates/.claude/skills/moai-design-systems/SKILL.md +802 -0
  115. moai_adk/templates/.claude/skills/moai-design-systems/examples.md +1238 -0
  116. moai_adk/templates/.claude/skills/moai-design-systems/reference.md +673 -0
  117. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +17 -13
  118. moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +9 -6
  119. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +15 -12
  120. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +14 -12
  121. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +14 -11
  122. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +10 -8
  123. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +15 -12
  124. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +13 -11
  125. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +16 -10
  126. moai_adk/templates/.claude/skills/moai-project-documentation.md +622 -0
  127. moai_adk/templates/.git-hooks/pre-push +143 -0
  128. moai_adk/templates/.github/workflows/c-tag-validation.yml +11 -0
  129. moai_adk/templates/.github/workflows/cpp-tag-validation.yml +11 -0
  130. moai_adk/templates/.github/workflows/csharp-tag-validation.yml +11 -0
  131. moai_adk/templates/.github/workflows/dart-tag-validation.yml +11 -0
  132. moai_adk/templates/.github/workflows/go-tag-validation.yml +130 -0
  133. moai_adk/templates/.github/workflows/java-tag-validation.yml +11 -0
  134. moai_adk/templates/.github/workflows/javascript-tag-validation.yml +135 -0
  135. moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +11 -0
  136. moai_adk/templates/.github/workflows/moai-gitflow.yml +166 -3
  137. moai_adk/templates/.github/workflows/moai-release-create.yml +100 -0
  138. moai_adk/templates/.github/workflows/moai-release-pipeline.yml +188 -0
  139. moai_adk/templates/.github/workflows/php-tag-validation.yml +11 -0
  140. moai_adk/templates/.github/workflows/python-tag-validation.yml +118 -0
  141. moai_adk/templates/.github/workflows/release.yml +118 -0
  142. moai_adk/templates/.github/workflows/ruby-tag-validation.yml +11 -0
  143. moai_adk/templates/.github/workflows/rust-tag-validation.yml +11 -0
  144. moai_adk/templates/.github/workflows/shell-tag-validation.yml +11 -0
  145. moai_adk/templates/.github/workflows/spec-issue-sync.yml +206 -35
  146. moai_adk/templates/.github/workflows/swift-tag-validation.yml +11 -0
  147. moai_adk/templates/.github/workflows/tag-report.yml +269 -0
  148. moai_adk/templates/.github/workflows/tag-validation.yml +186 -0
  149. moai_adk/templates/.github/workflows/typescript-tag-validation.yml +154 -0
  150. moai_adk/templates/.moai/config.json +21 -2
  151. moai_adk/templates/CLAUDE.md +972 -78
  152. moai_adk/templates/workflows/go-tag-validation.yml +30 -0
  153. moai_adk/templates/workflows/javascript-tag-validation.yml +41 -0
  154. moai_adk/templates/workflows/python-tag-validation.yml +42 -0
  155. moai_adk/templates/workflows/typescript-tag-validation.yml +31 -0
  156. moai_adk/utils/banner.py +5 -5
  157. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/METADATA +1518 -161
  158. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/RECORD +183 -100
  159. moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md +0 -313
  160. moai_adk/templates/.claude/hooks/alfred/README.md +0 -230
  161. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -174
  162. moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +0 -25
  163. moai_adk/templates/.claude/hooks/alfred/test_hook_output.py +0 -175
  164. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -640
  165. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -696
  166. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -474
  167. moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +0 -137
  168. moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +0 -218
  169. moai_adk/templates/.claude/skills/moai-spec-authoring/examples.md +0 -541
  170. moai_adk/templates/.claude/skills/moai-spec-authoring/reference.md +0 -622
  171. moai_adk/templates/.github/ISSUE_TEMPLATE/spec.yml +0 -176
  172. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
  173. moai_adk/templates/.moai/memory/DEVELOPMENT-GUIDE.md +0 -344
  174. moai_adk/templates/.moai/memory/GITFLOW-PROTECTION-POLICY.md +0 -220
  175. moai_adk/templates/.moai/memory/SPEC-METADATA.md +0 -356
  176. moai_adk/templates/.moai/memory/config-schema.md +0 -444
  177. moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
  178. moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
  179. moai_adk/templates/.moai/project/product.md +0 -161
  180. moai_adk/templates/.moai/project/structure.md +0 -156
  181. moai_adk/templates/.moai/project/tech.md +0 -227
  182. moai_adk/templates/__init__.py +0 -2
  183. /moai_adk/templates/{.moai/memory/CONFIG-SCHEMA.md → .claude/skills/moai-alfred-config-schema/reference.md} +0 -0
  184. /moai_adk/templates/{.moai/memory/CLAUDE-PRACTICES.md → .claude/skills/moai-alfred-practices/reference.md} +0 -0
  185. /moai_adk/templates/{.moai/memory/CLAUDE-RULES.md → .claude/skills/moai-alfred-rules/reference.md} +0 -0
  186. /moai_adk/templates/{.moai/memory/SKILLS-DESCRIPTION-POLICY.md → .claude/skills/moai-cc-skill-descriptions/reference.md} +0 -0
  187. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/CHECKLIST.md +0 -0
  188. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/EXAMPLES.md +0 -0
  189. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/INTERACTIVE-DISCOVERY.md +0 -0
  190. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/METADATA.md +0 -0
  191. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/PARALLEL-ANALYSIS-REPORT.md +0 -0
  192. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/PYTHON-VERSION-MATRIX.md +0 -0
  193. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL-FACTORY-WORKFLOW.md +0 -0
  194. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL-UPDATE-ADVISOR.md +0 -0
  195. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/STEP-BY-STEP-GUIDE.md +0 -0
  196. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/STRUCTURE.md +0 -0
  197. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/WEB-RESEARCH.md +0 -0
  198. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/reference.md +0 -0
  199. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/scripts/generate-structure.sh +0 -0
  200. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/scripts/validate-skill.sh +0 -0
  201. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/SKILL_TEMPLATE.md +0 -0
  202. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/examples-template.md +0 -0
  203. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/reference-template.md +0 -0
  204. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/scripts-template.sh +0 -0
  205. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/WHEEL +0 -0
  206. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/entry_points.txt +0 -0
  207. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/licenses/LICENSE +0 -0
@@ -38,9 +38,22 @@ from pathlib import Path
38
38
  from typing import Iterable, List, Optional
39
39
 
40
40
  DEFAULT_CODE_EXTS = (
41
- ".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs",
42
- ".java", ".kt", ".rb", ".php", ".c", ".cpp", ".cs",
43
- ".swift", ".scala"
41
+ ".py",
42
+ ".ts",
43
+ ".tsx",
44
+ ".js",
45
+ ".jsx",
46
+ ".go",
47
+ ".rs",
48
+ ".java",
49
+ ".kt",
50
+ ".rb",
51
+ ".php",
52
+ ".c",
53
+ ".cpp",
54
+ ".cs",
55
+ ".swift",
56
+ ".scala",
44
57
  )
45
58
 
46
59
 
@@ -88,35 +101,49 @@ def _load_rules(cwd: str) -> List[Rule]:
88
101
 
89
102
  # Defaults (ordered)
90
103
  return [
91
- Rule(
92
- include=[".moai/specs/**", "**/SPEC-*/spec.md"],
93
- expect="@SPEC:",
94
- exclude=[]
95
- ),
104
+ Rule(include=[".moai/specs/**", "**/SPEC-*/spec.md"], expect="@SPEC:", exclude=[]),
96
105
  Rule(
97
106
  include=[
98
- "**/*_test.py", "**/test_*.py", "**/*.test.ts",
99
- "**/*.test.tsx", "**/*.test.js", "**/*.test.jsx",
100
- "**/*.test.go", "**/*.test.rs", "**/*.spec.ts",
101
- "**/*.spec.tsx", "tests/**"
107
+ "**/*_test.py",
108
+ "**/test_*.py",
109
+ "**/*.test.ts",
110
+ "**/*.test.tsx",
111
+ "**/*.test.js",
112
+ "**/*.test.jsx",
113
+ "**/*.test.go",
114
+ "**/*.test.rs",
115
+ "**/*.spec.ts",
116
+ "**/*.spec.tsx",
117
+ "tests/**",
102
118
  ],
103
119
  expect="@TEST:",
104
- exclude=[".claude/**"]
120
+ exclude=[".claude/**"],
105
121
  ),
106
122
  Rule(
107
123
  include=["docs/**/*.md", "**/README.md", "**/*.api.md"],
108
124
  expect="@DOC:",
109
- exclude=[".claude/**"]
125
+ exclude=[".claude/**"],
110
126
  ),
111
127
  Rule(
112
128
  include=["**/*"],
113
129
  expect="@CODE:",
114
130
  exclude=[
115
- "tests/**", "docs/**", ".moai/**", ".claude/**",
116
- "**/*.md", "**/*.json", "**/*.yml", "**/*.yaml",
117
- "**/*.toml", "**/*.lock", "**/*.svg", "**/*.png",
118
- "**/*.jpg", "**/*.jpeg", "**/*.gif"
119
- ]
131
+ "tests/**",
132
+ "docs/**",
133
+ ".moai/**",
134
+ ".claude/**",
135
+ "**/*.md",
136
+ "**/*.json",
137
+ "**/*.yml",
138
+ "**/*.yaml",
139
+ "**/*.toml",
140
+ "**/*.lock",
141
+ "**/*.svg",
142
+ "**/*.png",
143
+ "**/*.jpg",
144
+ "**/*.jpeg",
145
+ "**/*.gif",
146
+ ],
120
147
  ),
121
148
  ]
122
149
 
@@ -147,17 +174,22 @@ def _iter_recent_changes(cwd: str) -> Iterable[Path]:
147
174
  # Staged files
148
175
  r1 = subprocess.run(
149
176
  ["git", "diff", "--name-only", "--cached"],
150
- cwd=cwd, capture_output=True, text=True, timeout=1
177
+ cwd=cwd,
178
+ capture_output=True,
179
+ text=True,
180
+ timeout=1,
151
181
  )
152
182
  # Modified (unstaged) tracked files
153
183
  r2 = subprocess.run(
154
- ["git", "ls-files", "-m"],
155
- cwd=cwd, capture_output=True, text=True, timeout=1
184
+ ["git", "ls-files", "-m"], cwd=cwd, capture_output=True, text=True, timeout=1
156
185
  )
157
186
  # Untracked (other) files respecting .gitignore
158
187
  r3 = subprocess.run(
159
188
  ["git", "ls-files", "-o", "--exclude-standard"],
160
- cwd=cwd, capture_output=True, text=True, timeout=1
189
+ cwd=cwd,
190
+ capture_output=True,
191
+ text=True,
192
+ timeout=1,
161
193
  )
162
194
  names = set()
163
195
  if r1.returncode == 0:
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # @CODE:VERSION-CACHE-001
3
+ """Version information cache with TTL support
4
+
5
+ TTL-based caching system for version check results to minimize network calls
6
+ during SessionStart hook execution.
7
+
8
+ SPEC: SPEC-UPDATE-ENHANCE-001 - SessionStart 버전 체크 시스템 강화
9
+ Phase 1: Cache System Implementation
10
+ """
11
+
12
+ import json
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+
18
+ class VersionCache:
19
+ """TTL-based version information cache
20
+
21
+ Caches version check results with configurable Time-To-Live (TTL)
22
+ to avoid excessive network calls to PyPI during SessionStart events.
23
+
24
+ Attributes:
25
+ cache_dir: Directory to store cache file
26
+ ttl_hours: Time-to-live in hours (default 24)
27
+ cache_file: Path to the cache JSON file
28
+
29
+ Examples:
30
+ >>> cache = VersionCache(Path(".moai/cache"), ttl_hours=24)
31
+ >>> cache.save({"current_version": "0.8.1", "latest_version": "0.9.0"})
32
+ True
33
+ >>> cache.is_valid()
34
+ True
35
+ >>> data = cache.load()
36
+ >>> data["current_version"]
37
+ '0.8.1'
38
+ """
39
+
40
+ def __init__(self, cache_dir: Path, ttl_hours: int = 4):
41
+ """Initialize cache with TTL in hours
42
+
43
+ Args:
44
+ cache_dir: Directory where cache file will be stored
45
+ ttl_hours: Time-to-live in hours (default 4)
46
+ """
47
+ self.cache_dir = Path(cache_dir)
48
+ self.ttl_hours = ttl_hours
49
+ self.cache_file = self.cache_dir / "version-check.json"
50
+
51
+ def _calculate_age_hours(self, last_check_iso: str) -> float:
52
+ """Calculate age in hours from ISO timestamp (internal helper)
53
+
54
+ Normalizes timezone-aware and naive datetimes for consistent comparison.
55
+
56
+ Args:
57
+ last_check_iso: ISO format timestamp string
58
+
59
+ Returns:
60
+ Age in hours
61
+
62
+ Raises:
63
+ ValueError: If timestamp parsing fails
64
+ """
65
+ last_check = datetime.fromisoformat(last_check_iso)
66
+
67
+ # Normalize to naive datetime (remove timezone for comparison)
68
+ if last_check.tzinfo is not None:
69
+ last_check = last_check.replace(tzinfo=None)
70
+
71
+ now = datetime.now()
72
+ return (now - last_check).total_seconds() / 3600
73
+
74
+ def is_valid(self) -> bool:
75
+ """Check if cache exists and is not expired
76
+
77
+ Returns:
78
+ True if cache file exists and is within TTL, False otherwise
79
+
80
+ Examples:
81
+ >>> cache = VersionCache(Path(".moai/cache"))
82
+ >>> cache.is_valid()
83
+ False # No cache file exists yet
84
+ """
85
+ if not self.cache_file.exists():
86
+ return False
87
+
88
+ try:
89
+ with open(self.cache_file, "r") as f:
90
+ data = json.load(f)
91
+
92
+ age_hours = self._calculate_age_hours(data["last_check"])
93
+ return age_hours < self.ttl_hours
94
+
95
+ except (json.JSONDecodeError, KeyError, ValueError, OSError):
96
+ # Corrupted or invalid cache file
97
+ return False
98
+
99
+ def load(self) -> dict[str, Any] | None:
100
+ """Load cached version info if valid
101
+
102
+ Returns:
103
+ Cached version info dictionary if valid, None otherwise
104
+
105
+ Examples:
106
+ >>> cache = VersionCache(Path(".moai/cache"))
107
+ >>> data = cache.load()
108
+ >>> data is None
109
+ True # No valid cache exists
110
+ """
111
+ if not self.is_valid():
112
+ return None
113
+
114
+ try:
115
+ with open(self.cache_file, "r") as f:
116
+ return json.load(f)
117
+ except (json.JSONDecodeError, OSError):
118
+ # Graceful degradation on read errors
119
+ return None
120
+
121
+ def save(self, version_info: dict[str, Any]) -> bool:
122
+ """Save version info to cache file
123
+
124
+ Creates cache directory if it doesn't exist.
125
+ Updates last_check timestamp to current time if not provided.
126
+
127
+ Args:
128
+ version_info: Version information dictionary to cache
129
+
130
+ Returns:
131
+ True on successful save, False on error
132
+
133
+ Examples:
134
+ >>> cache = VersionCache(Path(".moai/cache"))
135
+ >>> cache.save({"current_version": "0.8.1"})
136
+ True
137
+ """
138
+ try:
139
+ # Create cache directory if it doesn't exist
140
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
141
+
142
+ # Update last_check timestamp only if not provided (for testing)
143
+ if "last_check" not in version_info:
144
+ version_info["last_check"] = datetime.now(timezone.utc).isoformat()
145
+
146
+ # Write to cache file
147
+ with open(self.cache_file, "w") as f:
148
+ json.dump(version_info, f, indent=2)
149
+
150
+ return True
151
+
152
+ except (OSError, TypeError):
153
+ # Graceful degradation on write errors
154
+ return False
155
+
156
+ def clear(self) -> bool:
157
+ """Clear/remove cache file
158
+
159
+ Returns:
160
+ True if cache file was removed or didn't exist, False on error
161
+
162
+ Examples:
163
+ >>> cache = VersionCache(Path(".moai/cache"))
164
+ >>> cache.clear()
165
+ True
166
+ """
167
+ try:
168
+ if self.cache_file.exists():
169
+ self.cache_file.unlink()
170
+ return True
171
+ except OSError:
172
+ return False
173
+
174
+ def get_age_hours(self) -> float:
175
+ """Get age of cache in hours
176
+
177
+ Returns:
178
+ Age in hours, or 0.0 if cache doesn't exist or is invalid
179
+
180
+ Examples:
181
+ >>> cache = VersionCache(Path(".moai/cache"))
182
+ >>> cache.get_age_hours()
183
+ 0.0 # No cache exists
184
+ """
185
+ if not self.cache_file.exists():
186
+ return 0.0
187
+
188
+ try:
189
+ with open(self.cache_file, "r") as f:
190
+ data = json.load(f)
191
+
192
+ return self._calculate_age_hours(data["last_check"])
193
+
194
+ except (json.JSONDecodeError, KeyError, ValueError, OSError):
195
+ return 0.0
196
+
197
+
198
+ __all__ = ["VersionCache"]
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python3
2
+ """Event handlers for Alfred Hooks
3
+
4
+ Claude Code Event Handlers
5
+ """
6
+
7
+ from .notification import handle_notification, handle_stop, handle_subagent_stop
8
+ from .session import handle_session_end, handle_session_start
9
+ from .tool import handle_post_tool_use, handle_pre_tool_use
10
+ from .user import handle_user_prompt_submit
11
+
12
+ __all__ = [
13
+ "handle_session_start",
14
+ "handle_session_end",
15
+ "handle_user_prompt_submit",
16
+ "handle_pre_tool_use",
17
+ "handle_post_tool_use",
18
+ "handle_notification",
19
+ "handle_stop",
20
+ "handle_subagent_stop",
21
+ ]
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ """Notification and control handlers
3
+
4
+ Notification, Stop, SubagentStop event handling
5
+ """
6
+
7
+ import json
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+
11
+ from core import HookPayload, HookResult
12
+
13
+
14
+ def _get_command_state_file(cwd: str) -> Path:
15
+ """Get the path to command state tracking file"""
16
+ state_dir = Path(cwd) / ".moai" / "memory"
17
+ state_dir.mkdir(parents=True, exist_ok=True)
18
+ return state_dir / "command-execution-state.json"
19
+
20
+
21
+ def _load_command_state(cwd: str) -> dict:
22
+ """Load current command execution state"""
23
+ try:
24
+ state_file = _get_command_state_file(cwd)
25
+ if state_file.exists():
26
+ with open(state_file, "r", encoding="utf-8") as f:
27
+ return json.load(f)
28
+ except Exception:
29
+ pass
30
+ return {"last_command": None, "last_timestamp": None, "is_running": False}
31
+
32
+
33
+ def _save_command_state(cwd: str, state: dict) -> None:
34
+ """Save command execution state"""
35
+ try:
36
+ state_file = _get_command_state_file(cwd)
37
+ with open(state_file, "w", encoding="utf-8") as f:
38
+ json.dump(state, f, indent=2)
39
+ except Exception:
40
+ pass
41
+
42
+
43
+ def _is_duplicate_command(current_cmd: str, last_cmd: str, last_timestamp: str) -> bool:
44
+ """Check if current command is a duplicate of the last one within 3 seconds"""
45
+ if not last_cmd or not last_timestamp or current_cmd != last_cmd:
46
+ return False
47
+
48
+ try:
49
+ last_time = datetime.fromisoformat(last_timestamp)
50
+ current_time = datetime.now()
51
+ time_diff = (current_time - last_time).total_seconds()
52
+ # Consider it a duplicate if same command within 3 seconds
53
+ return time_diff < 3
54
+ except Exception:
55
+ return False
56
+
57
+
58
+ def handle_notification(payload: HookPayload) -> HookResult:
59
+ """Notification event handler
60
+
61
+ Detects and warns about duplicate command executions
62
+ (When the same /alfred: command is triggered multiple times within 3 seconds)
63
+ """
64
+ cwd = payload.get("cwd", ".")
65
+ notification = payload.get("notification", {})
66
+
67
+ # Extract command information from notification
68
+ current_cmd = None
69
+ if isinstance(notification, dict):
70
+ # Check if notification contains command information
71
+ text = notification.get("text", "") or str(notification)
72
+ if "/alfred:" in text:
73
+ # Extract /alfred: command
74
+ import re
75
+
76
+ match = re.search(r"/alfred:\S+", text)
77
+ if match:
78
+ current_cmd = match.group()
79
+
80
+ if not current_cmd:
81
+ return HookResult()
82
+
83
+ # Load current state
84
+ state = _load_command_state(cwd)
85
+ last_cmd = state.get("last_command")
86
+ last_timestamp = state.get("last_timestamp")
87
+
88
+ # Check for duplicate
89
+ if _is_duplicate_command(current_cmd, last_cmd, last_timestamp):
90
+ warning_msg = (
91
+ f"⚠️ Duplicate command detected: '{current_cmd}' "
92
+ f"is running multiple times within 3 seconds.\n"
93
+ f"This may indicate a system issue. Check logs in `.moai/logs/command-invocations.log`"
94
+ )
95
+
96
+ # Update state - mark as duplicate detected
97
+ state["duplicate_detected"] = True
98
+ state["duplicate_command"] = current_cmd
99
+ state["duplicate_timestamp"] = datetime.now().isoformat()
100
+ _save_command_state(cwd, state)
101
+
102
+ return HookResult(system_message=warning_msg, continue_execution=True)
103
+
104
+ # Update state with current command
105
+ state["last_command"] = current_cmd
106
+ state["last_timestamp"] = datetime.now().isoformat()
107
+ state["is_running"] = True
108
+ state["duplicate_detected"] = False
109
+ _save_command_state(cwd, state)
110
+
111
+ return HookResult()
112
+
113
+
114
+ def handle_stop(payload: HookPayload) -> HookResult:
115
+ """Stop event handler
116
+
117
+ Marks command execution as complete
118
+ """
119
+ cwd = payload.get("cwd", ".")
120
+ state = _load_command_state(cwd)
121
+ state["is_running"] = False
122
+ state["last_timestamp"] = datetime.now().isoformat()
123
+ _save_command_state(cwd, state)
124
+
125
+ return HookResult()
126
+
127
+
128
+ def handle_subagent_stop(payload: HookPayload) -> HookResult:
129
+ """SubagentStop event handler
130
+
131
+ Records when a sub-agent finishes execution
132
+ """
133
+ cwd = payload.get("cwd", ".")
134
+
135
+ # Extract subagent name if available
136
+ subagent_name = (
137
+ payload.get("subagent", {}).get("name")
138
+ if isinstance(payload.get("subagent"), dict)
139
+ else None
140
+ )
141
+
142
+ try:
143
+ state_file = _get_command_state_file(cwd).parent / "subagent-execution.log"
144
+ timestamp = datetime.now().isoformat()
145
+
146
+ with open(state_file, "a", encoding="utf-8") as f:
147
+ f.write(f"{timestamp} | Subagent Stop | {subagent_name}\n")
148
+ except Exception:
149
+ pass
150
+
151
+ return HookResult()
152
+
153
+
154
+ __all__ = ["handle_notification", "handle_stop", "handle_subagent_stop"]
@@ -6,7 +6,7 @@ SessionStart, SessionEnd event handling
6
6
 
7
7
  from core import HookPayload, HookResult
8
8
  from core.checkpoint import list_checkpoints
9
- from core.project import count_specs, detect_language, get_git_info, get_package_version_info
9
+ from core.project import count_specs, get_git_info, get_package_version_info
10
10
 
11
11
 
12
12
  def handle_session_start(payload: HookPayload) -> HookResult:
@@ -25,14 +25,14 @@ def handle_session_start(payload: HookPayload) -> HookResult:
25
25
 
26
26
  Message Format:
27
27
  🚀 MoAI-ADK Session Started
28
- Language: {language}
28
+ [Version: {version}] - optional if version check fails
29
29
  [Branch: {branch} ({commit hash})] - optional if git fails
30
30
  [Changes: {Number of Changed Files}] - optional if git fails
31
31
  [SPEC Progress: {Complete}/{Total} ({percent}%)] - optional if specs fail
32
32
  [Checkpoints: {number} available] - optional if checkpoint list fails
33
33
 
34
34
  Graceful Degradation Strategy:
35
- - CRITICAL: Language detection (must succeed - no try-except)
35
+ - OPTIONAL: Version info (skip if timeout/failure)
36
36
  - OPTIONAL: Git info (skip if timeout/failure)
37
37
  - OPTIONAL: SPEC progress (skip if timeout/failure)
38
38
  - OPTIONAL: Checkpoint list (skip if timeout/failure)
@@ -51,9 +51,11 @@ def handle_session_start(payload: HookPayload) -> HookResult:
51
51
  - FIX: Prevent duplicate output of clear step (only compact step is displayed)
52
52
  - UPDATE: Migrated to Claude Code standard Hook schema
53
53
  - HOTFIX: Add graceful degradation for timeout scenarios (Issue #66)
54
+ - Phase 3: Add major version warning and release notes display (@TEST:MAJOR-UPDATE-001-07/08)
54
55
 
55
56
  @TAG:CHECKPOINT-EVENT-001
56
57
  @TAG:HOOKS-TIMEOUT-001
58
+ @CODE:MAJOR-UPDATE-WARN-001
57
59
  """
58
60
  # Claude Code SessionStart runs in several stages (clear, compact, etc.)
59
61
  # Ignore the "clear" stage and output messages only at the "compact" stage
@@ -64,9 +66,6 @@ def handle_session_start(payload: HookPayload) -> HookResult:
64
66
 
65
67
  cwd = payload.get("cwd", ".")
66
68
 
67
- # CRITICAL: Language detection - MUST succeed (no try-except)
68
- language = detect_language(cwd)
69
-
70
69
  # OPTIONAL: Git info - skip if timeout/failure
71
70
  git_info = {}
72
71
  try:
@@ -113,17 +112,31 @@ def handle_session_start(payload: HookPayload) -> HookResult:
113
112
 
114
113
  # Add version info first (at the top, right after title)
115
114
  if version_info and version_info.get("current") != "unknown":
116
- version_line = f" 🗿 MoAI-ADK Ver: {version_info['current']}"
117
115
  if version_info.get("update_available"):
118
- version_line += f" {version_info['latest']} available ✨"
119
- lines.append(version_line)
120
-
121
- # Add upgrade recommendation if update is available
122
- if version_info.get("update_available") and version_info.get("upgrade_command"):
123
- lines.append(f" ⬆️ Upgrade: {version_info['upgrade_command']}")
116
+ # Check if this is a major version update
117
+ if version_info.get("is_major_update"):
118
+ # Major version warning
119
+ lines.append(
120
+ f" ⚠️ Major version update available: {version_info['current']} {version_info['latest']}"
121
+ )
122
+ lines.append(" Breaking changes detected. Review release notes:")
123
+ if version_info.get("release_notes_url"):
124
+ lines.append(f" 📝 {version_info['release_notes_url']}")
125
+ else:
126
+ # Regular update
127
+ lines.append(
128
+ f" 🗿 MoAI-ADK Ver: {version_info['current']} → {version_info['latest']} available ✨"
129
+ )
130
+ if version_info.get("release_notes_url"):
131
+ lines.append(f" 📝 Release Notes: {version_info['release_notes_url']}")
132
+
133
+ # Add upgrade recommendation
134
+ if version_info.get("upgrade_command"):
135
+ lines.append(f" ⬆️ Upgrade: {version_info['upgrade_command']}")
136
+ else:
137
+ # No update available - show current version only
138
+ lines.append(f" 🗿 MoAI-ADK Ver: {version_info['current']}")
124
139
 
125
- # Add language info
126
- lines.append(f" 🐍 Language: {language}")
127
140
 
128
141
  # Add Git info only if available (not degraded)
129
142
  if git_info:
@@ -54,8 +54,7 @@ def handle_pre_tool_use(payload: HookPayload) -> HookResult:
54
54
  checkpoint_branch = create_checkpoint(cwd, operation_type)
55
55
  if checkpoint_branch != "checkpoint-failed":
56
56
  system_message = (
57
- f"🛡️ Checkpoint created: {checkpoint_branch}\n"
58
- f" Operation: {operation_type}"
57
+ f"🛡️ Checkpoint created: {checkpoint_branch}\n Operation: {operation_type}"
59
58
  )
60
59
  return HookResult(system_message=system_message, continue_execution=True)
61
60
  except Exception:
@@ -66,10 +65,8 @@ def handle_pre_tool_use(payload: HookPayload) -> HookResult:
66
65
  issues = scan_recent_changes_for_missing_tags(cwd)
67
66
  if issues:
68
67
  # Summarize first few issues for display
69
- preview = "\n".join(
70
- f" - {i.path} 기대 태그: {i.expected}" for i in issues[:5]
71
- )
72
- more = "" if len(issues) <= 5 else f"\n (외 {len(issues)-5}건 더 존재)"
68
+ preview = "\n".join(f" - {i.path} → 기대 태그: {i.expected}" for i in issues[:5])
69
+ more = "" if len(issues) <= 5 else f"\n (외 {len(issues) - 5}건 더 존재)"
73
70
  msg = (
74
71
  "⚠️ TAG 누락 감지: 생성/수정한 파일 중 @TAG가 없는 항목이 있습니다.\n"
75
72
  f"{preview}{more}\n"
@@ -4,6 +4,9 @@
4
4
  Handling the UserPromptSubmit event
5
5
  """
6
6
 
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
7
10
  from core import HookPayload, HookResult
8
11
  from core.context import get_jit_context
9
12
 
@@ -29,11 +32,27 @@ def handle_user_prompt_submit(payload: HookPayload) -> HookResult:
29
32
  - GREEN: Recommend documents by calling get_jit_context()
30
33
  - REFACTOR: Message conditional display (only when there is a file)
31
34
  - UPDATE: Migrated to Claude Code standard Hook schema with snake_case fields
35
+ - FEATURE: Command execution logging for tracking double-run debugging
32
36
  """
33
37
  user_prompt = payload.get("userPrompt", "")
34
38
  cwd = payload.get("cwd", ".")
35
39
  context_files = get_jit_context(user_prompt, cwd)
36
40
 
41
+ # Command execution logging (DEBUG feature for tracking invocations)
42
+ if user_prompt.startswith("/alfred:"):
43
+ try:
44
+ log_dir = Path(cwd) / ".moai" / "logs"
45
+ log_dir.mkdir(parents=True, exist_ok=True)
46
+
47
+ log_file = log_dir / "command-invocations.log"
48
+ timestamp = datetime.now().isoformat()
49
+
50
+ with open(log_file, "a", encoding="utf-8") as f:
51
+ f.write(f"{timestamp} | {user_prompt}\n")
52
+ except Exception:
53
+ # Silently fail if logging fails (don't interrupt main flow)
54
+ pass
55
+
37
56
  system_message = f"📎 Loaded {len(context_files)} context file(s)" if context_files else None
38
57
 
39
58
  return HookResult(system_message=system_message, context_files=context_files)