1
0
Fork 0
mirror of https://github.com/imjasonh/cnotes synced 2026-07-21 14:38:02 +00:00

Implement proper deduplication using last event timestamp tracking

This commit is contained in:
Jason Hall 2025-07-21 14:51:00 -04:00
parent 2cc9c8c00d
commit 3cfe51703e
Failed to extract signature
4 changed files with 41 additions and 20 deletions

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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