219 lines
10 KiB
Markdown
219 lines
10 KiB
Markdown
---
|
|
phase: 03-hook-server
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- hook.go
|
|
- hook_test.go
|
|
- protocol.go
|
|
- protocol_test.go
|
|
autonomous: true
|
|
requirements: [STATE-03]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "HookEvent struct parse le JSON de Claude Code hooks (session_id, hook_event_name, notification_type, etc.)"
|
|
- "processHookEvent mappe correctement Notification/permission_prompt vers WaitType=permission"
|
|
- "processHookEvent mappe correctement Notification/idle_prompt vers WaitType=idle"
|
|
- "processHookEvent mappe correctement Stop vers WaitType=question"
|
|
- "processHookEvent mappe correctement PostToolUse/PreToolUse vers State=Working et clear WaitType"
|
|
- "UpdateFromHook cree une entree si la session n'existe pas encore dans le registre"
|
|
- "SessionInfo contient le champ WaitType serialise en JSON"
|
|
- "HTTP handler POST /hook retourne 200 et met a jour le registre"
|
|
- "HTTP handler refuse les methodes non-POST (405)"
|
|
- "HTTP handler refuse les payloads invalides (400)"
|
|
- "HTTP handler limite la taille du body (MaxBytesReader 64KB)"
|
|
artifacts:
|
|
- path: "hook.go"
|
|
provides: "HookEvent struct, handleHook HTTP handler, processHookEvent, UpdateFromHook"
|
|
- path: "hook_test.go"
|
|
provides: "Tests unitaires pour tous les mappings hook event et le handler HTTP"
|
|
- path: "protocol.go"
|
|
provides: "SessionInfo avec champ WaitType"
|
|
- path: "protocol_test.go"
|
|
provides: "Test serialisation JSON de WaitType"
|
|
key_links:
|
|
- from: "hook.go"
|
|
to: "daemon.go"
|
|
via: "UpdateFromHook modifie le SessionRegistry"
|
|
pattern: "registry\\.UpdateFromHook"
|
|
- from: "hook.go"
|
|
to: "protocol.go"
|
|
via: "SessionInfo.WaitType"
|
|
pattern: "WaitType"
|
|
---
|
|
|
|
<objective>
|
|
Hook event processing core: types, mapping, handler HTTP, et mise a jour du registre.
|
|
|
|
Purpose: Poser toute la logique de traitement des events Claude Code hooks. Chaque type d'event (Notification, Stop, PostToolUse, PreToolUse) est mappe vers le bon State et WaitType.
|
|
Output: hook.go avec la logique, hook_test.go avec les tests, protocol.go enrichi avec WaitType.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/03-hook-server/03-RESEARCH.md
|
|
|
|
<interfaces>
|
|
<!-- Existing types the executor needs -->
|
|
|
|
From protocol.go:
|
|
```go
|
|
type SessionInfo struct {
|
|
PID int `json:"pid"`
|
|
SessionID string `json:"session_id"`
|
|
Cwd string `json:"cwd"`
|
|
GitBranch string `json:"git_branch"`
|
|
State string `json:"state"`
|
|
Preview string `json:"preview"`
|
|
Workspace string `json:"workspace"`
|
|
Label string `json:"label,omitempty"`
|
|
WaitingSince *time.Time `json:"waiting_since,omitempty"`
|
|
}
|
|
```
|
|
|
|
From daemon.go:
|
|
```go
|
|
type TrackedSession struct {
|
|
Info SessionInfo
|
|
PrevState string
|
|
}
|
|
|
|
type SessionRegistry struct {
|
|
mu sync.RWMutex
|
|
sessions map[string]*TrackedSession
|
|
}
|
|
|
|
func (r *SessionRegistry) Update(info SessionInfo)
|
|
func (r *SessionRegistry) List() []SessionInfo
|
|
func (r *SessionRegistry) RemoveStale(activeIDs map[string]bool)
|
|
```
|
|
|
|
From daemon_test.go:
|
|
```go
|
|
func newTestDaemon(t *testing.T) *Daemon
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: HookEvent struct, processHookEvent mapping, UpdateFromHook, WaitType dans SessionInfo</name>
|
|
<files>hook.go, hook_test.go, protocol.go, protocol_test.go</files>
|
|
<read_first>
|
|
- protocol.go (SessionInfo actuel, a enrichir avec WaitType)
|
|
- daemon.go (SessionRegistry, TrackedSession, pattern Update)
|
|
- daemon_test.go (newTestDaemon helper, patterns de test)
|
|
- .planning/phases/03-hook-server/03-RESEARCH.md (payload JSON Claude Code, mapping events)
|
|
</read_first>
|
|
<behavior>
|
|
- TestProcessHookNotificationPermission: event Notification + notification_type=permission_prompt donne State="Needs Input", WaitType="permission"
|
|
- TestProcessHookNotificationIdle: event Notification + notification_type=idle_prompt donne State="Needs Input", WaitType="idle"
|
|
- TestProcessHookNotificationUnknown: event Notification + notification_type inconnu donne State="Needs Input", WaitType="question"
|
|
- TestProcessHookStop: event Stop donne State="Needs Input", WaitType="question"
|
|
- TestProcessHookPostToolUse: event PostToolUse donne State="Working", WaitType=""
|
|
- TestProcessHookPreToolUse: event PreToolUse donne State="Working", WaitType=""
|
|
- TestProcessHookIgnoresEmptySessionID: event avec session_id="" ne modifie pas le registre
|
|
- TestProcessHookIgnoresUnknownEvent: event avec hook_event_name inconnu ne modifie pas le registre
|
|
- TestUpdateFromHookCreatesNewEntry: session_id inconnu du registre cree une nouvelle entree
|
|
- TestUpdateFromHookSetsWaitingSince: transition Working vers NeedsInput met WaitingSince
|
|
- TestUpdateFromHookClearsWaitingSince: transition NeedsInput vers Working efface WaitingSince
|
|
- TestSessionInfoWaitTypeJSON: WaitType="permission" est serialise dans le JSON, WaitType="" est omis (omitempty)
|
|
</behavior>
|
|
<action>
|
|
1. Ajouter `WaitType string json:"wait_type,omitempty"` a SessionInfo dans protocol.go
|
|
2. Creer hook.go avec :
|
|
- HookEvent struct (session_id, transcript_path, cwd, hook_event_name, notification_type, message, title, last_assistant_message, stop_hook_active, tool_name). Tous les champs JSON avec tags corrects et omitempty pour les optionnels.
|
|
- `func (d *Daemon) processHookEvent(event HookEvent)` : switch sur event.HookEventName pour mapper vers state+waitType, puis appel a registry.UpdateFromHook. Ignorer session_id vide et events inconnus.
|
|
- `func (r *SessionRegistry) UpdateFromHook(sessionID, state, waitType, cwd string)` : lock, creer l'entree si absente, mettre a jour Info.SessionID/State/WaitType/Cwd, gerer la transition WaitingSince (meme logique que Update mais sans ecraser les autres champs).
|
|
3. Creer hook_test.go avec les 12 tests ci-dessus. Utiliser newTestDaemon pour les tests processHookEvent, NewRegistry directement pour les tests UpdateFromHook.
|
|
4. Ajouter un test dans protocol_test.go pour la serialisation JSON de WaitType.
|
|
|
|
NE PAS ajouter le handler HTTP dans cette tache (c'est la tache 2).
|
|
NE PAS modifier daemon.go (pas de startHookServer, pas de hookPort).
|
|
</action>
|
|
<verify>
|
|
<automated>nix-shell --run "go test -v -run 'TestProcessHook|TestUpdateFromHook|TestSessionInfoWaitType' ./..."</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q 'WaitType.*string.*json:"wait_type' protocol.go
|
|
- grep -q 'type HookEvent struct' hook.go
|
|
- grep -q 'func.*Daemon.*processHookEvent' hook.go
|
|
- grep -q 'func.*SessionRegistry.*UpdateFromHook' hook.go
|
|
- grep -q 'TestProcessHookNotificationPermission' hook_test.go
|
|
- grep -q 'TestProcessHookStop' hook_test.go
|
|
- grep -q 'TestUpdateFromHookCreatesNewEntry' hook_test.go
|
|
- grep -q 'TestSessionInfoWaitTypeJSON' protocol_test.go
|
|
</acceptance_criteria>
|
|
<done>Les 12 tests passent. HookEvent parse le JSON Claude Code. processHookEvent mappe les 4 types d'events vers le bon State/WaitType. UpdateFromHook gere les nouvelles sessions et les transitions WaitingSince. SessionInfo.WaitType est serialise en JSON.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: HTTP handler POST /hook avec validation et protection</name>
|
|
<files>hook.go, hook_test.go</files>
|
|
<read_first>
|
|
- hook.go (HookEvent et processHookEvent crees par Task 1)
|
|
- daemon_test.go (newTestDaemon, patterns httptest)
|
|
- .planning/phases/03-hook-server/03-RESEARCH.md (handler pattern, MaxBytesReader)
|
|
</read_first>
|
|
<behavior>
|
|
- TestHandleHookPostOK: POST /hook avec payload Notification valide retourne 200 et met a jour le registre
|
|
- TestHandleHookMethodNotAllowed: GET /hook retourne 405
|
|
- TestHandleHookBadJSON: POST /hook avec body invalide retourne 400
|
|
- TestHandleHookBodyTooLarge: POST /hook avec body > 64KB retourne 400
|
|
</behavior>
|
|
<action>
|
|
1. Ajouter dans hook.go :
|
|
- `func (d *Daemon) handleHook(w http.ResponseWriter, r *http.Request)` : verifie Method==POST (sinon 405), applique http.MaxBytesReader(w, r.Body, 64*1024), decode JSON dans HookEvent (sinon 400), appelle processHookEvent, retourne 200 OK.
|
|
2. Ajouter les 4 tests dans hook_test.go. Utiliser httptest.NewRequest + httptest.NewRecorder pour tester le handler directement (pas besoin d'ouvrir un port).
|
|
3. Pour TestHandleHookPostOK, verifier que le registre contient la session avec le bon WaitType apres l'appel.
|
|
4. Pour TestHandleHookBodyTooLarge, generer un body de 65KB.
|
|
|
|
NE PAS demarrer de serveur HTTP dans cette tache. Le handler est teste unitairement via httptest.
|
|
</action>
|
|
<verify>
|
|
<automated>nix-shell --run "go test -v -run 'TestHandleHook' ./..."</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q 'func.*Daemon.*handleHook' hook.go
|
|
- grep -q 'MaxBytesReader' hook.go
|
|
- grep -q 'StatusMethodNotAllowed' hook.go
|
|
- grep -q 'TestHandleHookPostOK' hook_test.go
|
|
- grep -q 'TestHandleHookMethodNotAllowed' hook_test.go
|
|
- grep -q 'TestHandleHookBadJSON' hook_test.go
|
|
- grep -q 'TestHandleHookBodyTooLarge' hook_test.go
|
|
</acceptance_criteria>
|
|
<done>Le handler HTTP /hook accepte les POST valides (200), rejette les non-POST (405), rejette le JSON invalide (400), limite la taille du body a 64KB. 4 tests passent.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
nix-shell --run "go test -v -run 'TestProcessHook|TestUpdateFromHook|TestHandleHook|TestSessionInfoWaitType' ./..."
|
|
nix-shell --run "go vet ./..."
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 16 tests passent (12 task 1 + 4 task 2)
|
|
- HookEvent struct couvre tous les champs du payload Claude Code
|
|
- processHookEvent mappe les 4 types d'events correctement
|
|
- UpdateFromHook gere les sessions inconnues et les transitions WaitingSince
|
|
- handleHook valide method, body size, et JSON
|
|
- SessionInfo.WaitType visible dans le JSON
|
|
- Aucune regression sur les tests existants
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-hook-server/03-01-SUMMARY.md`
|
|
</output>
|