@@ -57,30 +57,18 @@ func runCheckpoint(cmd *cobra.Command, gitRoot string) error {
5757// doCheckpoint captures the current session after a commit.
5858// Extracted so sync can call it without a cobra.Command.
5959func doCheckpoint (gitRoot string , w io.Writer ) error {
60- // Find session directory for this repo.
61- sessionDir := session .FindSessionDir (gitRoot )
62- if sessionDir == "" {
63- return nil
64- }
65-
66- files , err := session .FindSessionFiles (sessionDir )
67- if err != nil {
68- if os .IsNotExist (err ) {
69- return nil
70- }
71- return fmt .Errorf ("find session files: %w" , err )
72- }
73- if len (files ) == 0 {
74- return nil
75- }
76-
7760 // Open data DB.
7861 dataDB , err := db .OpenData (gitRoot )
7962 if err != nil {
8063 return fmt .Errorf ("open data DB: %w" , err )
8164 }
8265 defer dataDB .Close ()
8366
67+ // Run forward-only migrations for existing DBs.
68+ if err := db .MigrateDataSchema (dataDB ); err != nil {
69+ return fmt .Errorf ("migrate data schema: %w" , err )
70+ }
71+
8472 // Verify DB is healthy by running a simple query.
8573 if _ , err := dataDB .Exec ("SELECT 1" ); err != nil {
8674 return fmt .Errorf ("data DB is corrupt or unreadable: %w" , err )
@@ -97,107 +85,144 @@ func doCheckpoint(gitRoot string, w io.Writer) error {
9785 // Collect unique relative file paths from file-modifying tool_calls across all sessions.
9886 toolCallPaths := make (map [string ]struct {})
9987
100- for _ , f := range files {
101- // Incremental: check checkpoint_state to skip unchanged files.
102- info , statErr := os .Stat (f )
103- if statErr != nil {
104- continue
105- }
106-
107- data , err := os .ReadFile (f )
88+ // Iterate all adapters to discover sessions from all known agents.
89+ for _ , adapter := range session .Adapters {
90+ refs , err := adapter .Discover (gitRoot )
10891 if err != nil {
10992 continue
11093 }
111- if len (data ) == 0 {
112- continue
113- }
114-
115- hash := sha256Hex (data )
11694
117- // Check cached state — skip if size + hash match.
118- cachedSize , cachedHash , found , csErr := db .GetCheckpointState (dataDB , f )
119- if csErr != nil {
120- return fmt .Errorf ("check checkpoint state: %w" , csErr )
121- }
122- if found && cachedSize == info .Size () && cachedHash == hash {
123- continue
124- }
95+ for _ , ref := range refs {
96+ // Determine cache key for deduplication.
97+ cacheKey := ref .Path
98+ if cacheKey == "" {
99+ cacheKey = adapter .Name () + ":" + ref .DBID
100+ }
125101
126- exists , err := db .SessionExistsByHash (dataDB , hash )
127- if err != nil {
128- return fmt .Errorf ("dedup check: %w" , err )
129- }
130- if exists {
131- // File changed but session already exists (re-parse produced same hash).
132- // Update state cache and skip.
133- _ = db .UpsertCheckpointState (dataDB , f , info .Size (), hash )
134- continue
135- }
102+ // For file-based refs, check size+hash cache.
103+ var data []byte
104+ var hash string
105+ if ref .Path != "" {
106+ info , statErr := os .Stat (ref .Path )
107+ if statErr != nil {
108+ continue
109+ }
110+
111+ fileData , err := os .ReadFile (ref .Path )
112+ if err != nil || len (fileData ) == 0 {
113+ continue
114+ }
115+ data = fileData
116+ hash = sha256Hex (data )
117+
118+ cachedSize , cachedHash , found , csErr := db .GetCheckpointState (dataDB , cacheKey )
119+ if csErr != nil {
120+ return fmt .Errorf ("check checkpoint state: %w" , csErr )
121+ }
122+ if found && cachedSize == info .Size () && cachedHash == hash {
123+ continue
124+ }
125+ } else {
126+ // DB-based ref — use DBID as hash seed for dedup.
127+ hash = sha256Hex ([]byte (cacheKey ))
128+
129+ _ , _ , found , csErr := db .GetCheckpointState (dataDB , cacheKey )
130+ if csErr != nil {
131+ return fmt .Errorf ("check checkpoint state: %w" , csErr )
132+ }
133+ if found {
134+ continue
135+ }
136+ }
136137
137- payload , err := session .ParseTranscript (data )
138- if err != nil {
139- continue
140- }
138+ exists , err := db .SessionExistsByHash (dataDB , hash )
139+ if err != nil {
140+ return fmt .Errorf ("dedup check: %w" , err )
141+ }
142+ if exists {
143+ if ref .Path != "" {
144+ info , _ := os .Stat (ref .Path )
145+ if info != nil {
146+ _ = db .UpsertCheckpointState (dataDB , cacheKey , info .Size (), hash )
147+ }
148+ } else {
149+ _ = db .UpsertCheckpointState (dataDB , cacheKey , 0 , hash )
150+ }
151+ continue
152+ }
141153
142- // Redact secrets and anonymize paths before any DB insertion.
143- scrub .Scrub (payload )
154+ payload , err := adapter .Parse (ref )
155+ if err != nil || payload == nil {
156+ continue
157+ }
144158
145- if len (payload .Turns ) == 0 && len (payload .ToolCalls ) == 0 {
146- continue
147- }
159+ // Redact secrets and anonymize paths before any DB insertion.
160+ scrub .Scrub (payload )
148161
149- sessionID := newID ()
150- capturedAt := time .Now ().UTC ()
162+ if len (payload .Turns ) == 0 && len (payload .ToolCalls ) == 0 {
163+ continue
164+ }
151165
152- // Insert session into DuckDB.
153- if err := db .InsertSession (
154- dataDB , sessionID , "" , hash ,
155- payload .ActorType , payload .AgentID , email , payload .Branch , capturedAt .Format (time .RFC3339 ),
156- ); err != nil {
157- return fmt .Errorf ("insert session: %w" , err )
158- }
166+ sessionID := newID ()
167+ capturedAt := time .Now ().UTC ()
159168
160- // Insert turns into DuckDB.
161- for i , t := range payload .Turns {
162- ts := ""
163- if ! t .Timestamp .IsZero () {
164- ts = t .Timestamp .UTC ().Format (time .RFC3339 )
165- }
166- if err := db .InsertTurn (dataDB , newID (), sessionID , i , t .Role , t .Content , ts ); err != nil {
167- return fmt .Errorf ("insert turn: %w" , err )
169+ // Insert session into DuckDB.
170+ if err := db .InsertSession (
171+ dataDB , sessionID , "" , hash ,
172+ payload .ActorType , payload .AgentID , email , payload .Branch , capturedAt .Format (time .RFC3339 ),
173+ payload .Source ,
174+ ); err != nil {
175+ return fmt .Errorf ("insert session: %w" , err )
168176 }
169- }
170177
171- // Insert tool calls into DuckDB.
172- for i , tc := range payload .ToolCalls {
173- if err := db .InsertToolCall (dataDB , newID (), sessionID , i , tc .Tool , tc .Path , tc .CmdPrefix ); err != nil {
174- return fmt .Errorf ("insert tool_call: %w" , err )
178+ // Insert turns into DuckDB.
179+ for i , t := range payload .Turns {
180+ ts := ""
181+ if ! t .Timestamp .IsZero () {
182+ ts = t .Timestamp .UTC ().Format (time .RFC3339 )
183+ }
184+ if err := db .InsertTurn (dataDB , newID (), sessionID , i , t .Role , t .Content , ts ); err != nil {
185+ return fmt .Errorf ("insert turn: %w" , err )
186+ }
175187 }
176- }
177188
178- // Collect file-modifying tool_call paths for files_touched supplementation.
179- for _ , tc := range payload .ToolCalls {
180- if tc .Path == "" {
181- continue
182- }
183- switch tc .Tool {
184- case "Write" , "Edit" , "NotebookEdit" :
185- default :
186- continue
189+ // Insert tool calls into DuckDB.
190+ for i , tc := range payload .ToolCalls {
191+ if err := db .InsertToolCall (dataDB , newID (), sessionID , i , tc .Tool , tc .Path , tc .CmdPrefix ); err != nil {
192+ return fmt .Errorf ("insert tool_call: %w" , err )
193+ }
187194 }
188- rel := strings .TrimPrefix (tc .Path , gitRoot + "/" )
189- if rel == tc .Path {
190- // Path is not under gitRoot — external file, skip.
191- continue
195+
196+ // Collect file-modifying tool_call paths for files_touched supplementation.
197+ for _ , tc := range payload .ToolCalls {
198+ if tc .Path == "" {
199+ continue
200+ }
201+ switch tc .Tool {
202+ case "Write" , "Edit" , "NotebookEdit" :
203+ default :
204+ continue
205+ }
206+ rel := strings .TrimPrefix (tc .Path , gitRoot + "/" )
207+ if rel == tc .Path {
208+ continue
209+ }
210+ toolCallPaths [rel ] = struct {}{}
192211 }
193- toolCallPaths [rel ] = struct {}{}
194- }
195212
196- // Update checkpoint state cache.
197- _ = db .UpsertCheckpointState (dataDB , f , info .Size (), hash )
213+ // Update checkpoint state cache.
214+ if ref .Path != "" {
215+ info , _ := os .Stat (ref .Path )
216+ if info != nil {
217+ _ = db .UpsertCheckpointState (dataDB , cacheKey , info .Size (), hash )
218+ }
219+ } else {
220+ _ = db .UpsertCheckpointState (dataDB , cacheKey , 0 , hash )
221+ }
198222
199- sessionIDs = append (sessionIDs , sessionID )
200- inserted ++
223+ sessionIDs = append (sessionIDs , sessionID )
224+ inserted ++
225+ }
201226 }
202227
203228 if inserted == 0 {
0 commit comments