docs(03): create phase plan for hook server
This commit is contained in:
218
.planning/phases/03-hook-server/03-01-PLAN.md
Normal file
218
.planning/phases/03-hook-server/03-01-PLAN.md
Normal file
@@ -0,0 +1,218 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user