api/checkpoint: add CompactTranscriptStart marker to session metadata · Entire

api/checkpoint: add CompactTranscriptStart marker to session metadata

ed36714 → main

Add CompactTranscriptStart *int (compact_transcript_start) plus a GetCompactTranscriptStart accessor. transcript.jsonl now stores the full compacted session, so this marks where a checkpoint's slice begins: compactLines[CompactTranscriptStart:].

A pointer is used so "absent" (a legacy checkpoint whose transcript.jsonl holds only its delta, read from line 0) is distinguishable from 0 (a full file whose first checkpoint starts at the beginning).

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

Changes

2

320 unmodified lines
// Transcript position at checkpoint start - tracks what was added during this checkpoint
TranscriptIdentifierAtStart string `json:"transcript_identifier_at_start,omitempty"` // Last identifier when checkpoint started (UUID for Claude, message ID for Gemini)
CheckpointTranscriptStart   int    `json:"checkpoint_transcript_start,omitempty"`    // Transcript line offset at start of this checkpoint's data
CheckpointTranscriptStart   int    `json:"checkpoint_transcript_start,omitempty"`    // Raw transcript (full.jsonl) line offset at start of this checkpoint's data

// Deprecated: Use CheckpointTranscriptStart instead. Written for backward compatibility with older CLI versions.
TranscriptLinesAtStart int `json:"transcript_lines_at_start,omitempty"`

// CompactTranscriptStart is the line offset in the compact transcript.jsonl
// at which this checkpoint's data begins. transcript.jsonl stores the full
// compacted session (each checkpoint is self-contained), so readers segment
// this checkpoint's slice as compactLines[CompactTranscriptStart:].
//
// A nil pointer marks a legacy checkpoint whose transcript.jsonl holds only
// this checkpoint's delta (CLI versions before the full-compact-transcript
// change), which is read as-is from line 0. A pointer is used so that "absent"
// (legacy delta file) is distinguishable from 0 (full file, first checkpoint).
CompactTranscriptStart *int `json:"compact_transcript_start,omitempty"`

// Token usage for this checkpoint
TokenUsage *types.TokenUsage `json:"token_usage,omitempty"`
return m.TranscriptLinesAtStart
}

// GetCompactTranscriptStart returns the line offset in transcript.jsonl at which
// this checkpoint's data begins, and whether the offset was recorded. ok=false
// means a legacy checkpoint whose transcript.jsonl holds only this checkpoint's
delta (read it from line 0); ok=true with offset 0 means the full-compact file
// whose first checkpoint starts at the beginning.
func (m Metadata) GetCompactTranscriptStart() (offset int, ok bool) {
if m.CompactTranscriptStart == nil {
return 0, false
}
return *m.CompactTranscriptStart, true
}

// SessionFilePaths contains the absolute paths to session files from the git tree root.
// Paths include the full checkpoint path prefix (e.g., "/a1/b2c3d4e5f6/1/metadata.json").
// Used in CheckpointSummary.Sessions to map session IDs to their file locations.
package checkpoint

import "testing"
import (
    "encoding/json"
    "strings"
    "testing"
)

func TestImportedFlagsOnSummaryAndInfo(t *testing.T) {
    t.Parallel()
}

func TestGetCompactTranscriptStart(t *testing.T) {
    t.Parallel()

// nil pointer = legacy checkpoint whose transcript.jsonl holds only the delta.
    if offset, ok := (Metadata{}).GetCompactTranscriptStart(); ok || offset != 0 {
        t.Fatalf("nil: got (%d, %v), want (0, false)", offset, ok)
    }

// Pointer to 0 = full compact file whose first checkpoint starts at line 0.
    // Must be distinguishable from the nil (legacy) case above.
    zero := 0
    if offset, ok := (Metadata{CompactTranscriptStart: &zero}).GetCompactTranscriptStart(); !ok || offset != 0 {
        t.Fatalf("&0: got (%d, %v), want (0, true)", offset, ok)
    }

five := 5
    if offset, ok := (Metadata{CompactTranscriptStart: &five}).GetCompactTranscriptStart(); !ok || offset != 5 {
        t.Fatalf("&5: got (%d, %v), want (5, true)", offset, ok)
    }
}

func TestCompactTranscriptStart_JSONRoundTrip(t *testing.T) {
    t.Parallel()

// nil is omitted entirely, so legacy readers see no field.
    b, err := json.Marshal(Metadata{})
    if err != nil {
        t.Fatalf("marshal: %v", err)
    }
    if strings.Contains(string(b), "compact_transcript_start") {
        t.Fatalf("nil pointer should be omitted, got: %s", b)
    }

// A set value (including 0) round-trips and stays distinguishable from absent.
    zero := 0
    b, err = json.Marshal(Metadata{CompactTranscriptStart: &zero})
    if err != nil {
        t.Fatalf("marshal: %v", err)
    }
    if !strings.Contains(string(b), `"compact_transcript_start":0`) {
        t.Fatalf("expected explicit 0 in JSON, got: %s", b)
    }

var got Metadata
    if err := json.Unmarshal(b, &got); err != nil {
        t.Fatalf("unmarshal: %v", err)
    }
    if offset, ok := got.GetCompactTranscriptStart(); !ok || offset != 0 {
        t.Fatalf("round-trip: got (%d, %v), want (0, true)", offset, ok)
    }
}