moai-adk 0.4.8__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.
- moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md +313 -0
- moai_adk/templates/.claude/hooks/alfred/core/__init__.py +30 -28
- moai_adk/templates/.claude/hooks/alfred/test_hook_output.py +175 -0
- moai_adk/templates/CLAUDE.md +30 -19
- {moai_adk-0.4.8.dist-info → moai_adk-0.4.10.dist-info}/METADATA +10 -9
- {moai_adk-0.4.8.dist-info → moai_adk-0.4.10.dist-info}/RECORD +9 -7
- {moai_adk-0.4.8.dist-info → moai_adk-0.4.10.dist-info}/WHEEL +0 -0
- {moai_adk-0.4.8.dist-info → moai_adk-0.4.10.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.4.8.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
|
|
@@ -28,18 +28,23 @@ class HookResult:
|
|
|
28
28
|
Attributes conform to Claude Code Hook output specification:
|
|
29
29
|
https://docs.claude.com/en/docs/claude-code/hooks
|
|
30
30
|
|
|
31
|
-
Standard Fields (Claude Code schema):
|
|
31
|
+
Standard Fields (Claude Code schema - included in JSON output):
|
|
32
32
|
continue_execution: Allow execution to continue (default True)
|
|
33
33
|
suppress_output: Suppress hook output display (default False)
|
|
34
34
|
decision: "approve" or "block" operation (optional)
|
|
35
35
|
reason: Explanation for decision (optional)
|
|
36
36
|
permission_decision: "allow", "deny", or "ask" (optional)
|
|
37
|
+
system_message: Message displayed to user (top-level field)
|
|
37
38
|
|
|
38
|
-
MoAI-ADK
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
43
48
|
"""
|
|
44
49
|
|
|
45
50
|
# Claude Code standard fields
|
|
@@ -60,8 +65,10 @@ class HookResult:
|
|
|
60
65
|
|
|
61
66
|
Returns:
|
|
62
67
|
Dictionary conforming to Claude Code Hook specification with:
|
|
63
|
-
- Top-level fields: continue, suppressOutput, decision, reason,
|
|
64
|
-
|
|
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)
|
|
65
72
|
|
|
66
73
|
Examples:
|
|
67
74
|
>>> result = HookResult(continue_execution=True)
|
|
@@ -72,20 +79,27 @@ class HookResult:
|
|
|
72
79
|
>>> result.to_dict()
|
|
73
80
|
{'decision': 'block', 'reason': 'Dangerous'}
|
|
74
81
|
|
|
75
|
-
>>> result = HookResult(system_message="Test"
|
|
82
|
+
>>> result = HookResult(system_message="Test")
|
|
76
83
|
>>> result.to_dict()
|
|
77
|
-
{'continue': True, '
|
|
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
|
|
78
90
|
"""
|
|
79
91
|
output: dict[str, Any] = {}
|
|
80
92
|
|
|
81
93
|
# Add decision or continue flag
|
|
82
94
|
if self.decision:
|
|
83
95
|
output["decision"] = self.decision
|
|
84
|
-
if self.reason:
|
|
85
|
-
output["reason"] = self.reason
|
|
86
96
|
else:
|
|
87
97
|
output["continue"] = self.continue_execution
|
|
88
98
|
|
|
99
|
+
# Add reason if provided (works with both decision and permissionDecision)
|
|
100
|
+
if self.reason:
|
|
101
|
+
output["reason"] = self.reason
|
|
102
|
+
|
|
89
103
|
# Add suppressOutput if True
|
|
90
104
|
if self.suppress_output:
|
|
91
105
|
output["suppressOutput"] = True
|
|
@@ -94,24 +108,12 @@ class HookResult:
|
|
|
94
108
|
if self.permission_decision:
|
|
95
109
|
output["permissionDecision"] = self.permission_decision
|
|
96
110
|
|
|
97
|
-
#
|
|
98
|
-
hook_output: dict[str, Any] = {}
|
|
99
|
-
|
|
111
|
+
# Add systemMessage at TOP LEVEL (required by Claude Code schema)
|
|
100
112
|
if self.system_message:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if self.context_files:
|
|
104
|
-
hook_output["contextFiles"] = self.context_files
|
|
105
|
-
|
|
106
|
-
if self.suggestions:
|
|
107
|
-
hook_output["suggestions"] = self.suggestions
|
|
108
|
-
|
|
109
|
-
if self.exit_code != 0:
|
|
110
|
-
hook_output["exitCode"] = self.exit_code
|
|
113
|
+
output["systemMessage"] = self.system_message
|
|
111
114
|
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
output["hookSpecificOutput"] = hook_output
|
|
115
|
+
# Note: context_files, suggestions, exit_code are internal-only fields
|
|
116
|
+
# and are NOT included in the JSON output per Claude Code schema
|
|
115
117
|
|
|
116
118
|
return output
|
|
117
119
|
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# ///
|
|
5
|
+
"""Test Hook Output Validation
|
|
6
|
+
|
|
7
|
+
자동 테스트: Claude Code Hook JSON 스키마 검증
|
|
8
|
+
|
|
9
|
+
- SessionStart Hook JSON 출력 검증
|
|
10
|
+
- UserPromptSubmit Hook 특수 스키마 검증
|
|
11
|
+
- 모든 Hook 이벤트 스키마 일관성 검증
|
|
12
|
+
|
|
13
|
+
실행:
|
|
14
|
+
uv run test_hook_output.py
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
# Add hooks directory to sys.path
|
|
22
|
+
HOOKS_DIR = Path(__file__).parent
|
|
23
|
+
if str(HOOKS_DIR) not in sys.path:
|
|
24
|
+
sys.path.insert(0, str(HOOKS_DIR))
|
|
25
|
+
|
|
26
|
+
from core import HookResult
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_basic_output():
|
|
30
|
+
"""Test 1: Basic output with only continue flag"""
|
|
31
|
+
result = HookResult(continue_execution=True)
|
|
32
|
+
output = result.to_dict()
|
|
33
|
+
|
|
34
|
+
assert output == {"continue": True}, f"Expected {{'continue': True}}, got {output}"
|
|
35
|
+
print("✅ Test 1: Basic output - PASSED")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_system_message_top_level():
|
|
39
|
+
"""Test 2: systemMessage at TOP-LEVEL (not in hookSpecificOutput)"""
|
|
40
|
+
result = HookResult(system_message="Test message")
|
|
41
|
+
output = result.to_dict()
|
|
42
|
+
|
|
43
|
+
assert "systemMessage" in output, "systemMessage not found in output"
|
|
44
|
+
assert output["systemMessage"] == "Test message"
|
|
45
|
+
assert "hookSpecificOutput" not in output, "hookSpecificOutput should not be in to_dict() output"
|
|
46
|
+
print("✅ Test 2: systemMessage (top-level) - PASSED")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_decision_with_reason():
|
|
50
|
+
"""Test 3: decision + reason (block pattern)"""
|
|
51
|
+
result = HookResult(decision="block", reason="Dangerous operation")
|
|
52
|
+
output = result.to_dict()
|
|
53
|
+
|
|
54
|
+
assert output.get("decision") == "block"
|
|
55
|
+
assert output.get("reason") == "Dangerous operation"
|
|
56
|
+
assert "continue" not in output, "continue should not appear when decision is set"
|
|
57
|
+
print("✅ Test 3: decision + reason - PASSED")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_user_prompt_submit_schema():
|
|
61
|
+
"""Test 4: UserPromptSubmit special schema"""
|
|
62
|
+
result = HookResult(context_files=["tests/", "docs/"])
|
|
63
|
+
output = result.to_user_prompt_submit_dict()
|
|
64
|
+
|
|
65
|
+
assert "hookSpecificOutput" in output
|
|
66
|
+
assert output["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
|
|
67
|
+
assert "additionalContext" in output["hookSpecificOutput"]
|
|
68
|
+
assert "📎 Context: tests/" in output["hookSpecificOutput"]["additionalContext"]
|
|
69
|
+
print("✅ Test 4: UserPromptSubmit schema - PASSED")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_permission_decision():
|
|
73
|
+
"""Test 5: permissionDecision field"""
|
|
74
|
+
result = HookResult(permission_decision="deny")
|
|
75
|
+
output = result.to_dict()
|
|
76
|
+
|
|
77
|
+
assert output.get("permissionDecision") == "deny"
|
|
78
|
+
assert "continue" in output # continue should still be present
|
|
79
|
+
print("✅ Test 5: permissionDecision - PASSED")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_session_start_typical_output():
|
|
83
|
+
"""Test 6: Typical SessionStart output"""
|
|
84
|
+
result = HookResult(
|
|
85
|
+
continue_execution=True,
|
|
86
|
+
system_message="🚀 MoAI-ADK Session Started\n Language: python\n Branch: develop"
|
|
87
|
+
)
|
|
88
|
+
output = result.to_dict()
|
|
89
|
+
|
|
90
|
+
# Validate schema
|
|
91
|
+
assert "continue" in output or "decision" in output, "Missing continue or decision"
|
|
92
|
+
assert output.get("systemMessage", "").startswith("🚀 MoAI-ADK")
|
|
93
|
+
|
|
94
|
+
# Ensure internal fields are NOT in output
|
|
95
|
+
assert "context_files" not in output, "Internal field context_files leaked to output"
|
|
96
|
+
assert "suggestions" not in output, "Internal field suggestions leaked to output"
|
|
97
|
+
assert "exit_code" not in output, "Internal field exit_code leaked to output"
|
|
98
|
+
|
|
99
|
+
print("✅ Test 6: SessionStart typical output - PASSED")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_json_serializable():
|
|
103
|
+
"""Test 7: Output is JSON serializable"""
|
|
104
|
+
result = HookResult(
|
|
105
|
+
system_message="Test",
|
|
106
|
+
decision="approve",
|
|
107
|
+
reason="Valid operation"
|
|
108
|
+
)
|
|
109
|
+
output = result.to_dict()
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
json_str = json.dumps(output)
|
|
113
|
+
parsed = json.loads(json_str)
|
|
114
|
+
assert parsed == output
|
|
115
|
+
print("✅ Test 7: JSON serializable - PASSED")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"❌ Test 7: JSON serialization FAILED: {e}")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_user_prompt_submit_with_system_message():
|
|
122
|
+
"""Test 8: UserPromptSubmit with both context and system message"""
|
|
123
|
+
result = HookResult(
|
|
124
|
+
context_files=["src/"],
|
|
125
|
+
system_message="Loading context..."
|
|
126
|
+
)
|
|
127
|
+
output = result.to_user_prompt_submit_dict()
|
|
128
|
+
|
|
129
|
+
assert "hookSpecificOutput" in output
|
|
130
|
+
assert "Loading context..." in output["hookSpecificOutput"]["additionalContext"]
|
|
131
|
+
assert "📎 Context: src/" in output["hookSpecificOutput"]["additionalContext"]
|
|
132
|
+
print("✅ Test 8: UserPromptSubmit with system_message - PASSED")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def main():
|
|
136
|
+
"""Run all tests"""
|
|
137
|
+
print("\n" + "="*60)
|
|
138
|
+
print("🧪 Claude Code Hook Output Validation Tests")
|
|
139
|
+
print("="*60 + "\n")
|
|
140
|
+
|
|
141
|
+
tests = [
|
|
142
|
+
test_basic_output,
|
|
143
|
+
test_system_message_top_level,
|
|
144
|
+
test_decision_with_reason,
|
|
145
|
+
test_user_prompt_submit_schema,
|
|
146
|
+
test_permission_decision,
|
|
147
|
+
test_session_start_typical_output,
|
|
148
|
+
test_json_serializable,
|
|
149
|
+
test_user_prompt_submit_with_system_message,
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
failed = 0
|
|
153
|
+
for test in tests:
|
|
154
|
+
try:
|
|
155
|
+
test()
|
|
156
|
+
except AssertionError as e:
|
|
157
|
+
print(f"❌ {test.__name__}: FAILED - {e}")
|
|
158
|
+
failed += 1
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"❌ {test.__name__}: ERROR - {e}")
|
|
161
|
+
failed += 1
|
|
162
|
+
|
|
163
|
+
print("\n" + "="*60)
|
|
164
|
+
if failed == 0:
|
|
165
|
+
print(f"✅ ALL {len(tests)} TESTS PASSED")
|
|
166
|
+
print("="*60 + "\n")
|
|
167
|
+
sys.exit(0)
|
|
168
|
+
else:
|
|
169
|
+
print(f"❌ {failed}/{len(tests)} TESTS FAILED")
|
|
170
|
+
print("="*60 + "\n")
|
|
171
|
+
sys.exit(1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
main()
|
moai_adk/templates/CLAUDE.md
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MoAI-ADK - MoAI-Agentic Development Kit
|
|
2
2
|
|
|
3
3
|
**SPEC-First TDD Development with Alfred SuperAgent**
|
|
4
4
|
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
>
|
|
5
|
+
> **Document Language**: {{conversation_language_name}} ({{conversation_language}})
|
|
6
|
+
> **Project Owner**: {{project_owner}}
|
|
7
|
+
> **Config**: `.moai/config.json` → `project.conversation_language`
|
|
8
8
|
>
|
|
9
|
-
> 💡 **Alfred와의 모든 상호작용에서 `Skill("moai-alfred-interactive-questions")`를 통해 TUI 메뉴로 응답할 수 있습니다.**
|
|
10
9
|
> All interactions with Alfred can use `Skill("moai-alfred-interactive-questions")` for TUI-based responses.
|
|
11
10
|
|
|
12
11
|
---
|
|
13
12
|
|
|
13
|
+
## 🗿 🎩 Alfred's Core Directives
|
|
14
|
+
|
|
15
|
+
You are the SuperAgent **🎩 Alfred** of **🗿 MoAI-ADK**. Follow these core principles:
|
|
16
|
+
|
|
17
|
+
1. **Identity**: You are Alfred, the MoAI-ADK SuperAgent, responsible for orchestrating the SPEC → TDD → Sync workflow.
|
|
18
|
+
2. **Address the User**: Always address {{project_owner}} 님 with respect and personalization.
|
|
19
|
+
3. **Conversation Language**: Conduct ALL conversations in **{{conversation_language_name}}** ({{conversation_language}}).
|
|
20
|
+
4. **Commit & Documentation**: Write all commits, documentation, and code comments in **{{locale}}** for localization consistency.
|
|
21
|
+
5. **Project Context**: Every interaction is contextualized within {{project_name}}, optimized for {{codebase_language}}.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
14
25
|
## ▶◀ Meet Alfred: Your MoAI SuperAgent
|
|
15
26
|
|
|
16
27
|
**Alfred** orchestrates the MoAI-ADK agentic workflow across a four-layer stack (Commands → Sub-agents → Skills → Hooks). The SuperAgent interprets user intent, activates the right specialists, streams Claude Skills on demand, and enforces the TRUST 5 principles so every project follows the SPEC → TDD → Sync rhythm.
|
|
@@ -753,23 +764,23 @@ Alfred enforces these quality gates on every change:
|
|
|
753
764
|
|
|
754
765
|
---
|
|
755
766
|
|
|
756
|
-
##
|
|
767
|
+
## Project Information
|
|
757
768
|
|
|
758
|
-
-
|
|
759
|
-
-
|
|
760
|
-
-
|
|
761
|
-
-
|
|
762
|
-
-
|
|
763
|
-
-
|
|
764
|
-
-
|
|
765
|
-
-
|
|
769
|
+
- **Name**: {{project_name}}
|
|
770
|
+
- **Description**: {{project_description}}
|
|
771
|
+
- **Version**: {{moai_adk_version}}
|
|
772
|
+
- **Mode**: {{project_mode}}
|
|
773
|
+
- **Project Owner**: {{project_owner}}
|
|
774
|
+
- **Conversation Language**: {{conversation_language_name}} ({{conversation_language}})
|
|
775
|
+
- **Codebase Language**: {{codebase_language}}
|
|
776
|
+
- **Toolchain**: Automatically selects the best tools for {{codebase_language}}
|
|
766
777
|
|
|
767
|
-
###
|
|
778
|
+
### Language Configuration
|
|
768
779
|
|
|
769
|
-
-
|
|
770
|
-
-
|
|
771
|
-
-
|
|
780
|
+
- **Conversation Language** (`{{conversation_language}}`): All Alfred dialogs, documentation, and project interviews conducted in {{conversation_language_name}}
|
|
781
|
+
- **Codebase Language** (`{{codebase_language_lower}}`): Primary programming language for this project
|
|
782
|
+
- **Documentation**: Generated in {{conversation_language_name}}
|
|
772
783
|
|
|
773
784
|
---
|
|
774
785
|
|
|
775
|
-
|
|
786
|
+
**Note**: The conversation language is selected at the beginning of `/alfred:0-project` and applies to all subsequent project initialization steps. All generated documentation (product.md, structure.md, tech.md) will be created in {{conversation_language_name}}.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moai-adk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.10
|
|
4
4
|
Summary: MoAI Agentic Development Kit - SPEC-First TDD with Alfred SuperAgent & Complete Skills v2.0
|
|
5
5
|
Project-URL: Homepage, https://github.com/modu-ai/moai-adk
|
|
6
6
|
Project-URL: Repository, https://github.com/modu-ai/moai-adk
|
|
@@ -190,7 +190,7 @@ uv tool install moai-adk
|
|
|
190
190
|
|
|
191
191
|
# Verify installation
|
|
192
192
|
moai-adk --version
|
|
193
|
-
# Output: MoAI-ADK v0.4.
|
|
193
|
+
# Output: MoAI-ADK v0.4.10
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
Once installed, you can use the `moai-adk` command anywhere.
|
|
@@ -1223,14 +1223,14 @@ If you need to temporarily disable hooks, edit `.claude/settings.json`:
|
|
|
1223
1223
|
|
|
1224
1224
|
| Version | Key Features | Date |
|
|
1225
1225
|
| ---------- | ------------------------------------------------------------------------------------ | ---------- |
|
|
1226
|
+
| **v0.4.10** | 🔧 Hook robustness improvements + Bilingual documentation + Template language config | 2025-10-23 |
|
|
1227
|
+
| **v0.4.9** | 🎯 Hook JSON schema validation fixes + Comprehensive tests (468/468 passing) | 2025-10-23 |
|
|
1228
|
+
| **v0.4.8** | 🚀 Release automation + PyPI deployment + Skills refinement | 2025-10-23 |
|
|
1229
|
+
| **v0.4.7** | 📖 Korean language optimization + SPEC-First principle documentation | 2025-10-22 |
|
|
1226
1230
|
| **v0.4.6** | 🎉 Complete Skills v2.0 (100% Production-Ready) + 85,000 lines official docs + 300+ TDD examples | 2025-10-22 |
|
|
1227
1231
|
| **v0.4.5** | ✅ CI/CD fixes + Multi-language README + Deployment cleanup | 2025-10-22 |
|
|
1228
|
-
| **v0.4.4** | Korean language support | 2025-10-21 |
|
|
1229
|
-
| **v0.4.3** | Interactive question tool (TUI menu) | 2025-10-21 |
|
|
1230
|
-
| **v0.4.1** | Skills localization | 2025-10-21 |
|
|
1231
|
-
| **v0.4.0** | Claude Skills + AI team + 4-stage workflow | 2025-10-21 |
|
|
1232
1232
|
|
|
1233
|
-
> 📦 **Install Now**: `pip install moai-adk==0.4.
|
|
1233
|
+
> 📦 **Install Now**: `pip install moai-adk==0.4.10` or `uv tool install moai-adk==0.4.10`
|
|
1234
1234
|
|
|
1235
1235
|
---
|
|
1236
1236
|
|
|
@@ -1544,10 +1544,11 @@ Start a new experience of **trustworthy AI development** with Alfred! 🤖
|
|
|
1544
1544
|
|
|
1545
1545
|
---
|
|
1546
1546
|
|
|
1547
|
-
**MoAI-ADK v0.4.
|
|
1547
|
+
**MoAI-ADK v0.4.10** — SPEC-First TDD with AI SuperAgent & Complete Skills v2.0
|
|
1548
1548
|
- 📦 PyPI: https://pypi.org/project/moai-adk/
|
|
1549
1549
|
- 🏠 GitHub: https://github.com/modu-ai/moai-adk
|
|
1550
1550
|
- 📝 License: MIT
|
|
1551
|
-
- ⭐ Skills:
|
|
1551
|
+
- ⭐ Skills: 55+ Production-Ready Guides
|
|
1552
|
+
- ✅ Tests: 468/468 Passing (86% coverage)
|
|
1552
1553
|
|
|
1553
1554
|
---
|
|
@@ -38,7 +38,7 @@ moai_adk/core/template/languages.py,sha256=V0wLcxCIOve9Q_0_NhrHGQevSIN_MB612GwrO
|
|
|
38
38
|
moai_adk/core/template/merger.py,sha256=ZV8_U1HZJ3bfAtgyNmSxgj8KdTMt4eUnUG6kVVRT7bE,6909
|
|
39
39
|
moai_adk/core/template/processor.py,sha256=W4M3BNrlfA0dHRwvPWIkJmJwhkNDglXaNzsYBoVTSnQ,17019
|
|
40
40
|
moai_adk/templates/.gitignore,sha256=6VNKResdDpyaii3cmJA4pOLwK2PhYARIWkUODYtKyxg,310
|
|
41
|
-
moai_adk/templates/CLAUDE.md,sha256=
|
|
41
|
+
moai_adk/templates/CLAUDE.md,sha256=9eF_fWzNJucNBKAChoaJzVXa-LLhK4Cv7j4XZ1Whl8o,42030
|
|
42
42
|
moai_adk/templates/__init__.py,sha256=6MV1gCB7PLZMiL4gaD_dZSKxtcQyo45MMTuN8fVdchA,104
|
|
43
43
|
moai_adk/templates/.claude/settings.json,sha256=sXQ8gV3oyZJHqPHMxYaH-ps-LXliIMpLMj-Xx2jjE7o,3269
|
|
44
44
|
moai_adk/templates/.claude/agents/alfred/cc-manager.md,sha256=nyiNtcSqvLKK8Mp88hhTFBqrpsRiTqtNTpnAo1m0qQg,8048
|
|
@@ -57,9 +57,11 @@ moai_adk/templates/.claude/commands/alfred/0-project.md,sha256=c0aCthvHu57Eg4QHB
|
|
|
57
57
|
moai_adk/templates/.claude/commands/alfred/1-plan.md,sha256=4Afx44Xx1yp4kp5FwrJg4QxMt-NBkkfgNNi05lk0glg,21792
|
|
58
58
|
moai_adk/templates/.claude/commands/alfred/2-run.md,sha256=UMscmBrpqysmWoKvwyGXEnCEzDWVeS5VEvvYABFYC4g,20194
|
|
59
59
|
moai_adk/templates/.claude/commands/alfred/3-sync.md,sha256=xF9XnP20cJrRuCFEG_J3RnZtwMy5Ym3nj_f6eryBFBE,21535
|
|
60
|
+
moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md,sha256=5dd6KRFQXzp0L8lAWKRN7Momgg_8XNV1QZ6VGs03_pc,9064
|
|
60
61
|
moai_adk/templates/.claude/hooks/alfred/README.md,sha256=8JirNg3Jn2OUFmHySYBd8QxQgPkG7kcev86Zkf80asY,6852
|
|
61
62
|
moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py,sha256=dPlwlj7LlsJd-c-D5ONiQGkih4qULFcjzNvZQyto_kc,7547
|
|
62
|
-
moai_adk/templates/.claude/hooks/alfred/
|
|
63
|
+
moai_adk/templates/.claude/hooks/alfred/test_hook_output.py,sha256=zxA330umo4HHoYe_reOocqDeIU3OMBd31rcEfY9QR-U,5632
|
|
64
|
+
moai_adk/templates/.claude/hooks/alfred/core/__init__.py,sha256=Cz1YeL6c5j2ZhJwG3y-lpxde7paJ00tEaYRduc5gDoM,6375
|
|
63
65
|
moai_adk/templates/.claude/hooks/alfred/core/checkpoint.py,sha256=dsvFDSXQNSQlaQLpvhqFbytGOrOivyovi43Y18EmNeI,8483
|
|
64
66
|
moai_adk/templates/.claude/hooks/alfred/core/context.py,sha256=RQd6yk8OGT-twgYtUiNmJIL-UEt_h4oktxqiRP_wXAI,2103
|
|
65
67
|
moai_adk/templates/.claude/hooks/alfred/core/project.py,sha256=XVm-Y5TVjBBznW8AkFhk_uVg-EF1lZ2wx59Rxt_LOBA,9125
|
|
@@ -258,8 +260,8 @@ moai_adk/templates/.moai/project/tech.md,sha256=REecMv8wOvutt-pQZ5nlGk5YdReTan7A
|
|
|
258
260
|
moai_adk/utils/__init__.py,sha256=VnVfQzzKHvKw4bNdEw5xdscnRQYFrnr-v_TOBr3naPs,225
|
|
259
261
|
moai_adk/utils/banner.py,sha256=znppKd5yo-tTqgyhgPVJjstrTrfcy_v3X1_RFQxP4Fk,1878
|
|
260
262
|
moai_adk/utils/logger.py,sha256=g-m07PGKjK2bKRIInfSn6m-024Bedai-pV_WjZKDeu8,5064
|
|
261
|
-
moai_adk-0.4.
|
|
262
|
-
moai_adk-0.4.
|
|
263
|
-
moai_adk-0.4.
|
|
264
|
-
moai_adk-0.4.
|
|
265
|
-
moai_adk-0.4.
|
|
263
|
+
moai_adk-0.4.10.dist-info/METADATA,sha256=o-wzIPH9FTTUj3Ta4mbhbeepWXmxWALSCVBmySStonk,61844
|
|
264
|
+
moai_adk-0.4.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
265
|
+
moai_adk-0.4.10.dist-info/entry_points.txt,sha256=P9no1794UipqH72LP-ltdyfVd_MeB1WKJY_6-JQgV3U,52
|
|
266
|
+
moai_adk-0.4.10.dist-info/licenses/LICENSE,sha256=M1M2b07fWcSWRM6_P3wbZKndZvyfHyYk_Wu9bS8F7o8,1069
|
|
267
|
+
moai_adk-0.4.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|