diff --git a/cmd/capytrace/main.go b/cmd/capytrace/main.go index 95b5045..761d150 100644 --- a/cmd/capytrace/main.go +++ b/cmd/capytrace/main.go @@ -113,7 +113,10 @@ func handleEnd() { case "sqlite": home, _ := os.UserHomeDir() dataDir := filepath.Join(home, ".local", "share", "capytrace") - os.MkdirAll(dataDir, 0755) + if err := os.MkdirAll(dataDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Failed to create data directory: %v\n", err) + os.Exit(1) + } exp = exporter.NewSQLiteExporter(dataDir) default: exp = &exporter.MarkdownExporter{} diff --git a/internal/exporter/sqlite.go b/internal/exporter/sqlite.go index 3a6a39f..790898f 100644 --- a/internal/exporter/sqlite.go +++ b/internal/exporter/sqlite.go @@ -3,6 +3,7 @@ package exporter import ( "database/sql" "fmt" + "os" "path/filepath" "time" @@ -30,7 +31,11 @@ func (e *SQLiteExporter) Export(session *models.Session, savePath string) error if err != nil { return fmt.Errorf("failed to open database: %w", err) } - defer db.Close() + defer func() { + if closeErr := db.Close(); closeErr != nil { + fmt.Fprintf(os.Stderr, "Failed to close database: %v\n", closeErr) + } + }() // Create tables if they don't exist if err := e.createTables(db); err != nil { @@ -42,7 +47,11 @@ func (e *SQLiteExporter) Export(session *models.Session, savePath string) error if err != nil { return fmt.Errorf("failed to begin transaction: %w", err) } - defer tx.Rollback() + defer func() { + if rollbackErr := tx.Rollback(); rollbackErr != nil && rollbackErr != sql.ErrTxDone { + fmt.Fprintf(os.Stderr, "Failed to rollback transaction: %v\n", rollbackErr) + } + }() // Insert or update session _, err = tx.Exec(` @@ -151,7 +160,11 @@ func (e *SQLiteExporter) GetSessionStats(sessionID string) (*models.SessionSumma if err != nil { return nil, err } - defer db.Close() + defer func() { + if closeErr := db.Close(); closeErr != nil { + fmt.Fprintf(os.Stderr, "Failed to close database: %v\n", closeErr) + } + }() var summary models.SessionSummary var startTime, endTime time.Time @@ -177,7 +190,11 @@ func (e *SQLiteExporter) GetSessionStats(sessionID string) (*models.SessionSumma if err != nil { return nil, err } - defer rows.Close() + defer func() { + if closeErr := rows.Close(); closeErr != nil { + fmt.Fprintf(os.Stderr, "Failed to close rows: %v\n", closeErr) + } + }() for rows.Next() { var eventType string diff --git a/internal/filter/cursor_filter.go b/internal/filter/cursor_filter.go index 7284c78..3307bd6 100644 --- a/internal/filter/cursor_filter.go +++ b/internal/filter/cursor_filter.go @@ -14,7 +14,6 @@ import ( // remains idle or when followed by significant events like text changes. type CursorFilter struct { mu sync.Mutex - lastCursorEvent *models.Event lastEventTime time.Time pendingEvent *models.Event debounceTimer *time.Timer diff --git a/internal/recorder/session.go b/internal/recorder/session.go index 798f111..34488dd 100644 --- a/internal/recorder/session.go +++ b/internal/recorder/session.go @@ -105,13 +105,15 @@ func (s *Session) Start() error { activeSessionsMu.Unlock() // Record initial event - s.addEvent(models.Event{ + if err := s.addEvent(models.Event{ Type: "session_start", Timestamp: s.StartTime, Data: models.EventData{ Note: fmt.Sprintf("Started debugging session in %s", s.ProjectPath), }, - }) + }); err != nil { + return fmt.Errorf("failed to add initial event: %w", err) + } return s.save() } @@ -126,7 +128,7 @@ func (s *Session) End() error { // Flush any pending cursor events if pendingEvent := s.cursorFilter.FlushPending(); pendingEvent != nil { - s.Session.Events = append(s.Session.Events, *pendingEvent) + s.Events = append(s.Events, *pendingEvent) } s.cursorFilter.Stop() @@ -134,7 +136,7 @@ func (s *Session) End() error { s.Active = false // Record end event - s.Session.Events = append(s.Session.Events, models.Event{ + s.Events = append(s.Events, models.Event{ Type: "session_end", Timestamp: s.EndTime, Data: models.EventData{ @@ -191,7 +193,9 @@ func (s *Session) RecordEdit(filename, line, col, lineCount, changedTick string) // File edits are context triggers - process through filter first if filteredEvent := s.cursorFilter.ProcessEvent(&event); filteredEvent != nil { - s.addEvent(*filteredEvent) + if err := s.addEvent(*filteredEvent); err != nil { + return fmt.Errorf("failed to add filtered event: %w", err) + } } return s.addEvent(event) @@ -209,7 +213,9 @@ func (s *Session) RecordTerminalCommand(command string) error { // Terminal commands are context triggers if filteredEvent := s.cursorFilter.ProcessEvent(&event); filteredEvent != nil { - s.addEvent(*filteredEvent) + if err := s.addEvent(*filteredEvent); err != nil { + return fmt.Errorf("failed to add filtered event: %w", err) + } } return s.addEvent(event) @@ -276,7 +282,7 @@ func (s *Session) RecordLSPDiagnostic(filename, line, col, message, level string // addEvent appends an event to the session and persists it. func (s *Session) addEvent(event models.Event) error { s.mu.Lock() - s.Session.Events = append(s.Session.Events, event) + s.Events = append(s.Events, event) s.mu.Unlock() return s.save() @@ -410,13 +416,15 @@ func ResumeSession(sessionName, savePath string, filterConfig *filter.FilterConf session.startPeriodicAggregation(5 * time.Minute) // Record resume event - session.addEvent(models.Event{ + if err := session.addEvent(models.Event{ Type: "session_resume", Timestamp: time.Now(), Data: models.EventData{ Note: "Session resumed", }, - }) + }); err != nil { + return nil, fmt.Errorf("failed to add resume event: %w", err) + } return session, session.save() } diff --git a/lua/capytrace/config.lua b/lua/capytrace/config.lua index d371f3c..a64886e 100644 --- a/lua/capytrace/config.lua +++ b/lua/capytrace/config.lua @@ -8,11 +8,11 @@ local default_config = { record_git_diff = true, auto_save_on_exit = true, max_cursor_events = 100, -- Limit cursor movement recordings - + -- Smart Filter configuration (Anti-Spam Cursor Filter) filter_threshold = 500, -- Idle threshold in milliseconds (default: 500ms) debounce_interval = 200, -- Debounce interval for cursor movements (default: 200ms) - + -- Smart Aggregation configuration (Activity Block Builder) aggregation = { merge_window = 2000, -- Time window for merging file_edit events in milliseconds (default: 2s) @@ -28,7 +28,7 @@ local default_config = { }, periodic_update_interval = 300000, -- Update SESSION_SUMMARY.md every N milliseconds (default: 5min) }, - + log_events = { terminal_commands = true, file_open = true,