Files
Snow LeeandClaude Fable 5 c1f0d0f8ee feat(sync): session-linked notes — stamp changes with the agent session
`bdrive sync --note <text>` stamps session context onto every op the cycle
commits and persists it in the volume store (note.json, --note-ttl default
30m) so the daemon's own scans stamp it too — winning the race between
one-shot hook syncs and the 3s daemon scan. The plugin sync hook extracts
session_id from hook stdin JSON and passes it automatically, so history
links every change to the Claude Code session that made it. Conflict-copy
ops keep their own note; expired/cleared notes stop applying.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-11 10:56:53 -07:00

57 lines
1.5 KiB
Go

package store
import (
"os"
"path/filepath"
"time"
)
// The session note is transient per-volume context — "why edits are being
// made right now" (e.g. a Claude Code session id set by the plugin's sync
// hook). Whichever scanner commits an op while the note is live (the daemon
// or a one-shot `bdrive sync`) stamps it into Op.Note, so provenance doesn't
// depend on winning the race against the daemon's scan interval. The TTL
// keeps a stale note from mislabeling unrelated edits made hours later.
// Note is the on-disk shape of the session note.
type Note struct {
Text string `json:"text"`
Expires time.Time `json:"expires,omitzero"` // zero = never expires
}
func (s *Store) notePath() string { return filepath.Join(s.dir, "note.json") }
// SaveNote sets the session note. ttl > 0 bounds its life; empty text clears.
func (s *Store) SaveNote(text string, ttl time.Duration) error {
if text == "" {
return s.ClearNote()
}
n := Note{Text: text}
if ttl > 0 {
n.Expires = time.Now().Add(ttl)
}
return WriteJSONAtomic(s.notePath(), n)
}
// ClearNote removes the session note.
func (s *Store) ClearNote() error {
err := os.Remove(s.notePath())
if os.IsNotExist(err) {
return nil
}
return err
}
// LoadNote returns the live session note, or "" if none is set or it has
// expired. Never errors: a missing or unreadable note is just no note.
func (s *Store) LoadNote() string {
var n Note
if err := readJSON(s.notePath(), &n); err != nil {
return ""
}
if !n.Expires.IsZero() && time.Now().After(n.Expires) {
return ""
}
return n.Text
}