diff --git a/README.md b/README.md index 60287e2..398254e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ ⭐️ `cnotes` is pronounced like 💵 c-notes or 🏊 [_cenotes_](https://en.wikipedia.org/wiki/Cenote) +This tool ensures no duplicate content between commits by tracking the last processed event timestamp. + ## Quick Start diff --git a/cmd/root.go b/cmd/root.go index c7eb43a..d2bcd0f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -142,15 +142,15 @@ func processGitCommit(ctx context.Context, input HookInput, bashInput BashToolIn return nil } - // Get the timestamp of the previous commit in this session - previousCommitTime := getLastCommitTimeForSession(ctx, notesManager, input.SessionID) + // Get the last event time from the previous commit to avoid duplicates + lastEventTime := getLastEventTimeFromPreviousCommit(ctx, notesManager) // Small delay to ensure transcript is written time.Sleep(100 * time.Millisecond) - // Extract conversation context since the last commit + // Extract conversation context since the last event contextExtractor := conv.NewContextExtractor(cfg) - conversationContext, err := contextExtractor.ExtractContextSince(input.TranscriptPath, input.SessionID, previousCommitTime) + conversationContext, err := contextExtractor.ExtractContextSince(input.TranscriptPath, input.SessionID, lastEventTime) if err != nil { return fmt.Errorf("failed to extract conversation context: %w", err) } @@ -174,6 +174,7 @@ func processGitCommit(ctx context.Context, input HookInput, bashInput BashToolIn ToolsUsed: toolsUsed, CommitContext: buildCommitContext(bashInput.Command, gitOutput), ClaudeVersion: "claude-sonnet-4-20250514", + LastEventTime: conversationContext.LastEventTime, } // Add the note @@ -245,26 +246,35 @@ func contains(slice []string, item string) bool { return false } -// getLastCommitTimeForSession finds the most recent commit time for this session -func getLastCommitTimeForSession(ctx context.Context, notesManager *notes.NotesManager, sessionID string) time.Time { - // For now, let's use a simpler approach - get the time of the previous commit - // This works well when commits are made sequentially in a session - cmd := exec.Command("git", "log", "-1", "--format=%cI", "HEAD~1") - output, err := cmd.Output() - if err != nil { - // No previous commit or error, return zero time - return time.Time{} +// getLastEventTimeFromPreviousCommit gets the last processed event time from the previous commit +func getLastEventTimeFromPreviousCommit(ctx context.Context, notesManager *notes.NotesManager) time.Time { + // Try to get the previous commit's note + note, err := notesManager.GetConversationNote(ctx, "HEAD~1") + if err != nil || note == nil { + // No previous commit or note, use a time buffer approach + cmd := exec.Command("git", "log", "-1", "--format=%cI", "HEAD~1") + output, err := cmd.Output() + if err != nil { + return time.Time{} + } + + timeStr := strings.TrimSpace(string(output)) + commitTime, err := time.Parse(time.RFC3339, timeStr) + if err != nil { + return time.Time{} + } + + // Use a 60 second buffer for the first commit in a session + return commitTime.Add(-60 * time.Second) } - timeStr := strings.TrimSpace(string(output)) - commitTime, err := time.Parse(time.RFC3339, timeStr) - if err != nil { - return time.Time{} + // If we have a previous note with LastEventTime, use it + if !note.LastEventTime.IsZero() { + return note.LastEventTime } - // Add a larger buffer to ensure we capture user prompts that triggered the work - // User prompts often happen 30-60 seconds before the commit - return commitTime.Add(-60 * time.Second) + // Fall back to commit time with buffer + return note.Timestamp.Add(-60 * time.Second) } // readStdinWithTimeout reads from stdin with a timeout diff --git a/internal/context/conversation.go b/internal/context/conversation.go index 9f83da3..bdc082c 100644 --- a/internal/context/conversation.go +++ b/internal/context/conversation.go @@ -20,6 +20,7 @@ type ConversationContext struct { ClaudeResponses []string `json:"claude_responses"` ToolInteractions []ToolInteraction `json:"tool_interactions"` Events []ConversationEvent `json:"events"` // New: chronological events + LastEventTime time.Time `json:"last_event_time"` // Track the latest event timestamp } // ConversationEvent represents any event in the conversation @@ -319,6 +320,13 @@ func (ce *ContextExtractor) parseTranscriptContent(content, sessionID string, si } } + // Track the last event time from all events + for _, event := range context.Events { + if event.Timestamp.After(context.LastEventTime) { + context.LastEventTime = event.Timestamp + } + } + return context } diff --git a/internal/notes/git_notes.go b/internal/notes/git_notes.go index 4e7bb6a..90fcedf 100644 --- a/internal/notes/git_notes.go +++ b/internal/notes/git_notes.go @@ -17,6 +17,7 @@ type ConversationNote struct { ToolsUsed []string `json:"tools_used"` CommitContext string `json:"commit_context"` ClaudeVersion string `json:"claude_version"` + LastEventTime time.Time `json:"last_event_time,omitempty"` // Track last processed event to avoid duplicates } // NotesManager handles git notes operations for Claude conversations