Context
The repetition and emotion models (#80, #81) are cheap enough to run incrementally, but the natural trigger for recomputing learning scores is "a Claude Code session just ended". At that point you have a complete transcript, Claude is idle, and Klonode can run analysis without competing for tokens or disk.
This issue adds the detection step plus the trigger plumbing.
Detection
A session is "ended" when either:
- Timeout — the session's JSONL file hasn't grown in 10 minutes. Heuristic, cheap, works for most cases.
- Explicit end marker — Claude Code writes certain top-level line types at session close (needs verification against a completed session). If present, prefer this over the timeout.
- Process exit — if Claude Code is a child process we can wait on. Not the normal case; Klonode tails standalone sessions.
The server session watcher gains a `sessionState` map: `{sessionId -> {lastAppendAt, status: 'active' | 'idle' | 'ended'}}`.
Trigger
On transition to `ended`:
- Run `computeRepetition()` and `computeUrgency()` over the observation log slice for this session + all prior sessions.
- Diff the new learning.json against the old one. Only emit suggestions for nodes whose scores crossed a threshold since the last computation.
- Append new suggestions to `.klonode/suggestions.jsonl`.
- Emit an SSE event `session-ended` so the client can show a notification ("5 new suggestions").
UI
- Top bar watcher pill gains an extra state: "3 suggestions pending" after a session-end fires suggestions.
- Click jumps to the suggestions panel (filed separately).
Out of scope
- The actual suggestions panel UI — separate issue.
- Running the analyzer during an active session. Deliberately debounced to session end to avoid competing with Claude for resources.
Dependencies
Blocked by #79, #80, #81. Required by the suggestions panel issue.
Part of the #77 pivot roadmap.
Context
The repetition and emotion models (#80, #81) are cheap enough to run incrementally, but the natural trigger for recomputing learning scores is "a Claude Code session just ended". At that point you have a complete transcript, Claude is idle, and Klonode can run analysis without competing for tokens or disk.
This issue adds the detection step plus the trigger plumbing.
Detection
A session is "ended" when either:
The server session watcher gains a `sessionState` map: `{sessionId -> {lastAppendAt, status: 'active' | 'idle' | 'ended'}}`.
Trigger
On transition to `ended`:
UI
Out of scope
Dependencies
Blocked by #79, #80, #81. Required by the suggestions panel issue.
Part of the #77 pivot roadmap.