moai-adk 0.4.7__py3-none-any.whl → 0.4.10__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 (103) hide show
  1. moai_adk/templates/.claude/agents/alfred/cc-manager.md +188 -954
  2. moai_adk/templates/.claude/agents/alfred/debug-helper.md +4 -5
  3. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +2 -2
  4. moai_adk/templates/.claude/agents/alfred/git-manager.md +2 -2
  5. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +3 -3
  6. moai_adk/templates/.claude/agents/alfred/project-manager.md +6 -6
  7. moai_adk/templates/.claude/agents/alfred/quality-gate.md +4 -6
  8. moai_adk/templates/.claude/agents/alfred/skill-factory.md +36 -36
  9. moai_adk/templates/.claude/agents/alfred/spec-builder.md +2 -2
  10. moai_adk/templates/.claude/agents/alfred/tag-agent.md +2 -2
  11. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +3 -3
  12. moai_adk/templates/.claude/agents/alfred/trust-checker.md +5 -5
  13. moai_adk/templates/.claude/commands/alfred/0-project.md +98 -42
  14. moai_adk/templates/.claude/commands/alfred/1-plan.md +7 -7
  15. moai_adk/templates/.claude/commands/alfred/2-run.md +36 -36
  16. moai_adk/templates/.claude/commands/alfred/3-sync.md +10 -10
  17. moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md +313 -0
  18. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +14 -1
  19. moai_adk/templates/.claude/hooks/alfred/core/__init__.py +117 -33
  20. moai_adk/templates/.claude/hooks/alfred/handlers/session.py +8 -9
  21. moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +6 -6
  22. moai_adk/templates/.claude/hooks/alfred/handlers/user.py +5 -4
  23. moai_adk/templates/.claude/hooks/alfred/test_hook_output.py +175 -0
  24. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +1 -1
  25. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +1 -1
  26. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +1 -1
  27. moai_adk/templates/.claude/settings.json +42 -2
  28. moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/SKILL.md +480 -0
  29. moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/examples.md +257 -0
  30. moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/reference.md +120 -0
  31. moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +249 -0
  32. moai_adk/templates/.claude/skills/moai-cc-agents/templates/agent-template.md +32 -0
  33. moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +278 -0
  34. moai_adk/templates/.claude/skills/moai-cc-claude-md/templates/CLAUDE-template.md +26 -0
  35. moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +287 -0
  36. moai_adk/templates/.claude/skills/moai-cc-commands/templates/command-template.md +21 -0
  37. moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +232 -0
  38. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/pre-bash-check.sh +19 -0
  39. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/preserve-permissions.sh +19 -0
  40. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/validate-bash-command.py +24 -0
  41. moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +179 -0
  42. moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/templates/settings-mcp-template.json +39 -0
  43. moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +296 -0
  44. moai_adk/templates/.claude/skills/moai-cc-memory/templates/session-summary-template.md +18 -0
  45. moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +243 -0
  46. moai_adk/templates/.claude/skills/moai-cc-settings/templates/settings-complete-template.json +30 -0
  47. moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +271 -0
  48. moai_adk/templates/.claude/skills/moai-cc-skills/templates/SKILL-template.md +15 -0
  49. moai_adk/templates/.claude/skills/moai-skill-factory/EXAMPLES.md +261 -35
  50. moai_adk/templates/.claude/skills/moai-skill-factory/INTERACTIVE-DISCOVERY.md +15 -15
  51. moai_adk/templates/.claude/skills/moai-skill-factory/PARALLEL-ANALYSIS-REPORT.md +429 -0
  52. moai_adk/templates/.claude/skills/moai-skill-factory/PYTHON-VERSION-MATRIX.md +391 -0
  53. moai_adk/templates/.claude/skills/moai-skill-factory/SKILL-FACTORY-WORKFLOW.md +431 -0
  54. moai_adk/templates/.claude/skills/moai-skill-factory/SKILL.md +33 -33
  55. moai_adk/templates/.claude/skills/moai-skill-factory/STEP-BY-STEP-GUIDE.md +466 -0
  56. moai_adk/templates/.claude/skills/moai-skill-factory/reference.md +438 -42
  57. moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +129 -0
  58. moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +1300 -0
  59. moai_adk/templates/.claude/skills/moai-spec-authoring/examples/validate-spec.sh +161 -0
  60. moai_adk/templates/CLAUDE.md +111 -92
  61. {moai_adk-0.4.7.dist-info → moai_adk-0.4.10.dist-info}/METADATA +542 -150
  62. {moai_adk-0.4.7.dist-info → moai_adk-0.4.10.dist-info}/RECORD +65 -73
  63. moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/SKILL.md +0 -113
  64. moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/examples.md +0 -29
  65. moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/reference.md +0 -28
  66. moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/SKILL.md +0 -113
  67. moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/examples.md +0 -29
  68. moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/reference.md +0 -28
  69. moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/SKILL.md +0 -113
  70. moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/examples.md +0 -29
  71. moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/reference.md +0 -28
  72. moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/SKILL.md +0 -113
  73. moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/examples.md +0 -29
  74. moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/reference.md +0 -28
  75. moai_adk/templates/.claude/skills/moai-alfred-tui-survey/SKILL.md +0 -635
  76. moai_adk/templates/.claude/skills/moai-alfred-tui-survey/examples.md +0 -992
  77. moai_adk/templates/.claude/skills/moai-alfred-tui-survey/reference.md +0 -801
  78. moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +0 -121
  79. moai_adk/templates/.claude/skills/moai-claude-code/examples.md +0 -29
  80. moai_adk/templates/.claude/skills/moai-claude-code/reference.md +0 -28
  81. moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +0 -332
  82. moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +0 -384
  83. moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +0 -363
  84. moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +0 -595
  85. moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +0 -496
  86. moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +0 -123
  87. moai_adk/templates/.claude/skills/moai-lang-clojure/examples.md +0 -29
  88. moai_adk/templates/.claude/skills/moai-lang-clojure/reference.md +0 -30
  89. moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +0 -124
  90. moai_adk/templates/.claude/skills/moai-lang-elixir/examples.md +0 -29
  91. moai_adk/templates/.claude/skills/moai-lang-elixir/reference.md +0 -31
  92. moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +0 -124
  93. moai_adk/templates/.claude/skills/moai-lang-haskell/examples.md +0 -29
  94. moai_adk/templates/.claude/skills/moai-lang-haskell/reference.md +0 -31
  95. moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +0 -123
  96. moai_adk/templates/.claude/skills/moai-lang-julia/examples.md +0 -29
  97. moai_adk/templates/.claude/skills/moai-lang-julia/reference.md +0 -30
  98. moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +0 -123
  99. moai_adk/templates/.claude/skills/moai-lang-lua/examples.md +0 -29
  100. moai_adk/templates/.claude/skills/moai-lang-lua/reference.md +0 -30
  101. {moai_adk-0.4.7.dist-info → moai_adk-0.4.10.dist-info}/WHEEL +0 -0
  102. {moai_adk-0.4.7.dist-info → moai_adk-0.4.10.dist-info}/entry_points.txt +0 -0
  103. {moai_adk-0.4.7.dist-info → moai_adk-0.4.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,313 @@
1
+ # Hook JSON 스키마 검증 및 해결 보고서
2
+
3
+ **작성일**: 2025-10-23
4
+ **태그**: @CODE:HOOKS-REFACTOR-001
5
+ **상태**: ✅ 해결 완료
6
+
7
+ ---
8
+
9
+ ## 📋 문제 요약
10
+
11
+ ### 초기 오류
12
+ ```
13
+ SessionStart:startup hook error: JSON validation failed: Hook JSON output validation failed
14
+ Expected schema: { ... "systemMessage": ... }
15
+ ```
16
+
17
+ ### 근본 원인
18
+ Claude Code Hook 스키마에서 `systemMessage`가 **최상위 필드**여야 하지만, 일부 구현에서는 이를 `hookSpecificOutput` 내부에 중첩시키고 있었습니다.
19
+
20
+ ---
21
+
22
+ ## 🔍 분석 결과
23
+
24
+ ### Claude Code 공식 Hook 스키마
25
+
26
+ #### 1. 일반 Hook 이벤트 (SessionStart, PreToolUse, PostToolUse, SessionEnd 등)
27
+
28
+ ```json
29
+ {
30
+ "continue": true|false, // ✅ 기본 필드
31
+ "systemMessage": "string", // ✅ 최상위 필드 (NOT in hookSpecificOutput)
32
+ "decision": "approve"|"block"|undefined, // ✅ 선택적
33
+ "reason": "string", // ✅ 선택적
34
+ "permissionDecision": "allow"|"deny"|"ask"|undefined, // ✅ 선택적
35
+ "suppressOutput": true|false // ✅ 선택적
36
+ }
37
+ ```
38
+
39
+ #### 2. UserPromptSubmit 전용 스키마
40
+
41
+ ```json
42
+ {
43
+ "continue": true,
44
+ "hookSpecificOutput": { // ✅ UserPromptSubmit에만 사용
45
+ "hookEventName": "UserPromptSubmit",
46
+ "additionalContext": "string"
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### 핵심 규칙
52
+
53
+ | 규칙 | 설명 |
54
+ |------|------|
55
+ | **systemMessage 위치** | 최상위 필드 (`output["systemMessage"]`) |
56
+ | **hookSpecificOutput** | UserPromptSubmit 전용 |
57
+ | **내부 필드** | `context_files`, `suggestions`, `exit_code`는 Python 로직용 (JSON 출력 제외) |
58
+ | **JSON 직렬화** | 모든 필드는 JSON 직렬화 가능해야 함 |
59
+
60
+ ---
61
+
62
+ ## ✅ 해결 방안
63
+
64
+ ### 1. 코드 수정
65
+
66
+ **파일**: `.claude/hooks/alfred/core/__init__.py`
67
+
68
+ #### `to_dict()` 메서드 (라인 63-118)
69
+ ```python
70
+ def to_dict(self) -> dict[str, Any]:
71
+ """Claude Code 표준 Hook 출력 스키마로 변환"""
72
+ output: dict[str, Any] = {}
73
+
74
+ # 1. decision 또는 continue 추가
75
+ if self.decision:
76
+ output["decision"] = self.decision
77
+ else:
78
+ output["continue"] = self.continue_execution
79
+
80
+ # 2. reason 추가 (decision 또는 permissionDecision과 함께)
81
+ if self.reason:
82
+ output["reason"] = self.reason
83
+
84
+ # 3. suppressOutput 추가 (True인 경우만)
85
+ if self.suppress_output:
86
+ output["suppressOutput"] = True
87
+
88
+ # 4. permissionDecision 추가
89
+ if self.permission_decision:
90
+ output["permissionDecision"] = self.permission_decision
91
+
92
+ # 5. ⭐ systemMessage를 최상위 필드로 추가 (NOT in hookSpecificOutput)
93
+ if self.system_message:
94
+ output["systemMessage"] = self.system_message
95
+
96
+ # 🚫 내부 필드는 JSON 출력에서 제외
97
+ # - context_files: JIT 문맥 로드 (내부용)
98
+ # - suggestions: 제안 (내부용)
99
+ # - exit_code: 진단 (내부용)
100
+
101
+ return output
102
+ ```
103
+
104
+ #### `to_user_prompt_submit_dict()` 메서드 (라인 120-160)
105
+ ```python
106
+ def to_user_prompt_submit_dict(self) -> dict[str, Any]:
107
+ """UserPromptSubmit Hook 전용 스키마"""
108
+ if self.context_files:
109
+ context_str = "\n".join([f"📎 Context: {f}" for f in self.context_files])
110
+ else:
111
+ context_str = ""
112
+
113
+ if self.system_message:
114
+ if context_str:
115
+ context_str = f"{self.system_message}\n\n{context_str}"
116
+ else:
117
+ context_str = self.system_message
118
+
119
+ return {
120
+ "continue": self.continue_execution,
121
+ "hookSpecificOutput": {
122
+ "hookEventName": "UserPromptSubmit",
123
+ "additionalContext": context_str
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### 2. 설정 검증
129
+
130
+ **파일**: `.claude/settings.json` (라인 8-60)
131
+
132
+ ```json
133
+ {
134
+ "hooks": {
135
+ "SessionStart": [
136
+ {
137
+ "hooks": [
138
+ {
139
+ "command": "uv run \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/alfred/alfred_hooks.py SessionStart",
140
+ "type": "command"
141
+ }
142
+ ]
143
+ }
144
+ ],
145
+ "UserPromptSubmit": [
146
+ {
147
+ "hooks": [
148
+ {
149
+ "command": "uv run \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/alfred/alfred_hooks.py UserPromptSubmit",
150
+ "type": "command"
151
+ }
152
+ ]
153
+ }
154
+ ]
155
+ }
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ## 🧪 검증 결과
162
+
163
+ ### 1. 자동 테스트 (8/8 통과)
164
+
165
+ **파일**: `.claude/hooks/alfred/test_hook_output.py`
166
+
167
+ ```bash
168
+ $ cd .claude/hooks/alfred && python test_hook_output.py
169
+
170
+ ✅ Test 1: Basic output - PASSED
171
+ ✅ Test 2: systemMessage (top-level) - PASSED
172
+ ✅ Test 3: decision + reason - PASSED
173
+ ✅ Test 4: UserPromptSubmit schema - PASSED
174
+ ✅ Test 5: permissionDecision - PASSED
175
+ ✅ Test 6: SessionStart typical output - PASSED
176
+ ✅ Test 7: JSON serializable - PASSED
177
+ ✅ Test 8: UserPromptSubmit with system_message - PASSED
178
+
179
+ ✅ ALL 8 TESTS PASSED
180
+ ```
181
+
182
+ ### 2. 실제 Hook 실행 검증
183
+
184
+ #### SessionStart (compact phase)
185
+ ```bash
186
+ $ echo '{"cwd": ".", "phase": "compact"}' | uv run .claude/hooks/alfred/alfred_hooks.py SessionStart
187
+
188
+ {
189
+ "continue": true,
190
+ "systemMessage": "🚀 MoAI-ADK Session Started\n Language: python\n Branch: develop (d905363)\n Changes: 215\n SPEC Progress: 30/31 (96%)\n Checkpoints: 2 available\n - delete-20251022-134841\n - critical-file-20251019-230247\n Restore: /alfred:0-project restore"
191
+ }
192
+ ```
193
+
194
+ ✅ **검증**:
195
+ - `systemMessage`가 최상위 필드
196
+ - JSON 유효성 확인
197
+ - `hookSpecificOutput` 없음 (올바름)
198
+
199
+ #### SessionStart (clear phase)
200
+ ```bash
201
+ $ echo '{"cwd": ".", "phase": "clear"}' | uv run .claude/hooks/alfred/alfred_hooks.py SessionStart
202
+
203
+ {"continue": true}
204
+ ```
205
+
206
+ ✅ **검증**:
207
+ - 최소 스키마 (continue만)
208
+ - clear 단계에서 중복 출력 방지
209
+
210
+ #### UserPromptSubmit
211
+ ```bash
212
+ $ echo '{"cwd": ".", "userPrompt": "test"}' | uv run .claude/hooks/alfred/alfred_hooks.py UserPromptSubmit
213
+
214
+ {
215
+ "continue": true,
216
+ "hookSpecificOutput": {
217
+ "hookEventName": "UserPromptSubmit",
218
+ "additionalContext": "📎 Loaded 1 context file(s)\n\n📎 Context: tests/"
219
+ }
220
+ }
221
+ ```
222
+
223
+ ✅ **검증**:
224
+ - UserPromptSubmit 특수 스키마
225
+ - `hookSpecificOutput` 사용 (올바름)
226
+
227
+ ---
228
+
229
+ ## 📚 각 Hook 이벤트별 스키마 가이드
230
+
231
+ | 이벤트 | 최소 JSON | 예시 | 차단 가능 |
232
+ |--------|-----------|------|----------|
233
+ | **SessionStart** | `{"continue": true}` | 프로젝트 상태 표시 | ❌ No |
234
+ | **SessionEnd** | `{"continue": true}` | 정리 작업 | ❌ No |
235
+ | **PreToolUse** | `{"continue": true}` | 도구 실행 승인/차단 | ✅ Yes |
236
+ | **PostToolUse** | `{"continue": true}` | 도구 실행 후 피드백 | ❌ No* |
237
+ | **UserPromptSubmit** | 특수 스키마 | 프롬프트 문맥 추가 | ✅ Yes |
238
+ | **Notification** | `{"continue": true}` | 알림 처리 | ❌ No |
239
+ | **Stop** | `{"continue": true}` | 종료 차단 | ✅ Yes |
240
+ | **SubagentStop** | `{"continue": true}` | 서브에이전트 종료 차단 | ✅ Yes |
241
+
242
+ *: PostToolUse는 도구가 이미 실행되었으므로 차단 불가능하지만, 피드백 제공 가능
243
+
244
+ ---
245
+
246
+ ## 🔧 구현 세부사항
247
+
248
+ ### HookResult 클래스 필드
249
+
250
+ ```python
251
+ @dataclass
252
+ class HookResult:
253
+ # ✅ Claude Code 표준 필드 (JSON에 포함)
254
+ continue_execution: bool = True
255
+ suppress_output: bool = False
256
+ decision: Literal["approve", "block"] | None = None
257
+ reason: str | None = None
258
+ permission_decision: Literal["allow", "deny", "ask"] | None = None
259
+ system_message: str | None = None # ⭐ TOP-LEVEL in JSON
260
+
261
+ # 🚫 내부 필드 (JSON 출력 제외)
262
+ context_files: list[str] = field(default_factory=list)
263
+ suggestions: list[str] = field(default_factory=list)
264
+ exit_code: int = 0
265
+ ```
266
+
267
+ ### 메서드별 역할
268
+
269
+ | 메서드 | 사용 사건 | 반환 스키마 |
270
+ |--------|---------|----------|
271
+ | `to_dict()` | 일반 Hook 이벤트 | 표준 Claude Code 스키마 |
272
+ | `to_user_prompt_submit_dict()` | UserPromptSubmit 이벤트 | 특수 스키마 + hookSpecificOutput |
273
+
274
+ ---
275
+
276
+ ## 📖 참고 문서
277
+
278
+ ### 공식 Claude Code 문서
279
+ - **Claude Code Hooks**: https://docs.claude.com/en/docs/claude-code/hooks
280
+ - **Hook Output Schema**: https://docs.claude.com/en/docs/claude-code/hooks#output-schema
281
+
282
+ ### Context7 참고 자료
283
+ - **Claude Code Hooks Mastery** (Trust Score: 8.3, 100+ 코드 스니펫)
284
+ - **Claude Code Templates** (Trust Score: 10)
285
+
286
+ ### 프로젝트 문서
287
+ - **CLAUDE.md**: `Error Message Standard (Shared)` 섹션
288
+ - **Hook 구현**: `.claude/hooks/alfred/handlers/` 디렉토리
289
+
290
+ ---
291
+
292
+ ## 🎯 결론
293
+
294
+ ✅ **상태**: 해결 완료
295
+ ✅ **검증**: 8/8 자동 테스트 통과
296
+ ✅ **실제 실행**: 모든 Hook 이벤트 정상 작동
297
+
298
+ ### 핵심 수정사항
299
+ 1. `systemMessage`를 최상위 필드로 이동 (NOT in hookSpecificOutput)
300
+ 2. UserPromptSubmit 특수 스키마 분리
301
+ 3. 내부 필드 JSON 출력 제외
302
+ 4. 모든 Hook 이벤트 스키마 정규화
303
+
304
+ ### 다음 단계
305
+ - ✅ Hook 스키마 검증 자동화
306
+ - ✅ 테스트 스크립트 작성
307
+ - ⏭️ 현재 상태 유지 및 모니터링
308
+
309
+ ---
310
+
311
+ **검증 완료**: 2025-10-23
312
+ **담당자**: @agent-cc-manager
313
+ **참고**: @CODE:HOOKS-REFACTOR-001
@@ -136,7 +136,8 @@ def main() -> None:
136
136
  handler = handlers.get(event_name)
137
137
  result = handler({"cwd": cwd, **data}) if handler else HookResult()
138
138
 
139
- # UserPromptSubmit uses a special output schema
139
+ # Output Hook result as JSON
140
+ # Note: UserPromptSubmit uses to_user_prompt_submit_dict() for special schema
140
141
  if event_name == "UserPromptSubmit":
141
142
  print(json.dumps(result.to_user_prompt_submit_dict()))
142
143
  else:
@@ -145,9 +146,21 @@ def main() -> None:
145
146
  sys.exit(0)
146
147
 
147
148
  except json.JSONDecodeError as e:
149
+ # Return valid Hook response even on JSON parse error
150
+ error_response: dict[str, Any] = {
151
+ "continue": True,
152
+ "hookSpecificOutput": {"error": f"JSON parse error: {e}"}
153
+ }
154
+ print(json.dumps(error_response))
148
155
  print(f"JSON parse error: {e}", file=sys.stderr)
149
156
  sys.exit(1)
150
157
  except Exception as e:
158
+ # Return valid Hook response even on unexpected error
159
+ error_response: dict[str, Any] = {
160
+ "continue": True,
161
+ "hookSpecificOutput": {"error": f"Hook error: {e}"}
162
+ }
163
+ print(json.dumps(error_response))
151
164
  print(f"Unexpected error: {e}", file=sys.stderr)
152
165
  sys.exit(1)
153
166
 
@@ -4,8 +4,8 @@
4
4
  Common type definitions and utility functions
5
5
  """
6
6
 
7
- from dataclasses import asdict, dataclass, field
8
- from typing import Any, NotRequired, TypedDict
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Literal, NotRequired, TypedDict
9
9
 
10
10
 
11
11
  class HookPayload(TypedDict):
@@ -16,63 +16,147 @@ class HookPayload(TypedDict):
16
16
  """
17
17
 
18
18
  cwd: str
19
- userPrompt: NotRequired[str] # Includes only UserPromptSubmit events
19
+ userPrompt: NotRequired[str] # Includes only UserPromptSubmit events
20
20
  tool: NotRequired[str] # PreToolUse/PostToolUse events
21
21
  arguments: NotRequired[dict[str, Any]] # Tool arguments
22
22
 
23
23
 
24
24
  @dataclass
25
25
  class HookResult:
26
- """Hook execution result"""
26
+ """Hook execution result following Claude Code standard schema.
27
+
28
+ Attributes conform to Claude Code Hook output specification:
29
+ https://docs.claude.com/en/docs/claude-code/hooks
30
+
31
+ Standard Fields (Claude Code schema - included in JSON output):
32
+ continue_execution: Allow execution to continue (default True)
33
+ suppress_output: Suppress hook output display (default False)
34
+ decision: "approve" or "block" operation (optional)
35
+ reason: Explanation for decision (optional)
36
+ permission_decision: "allow", "deny", or "ask" (optional)
37
+ system_message: Message displayed to user (top-level field)
38
+
39
+ Internal Fields (MoAI-ADK only - NOT in JSON output):
40
+ context_files: List of context files to load (internal use only)
41
+ suggestions: Suggestions for user (internal use only)
42
+ exit_code: Exit code for diagnostics (internal use only)
43
+
44
+ Note:
45
+ - systemMessage appears at TOP LEVEL in JSON output
46
+ - hookSpecificOutput is ONLY used for UserPromptSubmit events
47
+ - Internal fields are used for Python logic but not serialized to JSON
48
+ """
49
+
50
+ # Claude Code standard fields
51
+ continue_execution: bool = True
52
+ suppress_output: bool = False
53
+ decision: Literal["approve", "block"] | None = None
54
+ reason: str | None = None
55
+ permission_decision: Literal["allow", "deny", "ask"] | None = None
27
56
 
28
- message: str | None = None
29
- systemMessage: str | None = None # Message displayed directly to the user # noqa: N815
30
- blocked: bool = False
31
- contextFiles: list[str] = field(default_factory=list) # noqa: N815
57
+ # MoAI-ADK custom fields (wrapped in hookSpecificOutput)
58
+ system_message: str | None = None
59
+ context_files: list[str] = field(default_factory=list)
32
60
  suggestions: list[str] = field(default_factory=list)
33
- exitCode: int = 0 # noqa: N815
61
+ exit_code: int = 0
34
62
 
35
63
  def to_dict(self) -> dict[str, Any]:
36
- """Dictionary conversion for general Hook"""
37
- return asdict(self)
64
+ """Convert to Claude Code standard Hook output schema.
65
+
66
+ Returns:
67
+ Dictionary conforming to Claude Code Hook specification with:
68
+ - Top-level fields: continue, suppressOutput, decision, reason,
69
+ permissionDecision, systemMessage
70
+ - MoAI-ADK internal fields (context_files, suggestions, exit_code)
71
+ are NOT included in JSON output (used for internal logic only)
72
+
73
+ Examples:
74
+ >>> result = HookResult(continue_execution=True)
75
+ >>> result.to_dict()
76
+ {'continue': True}
77
+
78
+ >>> result = HookResult(decision="block", reason="Dangerous")
79
+ >>> result.to_dict()
80
+ {'decision': 'block', 'reason': 'Dangerous'}
81
+
82
+ >>> result = HookResult(system_message="Test")
83
+ >>> result.to_dict()
84
+ {'continue': True, 'systemMessage': 'Test'}
85
+
86
+ Note:
87
+ - systemMessage is a TOP-LEVEL field (not nested in hookSpecificOutput)
88
+ - hookSpecificOutput is ONLY used for UserPromptSubmit events
89
+ - context_files, suggestions, exit_code are internal-only fields
90
+ """
91
+ output: dict[str, Any] = {}
92
+
93
+ # Add decision or continue flag
94
+ if self.decision:
95
+ output["decision"] = self.decision
96
+ else:
97
+ output["continue"] = self.continue_execution
98
+
99
+ # Add reason if provided (works with both decision and permissionDecision)
100
+ if self.reason:
101
+ output["reason"] = self.reason
102
+
103
+ # Add suppressOutput if True
104
+ if self.suppress_output:
105
+ output["suppressOutput"] = True
106
+
107
+ # Add permissionDecision if set
108
+ if self.permission_decision:
109
+ output["permissionDecision"] = self.permission_decision
110
+
111
+ # Add systemMessage at TOP LEVEL (required by Claude Code schema)
112
+ if self.system_message:
113
+ output["systemMessage"] = self.system_message
114
+
115
+ # Note: context_files, suggestions, exit_code are internal-only fields
116
+ # and are NOT included in the JSON output per Claude Code schema
117
+
118
+ return output
38
119
 
39
120
  def to_user_prompt_submit_dict(self) -> dict[str, Any]:
40
- """UserPromptSubmit Hook-specific output format
121
+ """UserPromptSubmit Hook-specific output format.
41
122
 
42
- Claude Code requires a special schema for UserPromptSubmit:
43
- {
44
- "hookEventName": "UserPromptSubmit",
45
- "additionalContext": "string (required)"
46
- }
123
+ Claude Code requires a special schema for UserPromptSubmit events.
124
+ The result is wrapped in the standard Hook schema with hookSpecificOutput.
47
125
 
48
126
  Returns:
49
- Claude Code UserPromptSubmit Hook Dictionary matching schema
127
+ Claude Code UserPromptSubmit Hook Dictionary matching schema:
128
+ {
129
+ "continue": true,
130
+ "hookSpecificOutput": {
131
+ "hookEventName": "UserPromptSubmit",
132
+ "additionalContext": "string"
133
+ }
134
+ }
50
135
 
51
136
  Examples:
52
- >>> result = HookResult(contextFiles=["tests/"])
137
+ >>> result = HookResult(context_files=["tests/"])
53
138
  >>> result.to_user_prompt_submit_dict()
54
- {'hookEventName': 'UserPromptSubmit', 'additionalContext': '📎 Context: tests/'}
139
+ {'continue': True, 'hookSpecificOutput': {'hookEventName': 'UserPromptSubmit', 'additionalContext': '📎 Context: tests/'}}
55
140
  """
56
- # Convert contextFiles to additionalContext string
57
- if self.contextFiles:
58
- context_str = "\n".join([f"📎 Context: {f}" for f in self.contextFiles])
141
+ # Convert context_files to additionalContext string
142
+ if self.context_files:
143
+ context_str = "\n".join([f"📎 Context: {f}" for f in self.context_files])
59
144
  else:
60
145
  context_str = ""
61
146
 
62
- # Add message if there is one
63
- if self.message:
147
+ # Add system_message if there is one
148
+ if self.system_message:
64
149
  if context_str:
65
- context_str = f"{self.message}\n\n{context_str}"
150
+ context_str = f"{self.system_message}\n\n{context_str}"
66
151
  else:
67
- context_str = self.message
68
-
69
- # If the string is empty, use default
70
- if not context_str:
71
- context_str = ""
152
+ context_str = self.system_message
72
153
 
73
154
  return {
74
- "hookEventName": "UserPromptSubmit",
75
- "additionalContext": context_str
155
+ "continue": self.continue_execution,
156
+ "hookSpecificOutput": {
157
+ "hookEventName": "UserPromptSubmit",
158
+ "additionalContext": context_str
159
+ }
76
160
  }
77
161
 
78
162
 
@@ -19,7 +19,7 @@ def handle_session_start(payload: HookPayload) -> HookResult:
19
19
  payload: Claude Code event payload (cwd key required)
20
20
 
21
21
  Returns:
22
- HookResult(message=project status summary message, systemMessage=for user display)
22
+ HookResult(system_message=project status summary message)
23
23
 
24
24
  Message Format:
25
25
  🚀 MoAI-ADK Session Started
@@ -31,14 +31,15 @@ def handle_session_start(payload: HookPayload) -> HookResult:
31
31
 
32
32
  Note:
33
33
  - Claude Code processes SessionStart in several stages (clear → compact)
34
- - Display message only at compact stage to prevent duplicate output
35
- - "clear" step returns empty result (invisible to user)
34
+ - Display message only at "compact" stage to prevent duplicate output
35
+ - "clear" step returns minimal result (empty hookSpecificOutput)
36
36
 
37
37
  TDD History:
38
38
  - RED: Session startup message format test
39
39
  - GREEN: Generate status message by combining helper functions
40
40
  - REFACTOR: Improved message format, improved readability, added checkpoint list
41
41
  - FIX: Prevent duplicate output of clear step (only compact step is displayed)
42
+ - UPDATE: Migrated to Claude Code standard Hook schema
42
43
 
43
44
  @TAG:CHECKPOINT-EVENT-001
44
45
  """
@@ -46,7 +47,8 @@ def handle_session_start(payload: HookPayload) -> HookResult:
46
47
  # Ignore the "clear" stage and output messages only at the "compact" stage
47
48
  event_phase = payload.get("phase", "")
48
49
  if event_phase == "clear":
49
- return HookResult() # returns an empty result (prevents duplicate output)
50
+ # Return minimal valid Hook result for clear phase
51
+ return HookResult(continue_execution=True)
50
52
 
51
53
  cwd = payload.get("cwd", ".")
52
54
  language = detect_language(cwd)
@@ -59,7 +61,7 @@ def handle_session_start(payload: HookPayload) -> HookResult:
59
61
  changes = git_info.get("changes", 0)
60
62
  spec_progress = f"{specs['completed']}/{specs['total']}"
61
63
 
62
- # systemMessage: displayed directly to the user
64
+ # system_message: displayed directly to the user
63
65
  lines = [
64
66
  "🚀 MoAI-ADK Session Started",
65
67
  f" Language: {language}",
@@ -78,10 +80,7 @@ def handle_session_start(payload: HookPayload) -> HookResult:
78
80
 
79
81
  system_message = "\n".join(lines)
80
82
 
81
- return HookResult(
82
- message=system_message, # for Claude context
83
- systemMessage=system_message, # For user display
84
- )
83
+ return HookResult(system_message=system_message)
85
84
 
86
85
 
87
86
  def handle_session_end(payload: HookPayload) -> HookResult:
@@ -20,8 +20,8 @@ def handle_pre_tool_use(payload: HookPayload) -> HookResult:
20
20
 
21
21
  Returns:
22
22
  HookResult(
23
- message=checkpoint creation notification (when danger is detected);
24
- blocked=False (always continue operation)
23
+ system_message=checkpoint creation notification (when danger is detected);
24
+ continue_execution=True (always continue operation)
25
25
  )
26
26
 
27
27
  Checkpoint Triggers:
@@ -34,7 +34,7 @@ def handle_pre_tool_use(payload: HookPayload) -> HookResult:
34
34
  → "🛡️ Checkpoint created: before-delete-20251015-143000"
35
35
 
36
36
  Notes:
37
- - Return blocked=False even after detection of danger (continue operation)
37
+ - Return continue_execution=True even after detection of danger (continue operation)
38
38
  - Work continues even when checkpoint fails (ignores)
39
39
  - Transparent background operation
40
40
 
@@ -52,14 +52,14 @@ def handle_pre_tool_use(payload: HookPayload) -> HookResult:
52
52
  checkpoint_branch = create_checkpoint(cwd, operation_type)
53
53
 
54
54
  if checkpoint_branch != "checkpoint-failed":
55
- message = (
55
+ system_message = (
56
56
  f"🛡️ Checkpoint created: {checkpoint_branch}\n"
57
57
  f" Operation: {operation_type}"
58
58
  )
59
59
 
60
- return HookResult(message=message, blocked=False)
60
+ return HookResult(system_message=system_message, continue_execution=True)
61
61
 
62
- return HookResult(blocked=False)
62
+ return HookResult(continue_execution=True)
63
63
 
64
64
 
65
65
  def handle_post_tool_use(payload: HookPayload) -> HookResult:
@@ -20,22 +20,23 @@ def handle_user_prompt_submit(payload: HookPayload) -> HookResult:
20
20
 
21
21
  Returns:
22
22
  HookResult(
23
- message=Number of Files loaded (or None),
24
- contextFiles=Recommended document path list
23
+ system_message=Number of Files loaded (or None),
24
+ context_files=Recommended document path list
25
25
  )
26
26
 
27
27
  TDD History:
28
28
  - RED: JIT document loading scenario testing
29
29
  - GREEN: Recommend documents by calling get_jit_context()
30
30
  - REFACTOR: Message conditional display (only when there is a file)
31
+ - UPDATE: Migrated to Claude Code standard Hook schema with snake_case fields
31
32
  """
32
33
  user_prompt = payload.get("userPrompt", "")
33
34
  cwd = payload.get("cwd", ".")
34
35
  context_files = get_jit_context(user_prompt, cwd)
35
36
 
36
- message = f"📎 Loaded {len(context_files)} context file(s)" if context_files else None
37
+ system_message = f"📎 Loaded {len(context_files)} context file(s)" if context_files else None
37
38
 
38
- return HookResult(message=message, contextFiles=context_files)
39
+ return HookResult(system_message=system_message, context_files=context_files)
39
40
 
40
41
 
41
42
  __all__ = ["handle_user_prompt_submit"]