From 66ffad0de620a5a1c05eac4d15c8ace689f9d451 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:55:23 -0700 Subject: [PATCH 01/11] Add support for v2 started_at compact transcript lines Entire-Checkpoint: e5883f33cb01 --- cmd/entire/cli/checkpoint/checkpoint.go | 6 + cmd/entire/cli/checkpoint/v2_committed.go | 2 +- cmd/entire/cli/checkpoint/v2_store_test.go | 36 ++++++ cmd/entire/cli/session/state.go | 8 ++ .../strategy/manual_commit_condensation.go | 30 +++-- .../cli/strategy/manual_commit_hooks.go | 2 + cmd/entire/cli/strategy/manual_commit_test.go | 109 ++++++++++++++++++ .../cli/strategy/manual_commit_types.go | 15 +-- 8 files changed, 193 insertions(+), 15 deletions(-) diff --git a/cmd/entire/cli/checkpoint/checkpoint.go b/cmd/entire/cli/checkpoint/checkpoint.go index b9db5476e..476a6df67 100644 --- a/cmd/entire/cli/checkpoint/checkpoint.go +++ b/cmd/entire/cli/checkpoint/checkpoint.go @@ -273,6 +273,12 @@ type WriteCommittedOptions struct { // CheckpointTranscriptStart is written to both CommittedMetadata.CheckpointTranscriptStart // and the deprecated CommittedMetadata.TranscriptLinesAtStart for backward compatibility. + // CompactTranscriptStart is the transcript.jsonl line offset at start of this + // checkpoint's data. Used by v2 /main where checkpoint_transcript_start in metadata + // should correspond to the compact transcript (transcript.jsonl) rather than the + // raw transcript (full.jsonl). V1 continues using CheckpointTranscriptStart. + CompactTranscriptStart int + // TokenUsage contains the token usage for this checkpoint TokenUsage *agent.TokenUsage diff --git a/cmd/entire/cli/checkpoint/v2_committed.go b/cmd/entire/cli/checkpoint/v2_committed.go index 85250c073..ba291ecc6 100644 --- a/cmd/entire/cli/checkpoint/v2_committed.go +++ b/cmd/entire/cli/checkpoint/v2_committed.go @@ -381,7 +381,7 @@ func (s *V2GitStore) writeMainSessionToSubdirectory(opts WriteCommittedOptions, IsTask: opts.IsTask, ToolUseID: opts.ToolUseID, TranscriptIdentifierAtStart: opts.TranscriptIdentifierAtStart, - CheckpointTranscriptStart: opts.CheckpointTranscriptStart, + CheckpointTranscriptStart: opts.CompactTranscriptStart, TokenUsage: opts.TokenUsage, SessionMetrics: opts.SessionMetrics, InitialAttribution: opts.InitialAttribution, diff --git a/cmd/entire/cli/checkpoint/v2_store_test.go b/cmd/entire/cli/checkpoint/v2_store_test.go index 3e4887713..47cb318bf 100644 --- a/cmd/entire/cli/checkpoint/v2_store_test.go +++ b/cmd/entire/cli/checkpoint/v2_store_test.go @@ -379,6 +379,42 @@ func TestV2GitStore_WriteCommittedMain_NoCompactTranscript_SkipsGracefully(t *te assert.Error(t, err, "transcript.jsonl should not exist when CompactTranscript is nil") } +func TestV2GitStore_WriteCommittedMain_UsesCompactTranscriptStart(t *testing.T) { + t.Parallel() + repo := initTestRepo(t) + store := NewV2GitStore(repo, "origin") + ctx := context.Background() + + cpID := id.MustCheckpointID("a1b2c3d4e5f7") + compactData := []byte("{\"v\":1,\"type\":\"user\",\"content\":\"hello\"}\n{\"v\":1,\"type\":\"assistant\",\"content\":\"hi\"}\n") + + _, err := store.writeCommittedMain(ctx, WriteCommittedOptions{ + CheckpointID: cpID, + SessionID: "test-session-compact-start", + Strategy: "manual-commit", + Transcript: []byte(`{"type":"human","message":"hello"}`), + CompactTranscript: compactData, + Prompts: []string{"hello"}, + AuthorName: "Test", + AuthorEmail: "test@test.com", + CheckpointTranscriptStart: 42, // full.jsonl offset (should NOT appear in v2 metadata) + CompactTranscriptStart: 15, // transcript.jsonl offset (should appear in v2 metadata) + }) + require.NoError(t, err) + + tree := v2MainTree(t, repo) + cpPath := cpID.Path() + + // Read session metadata from /main + metadataContent := v2ReadFile(t, tree, cpPath+"/0/"+paths.MetadataFileName) + var metadata CommittedMetadata + require.NoError(t, json.Unmarshal([]byte(metadataContent), &metadata)) + + // checkpoint_transcript_start should be the compact offset (15), not the full.jsonl offset (42) + assert.Equal(t, 15, metadata.CheckpointTranscriptStart, + "v2 /main metadata should use CompactTranscriptStart for checkpoint_transcript_start") +} + func TestV2GitStore_UpdateCommitted_WritesCompactTranscript(t *testing.T) { t.Parallel() repo := initTestRepo(t) diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index 79e01eb05..4dd85186d 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -109,6 +109,14 @@ type State struct { // against this value without reading the full transcript content. CheckpointTranscriptSize int64 `json:"checkpoint_transcript_size,omitempty"` + // CompactTranscriptStart is the transcript.jsonl (compact format) line offset + // where the current checkpoint cycle began. Parallel to CheckpointTranscriptStart + // (which tracks full.jsonl lines). Used for v2 checkpoint metadata where + // checkpoint_transcript_start should correspond to transcript.jsonl, not full.jsonl. + // Set to 0 at session start, updated to cumulative compact line count after each + // condensation. + CompactTranscriptStart int `json:"compact_transcript_start,omitempty"` + // Deprecated: CondensedTranscriptLines is replaced by CheckpointTranscriptStart. // Kept for backward compatibility with existing state files. // Use NormalizeAfterLoad() to migrate. diff --git a/cmd/entire/cli/strategy/manual_commit_condensation.go b/cmd/entire/cli/strategy/manual_commit_condensation.go index a14bff5c4..1b0f9ed75 100644 --- a/cmd/entire/cli/strategy/manual_commit_condensation.go +++ b/cmd/entire/cli/strategy/manual_commit_condensation.go @@ -1,6 +1,7 @@ package strategy import ( + "bytes" "context" "encoding/json" "errors" @@ -249,6 +250,7 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re redactedForCompact = nil } writeOpts.CompactTranscript = compactTranscriptForV2(ctx, ag, redactedForCompact, state.CheckpointTranscriptStart) + writeOpts.CompactTranscriptStart = state.CompactTranscriptStart } // Write checkpoint metadata to v1 branch @@ -258,14 +260,20 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re writeCommittedV2IfEnabled(ctx, repo, writeOpts) + compactLines := 0 + if writeOpts.CompactTranscript != nil { + compactLines = countCompactLines(writeOpts.CompactTranscript) + } + return &CondenseResult{ - CheckpointID: checkpointID, - SessionID: state.SessionID, - CheckpointsCount: state.StepCount, - FilesTouched: sessionData.FilesTouched, - Prompts: sessionData.Prompts, - TotalTranscriptLines: sessionData.FullTranscriptLines, - Transcript: sessionData.Transcript, + CheckpointID: checkpointID, + SessionID: state.SessionID, + CheckpointsCount: state.StepCount, + FilesTouched: sessionData.FilesTouched, + Prompts: sessionData.Prompts, + TotalTranscriptLines: sessionData.FullTranscriptLines, + CompactTranscriptLines: compactLines, + Transcript: sessionData.Transcript, }, nil } @@ -928,6 +936,7 @@ func (s *ManualCommitStrategy) CondenseSessionByID(ctx context.Context, sessionI // Update session state: reset step count and transition to idle state.StepCount = 0 state.CheckpointTranscriptStart = result.TotalTranscriptLines + state.CompactTranscriptStart += result.CompactTranscriptLines state.CheckpointTranscriptSize = int64(len(result.Transcript)) state.Phase = session.PhaseIdle state.LastCheckpointID = checkpointID @@ -1036,6 +1045,7 @@ func (s *ManualCommitStrategy) CondenseAndMarkFullyCondensed(ctx context.Context // Update state — keep Phase = ENDED (unlike CondenseSessionByID which sets IDLE) state.StepCount = 0 state.CheckpointTranscriptStart = result.TotalTranscriptLines + state.CompactTranscriptStart += result.CompactTranscriptLines state.LastCheckpointID = checkpointID state.AttributionBaseCommit = state.BaseCommit state.PromptAttributions = nil @@ -1121,6 +1131,12 @@ func compactTranscriptForV2(ctx context.Context, ag agent.Agent, transcript []by return compacted } +// countCompactLines returns the number of lines in a compact transcript. +// Each line is newline-terminated by compact.appendLine, so we count '\n' bytes. +func countCompactLines(compactTranscript []byte) int { + return bytes.Count(compactTranscript, []byte{'\n'}) +} + // writeCommittedV2IfEnabled writes checkpoint data to v2 refs when checkpoints_v2 // is enabled in settings. Failures are logged as warnings — v2 writes are // best-effort during the dual-write period and must not block the v1 path. diff --git a/cmd/entire/cli/strategy/manual_commit_hooks.go b/cmd/entire/cli/strategy/manual_commit_hooks.go index e28bdce10..fbe78f8f3 100644 --- a/cmd/entire/cli/strategy/manual_commit_hooks.go +++ b/cmd/entire/cli/strategy/manual_commit_hooks.go @@ -1289,6 +1289,7 @@ func (s *ManualCommitStrategy) condenseAndUpdateState( state.AttributionBaseCommit = newHead state.StepCount = 0 state.CheckpointTranscriptStart = result.TotalTranscriptLines + state.CompactTranscriptStart += result.CompactTranscriptLines state.CheckpointTranscriptSize = int64(len(result.Transcript)) // Clear attribution tracking — condensation already used these values @@ -2649,6 +2650,7 @@ func (s *ManualCommitStrategy) carryForwardToNewShadowBranch( // but this would complicate checkpoint retrieval and require careful tracking of dependencies. state.StepCount = 1 state.CheckpointTranscriptStart = 0 + state.CompactTranscriptStart = 0 state.CheckpointTranscriptSize = 0 state.LastCheckpointID = "" // NOTE: TurnCheckpointIDs is intentionally NOT cleared here. Those checkpoint diff --git a/cmd/entire/cli/strategy/manual_commit_test.go b/cmd/entire/cli/strategy/manual_commit_test.go index b5e5c8a5c..53a079d3b 100644 --- a/cmd/entire/cli/strategy/manual_commit_test.go +++ b/cmd/entire/cli/strategy/manual_commit_test.go @@ -4028,6 +4028,115 @@ func TestCondenseSession_V2DualWrite(t *testing.T) { require.NoError(t, err, "full.jsonl should exist on /full/current") } +// TestCondenseSession_V2CompactTranscriptStart verifies that the v2 /main metadata +// uses the compact transcript.jsonl line offset (CompactTranscriptStart) for +// checkpoint_transcript_start, not the full.jsonl line offset. +func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { + dir := t.TempDir() + repo, err := git.PlainInit(dir, false) + require.NoError(t, err) + + worktree, err := repo.Worktree() + require.NoError(t, err) + + require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o644)) + _, err = worktree.Add("main.go") + require.NoError(t, err) + commitHash, err := worktree.Commit("Initial commit", &git.CommitOptions{ + Author: &object.Signature{Name: "Test", Email: "test@test.com", When: time.Now()}, + }) + require.NoError(t, err) + + t.Chdir(dir) + + // Enable checkpoints_v2 via settings + entireDir := filepath.Join(dir, ".entire") + require.NoError(t, os.MkdirAll(entireDir, 0o755)) + settingsJSON := `{"enabled": true, "strategy": "manual-commit", "strategy_options": {"checkpoints_v2": true}}` + require.NoError(t, os.WriteFile(filepath.Join(entireDir, "settings.json"), []byte(settingsJSON), 0o644)) + + s := &ManualCommitStrategy{} + sessionID := "2025-01-15-test-v2-compact-start" + + // Create metadata directory with transcript + metadataDir := ".entire/metadata/" + sessionID + metadataDirAbs := filepath.Join(dir, metadataDir) + require.NoError(t, os.MkdirAll(metadataDirAbs, 0o755)) + + transcript := `{"type":"human","message":{"content":"hello"}} +{"type":"assistant","message":{"content":"hi there"}} +` + require.NoError(t, os.WriteFile(filepath.Join(metadataDirAbs, paths.TranscriptFileName), []byte(transcript), 0o644)) + + // SaveStep to create shadow branch + err = s.SaveStep(context.Background(), StepContext{ + SessionID: sessionID, + ModifiedFiles: []string{"main.go"}, + MetadataDir: metadataDir, + MetadataDirAbs: metadataDirAbs, + CommitMessage: "Checkpoint 1", + AuthorName: "Test", + AuthorEmail: "test@test.com", + }) + require.NoError(t, err) + + state, err := s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + state.TranscriptPath = filepath.Join(metadataDirAbs, paths.TranscriptFileName) + state.BaseCommit = commitHash.String()[:7] + state.AgentType = agent.AgentTypeClaudeCode + + // First condensation — CompactTranscriptStart should be 0 + checkpointID := id.MustCheckpointID("cc11dd22ee33") + result, err := s.CondenseSession(context.Background(), repo, checkpointID, state, nil) + require.NoError(t, err) + require.NotNil(t, result) + + // Verify v2 /main metadata has checkpoint_transcript_start = 0 (first checkpoint) + v2MainRef, err := repo.Reference(plumbing.ReferenceName(paths.V2MainRefName), true) + require.NoError(t, err) + v2MainCommit, err := repo.CommitObject(v2MainRef.Hash()) + require.NoError(t, err) + v2MainTree, err := v2MainCommit.Tree() + require.NoError(t, err) + + cpPath := checkpointID.Path() + sessionTree, err := v2MainTree.Tree(cpPath + "/0") + require.NoError(t, err) + metadataFile, err := sessionTree.File(paths.MetadataFileName) + require.NoError(t, err) + metadataContent, err := metadataFile.Contents() + require.NoError(t, err) + + var v2Metadata checkpoint.CommittedMetadata + require.NoError(t, json.Unmarshal([]byte(metadataContent), &v2Metadata)) + require.Equal(t, 0, v2Metadata.CheckpointTranscriptStart, + "first checkpoint v2 metadata should have checkpoint_transcript_start=0") + + // Read the v1 metadata for comparison + v1Ref, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true) + require.NoError(t, err) + v1Commit, err := repo.CommitObject(v1Ref.Hash()) + require.NoError(t, err) + v1Tree, err := v1Commit.Tree() + require.NoError(t, err) + v1SessionTree, err := v1Tree.Tree(cpPath + "/0") + require.NoError(t, err) + v1MetadataFile, err := v1SessionTree.File(paths.MetadataFileName) + require.NoError(t, err) + v1MetadataContent, err := v1MetadataFile.Contents() + require.NoError(t, err) + + var v1Metadata checkpoint.CommittedMetadata + require.NoError(t, json.Unmarshal([]byte(v1MetadataContent), &v1Metadata)) + require.Equal(t, 0, v1Metadata.CheckpointTranscriptStart, + "first checkpoint v1 metadata should also have checkpoint_transcript_start=0") + + // Verify compact transcript lines were counted in the result + require.Positive(t, result.CompactTranscriptLines, + "CondenseResult should report compact transcript lines") +} + // TestCondenseSession_V2Disabled_NoV2Refs verifies that when checkpoints_v2 is // not enabled, CondenseSession only writes to v1 and does not create v2 refs. func TestCondenseSession_V2Disabled_NoV2Refs(t *testing.T) { diff --git a/cmd/entire/cli/strategy/manual_commit_types.go b/cmd/entire/cli/strategy/manual_commit_types.go index 65d490157..96d92e36f 100644 --- a/cmd/entire/cli/strategy/manual_commit_types.go +++ b/cmd/entire/cli/strategy/manual_commit_types.go @@ -48,13 +48,14 @@ type CheckpointInfo struct { // CondenseResult contains the result of a session condensation operation. type CondenseResult struct { - CheckpointID id.CheckpointID // 12-hex-char from Entire-Checkpoint trailer, used as directory path - SessionID string - CheckpointsCount int - FilesTouched []string - Prompts []string // User prompts from the condensed session - TotalTranscriptLines int // Total lines in transcript after this condensation - Transcript []byte // Raw transcript bytes for downstream consumers (trail title generation) + CheckpointID id.CheckpointID // 12-hex-char from Entire-Checkpoint trailer, used as directory path + SessionID string + CheckpointsCount int + FilesTouched []string + Prompts []string // User prompts from the condensed session + TotalTranscriptLines int // Total lines in full.jsonl after this condensation + CompactTranscriptLines int // Lines in the compact transcript written for this checkpoint (0 if v2 disabled) + Transcript []byte // Raw transcript bytes for downstream consumers (trail title generation) } // ExtractedSessionData contains data extracted from a shadow branch. From d4e6abe96e2378fbd8368f7ad801b2c45d70aeef Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:21:56 -0700 Subject: [PATCH 02/11] Backfill compact transcript start for legacy session state. Initialize compact transcript offsets from existing checkpoint offsets during state normalization and add tests to preserve migration behavior. Made-with: Cursor Entire-Checkpoint: 4678bd55995f --- cmd/entire/cli/checkpoint/checkpoint.go | 7 +-- cmd/entire/cli/checkpoint/v2_store_test.go | 6 +- cmd/entire/cli/session/state.go | 15 +++-- cmd/entire/cli/session/state_test.go | 59 ++++++++++++++----- .../strategy/manual_commit_condensation.go | 3 +- cmd/entire/cli/strategy/manual_commit_test.go | 11 ++-- 6 files changed, 66 insertions(+), 35 deletions(-) diff --git a/cmd/entire/cli/checkpoint/checkpoint.go b/cmd/entire/cli/checkpoint/checkpoint.go index 476a6df67..270a66af0 100644 --- a/cmd/entire/cli/checkpoint/checkpoint.go +++ b/cmd/entire/cli/checkpoint/checkpoint.go @@ -273,10 +273,9 @@ type WriteCommittedOptions struct { // CheckpointTranscriptStart is written to both CommittedMetadata.CheckpointTranscriptStart // and the deprecated CommittedMetadata.TranscriptLinesAtStart for backward compatibility. - // CompactTranscriptStart is the transcript.jsonl line offset at start of this - // checkpoint's data. Used by v2 /main where checkpoint_transcript_start in metadata - // should correspond to the compact transcript (transcript.jsonl) rather than the - // raw transcript (full.jsonl). V1 continues using CheckpointTranscriptStart. + // CompactTranscriptStart is the transcript.jsonl line offset at checkpoint start. + // V2 /main writes this to checkpoint_transcript_start; v1 continues to use + // CheckpointTranscriptStart (full.jsonl). CompactTranscriptStart int // TokenUsage contains the token usage for this checkpoint diff --git a/cmd/entire/cli/checkpoint/v2_store_test.go b/cmd/entire/cli/checkpoint/v2_store_test.go index 47cb318bf..41971a8d7 100644 --- a/cmd/entire/cli/checkpoint/v2_store_test.go +++ b/cmd/entire/cli/checkpoint/v2_store_test.go @@ -397,8 +397,8 @@ func TestV2GitStore_WriteCommittedMain_UsesCompactTranscriptStart(t *testing.T) Prompts: []string{"hello"}, AuthorName: "Test", AuthorEmail: "test@test.com", - CheckpointTranscriptStart: 42, // full.jsonl offset (should NOT appear in v2 metadata) - CompactTranscriptStart: 15, // transcript.jsonl offset (should appear in v2 metadata) + CheckpointTranscriptStart: 42, // full.jsonl offset (must not be used in v2 metadata) + CompactTranscriptStart: 15, // transcript.jsonl offset (must be used in v2 metadata) }) require.NoError(t, err) @@ -410,7 +410,7 @@ func TestV2GitStore_WriteCommittedMain_UsesCompactTranscriptStart(t *testing.T) var metadata CommittedMetadata require.NoError(t, json.Unmarshal([]byte(metadataContent), &metadata)) - // checkpoint_transcript_start should be the compact offset (15), not the full.jsonl offset (42) + // v2 should store the compact offset, not the full transcript offset. assert.Equal(t, 15, metadata.CheckpointTranscriptStart, "v2 /main metadata should use CompactTranscriptStart for checkpoint_transcript_start") } diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index 4dd85186d..a83c493f3 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -109,12 +109,9 @@ type State struct { // against this value without reading the full transcript content. CheckpointTranscriptSize int64 `json:"checkpoint_transcript_size,omitempty"` - // CompactTranscriptStart is the transcript.jsonl (compact format) line offset - // where the current checkpoint cycle began. Parallel to CheckpointTranscriptStart - // (which tracks full.jsonl lines). Used for v2 checkpoint metadata where - // checkpoint_transcript_start should correspond to transcript.jsonl, not full.jsonl. - // Set to 0 at session start, updated to cumulative compact line count after each - // condensation. + // CompactTranscriptStart is the transcript.jsonl line offset where the current + // checkpoint cycle began. It parallels CheckpointTranscriptStart (full.jsonl) + // and is updated after each condensation. CompactTranscriptStart int `json:"compact_transcript_start,omitempty"` // Deprecated: CondensedTranscriptLines is replaced by CheckpointTranscriptStart. @@ -242,6 +239,12 @@ func (s *State) NormalizeAfterLoad(ctx context.Context) { s.CheckpointTranscriptStart = s.TranscriptLinesAtStart } } + // Backfill CompactTranscriptStart for legacy sessions so v2 incremental scoping + // does not regress to zero on first post-upgrade condensation. + // Legacy state has no compact-specific offset, so we reuse checkpoint start. + if s.CompactTranscriptStart == 0 && s.CheckpointTranscriptStart > 0 { + s.CompactTranscriptStart = s.CheckpointTranscriptStart + } // Clear deprecated fields so they aren't re-persisted. // Note: this is a one-way migration. If the state is re-saved, older CLI versions // will see 0 for these fields and fall back to scoping from the transcript start. diff --git a/cmd/entire/cli/session/state_test.go b/cmd/entire/cli/session/state_test.go index b4742c96b..7fee6e7cf 100644 --- a/cmd/entire/cli/session/state_test.go +++ b/cmd/entire/cli/session/state_test.go @@ -77,36 +77,66 @@ func TestState_NormalizeAfterLoad(t *testing.T) { assert.Equal(t, 200, state.CheckpointTranscriptStart) assert.Equal(t, 0, state.TranscriptLinesAtStart) }) + + t.Run("backfills_CompactTranscriptStart_from_CheckpointTranscriptStart", func(t *testing.T) { + t.Parallel() + state := &State{ + CheckpointTranscriptStart: 120, + } + state.NormalizeAfterLoad(context.Background()) + assert.Equal(t, 120, state.CompactTranscriptStart) + }) + + t.Run("preserves_existing_CompactTranscriptStart", func(t *testing.T) { + t.Parallel() + state := &State{ + CheckpointTranscriptStart: 120, + CompactTranscriptStart: 45, + } + state.NormalizeAfterLoad(context.Background()) + assert.Equal(t, 45, state.CompactTranscriptStart) + }) } func TestState_NormalizeAfterLoad_JSONRoundTrip(t *testing.T) { tests := []struct { - name string - json string - wantCTS int // CheckpointTranscriptStart - wantStep int // StepCount + name string + json string + wantCTS int // CheckpointTranscriptStart + wantCompact int // CompactTranscriptStart + wantStep int // StepCount }{ { name: "migrates old condensed_transcript_lines", json: `{"session_id":"s1","condensed_transcript_lines":42,"checkpoint_count":5}`, wantCTS: 42, + wantCompact: 42, wantStep: 5, }, { - name: "migrates old transcript_lines_at_start", - json: `{"session_id":"s1","transcript_lines_at_start":75}`, - wantCTS: 75, + name: "migrates old transcript_lines_at_start", + json: `{"session_id":"s1","transcript_lines_at_start":75}`, + wantCTS: 75, + wantCompact: 75, + }, + { + name: "preserves new field over old", + json: `{"session_id":"s1","condensed_transcript_lines":10,"checkpoint_transcript_start":50}`, + wantCTS: 50, + wantCompact: 50, }, { - name: "preserves new field over old", - json: `{"session_id":"s1","condensed_transcript_lines":10,"checkpoint_transcript_start":50}`, - wantCTS: 50, + name: "handles clean new format", + json: `{"session_id":"s1","checkpoint_transcript_start":25,"checkpoint_count":3}`, + wantCTS: 25, + wantCompact: 25, + wantStep: 3, }, { - name: "handles clean new format", - json: `{"session_id":"s1","checkpoint_transcript_start":25,"checkpoint_count":3}`, - wantCTS: 25, - wantStep: 3, + name: "preserves explicit compact_transcript_start", + json: `{"session_id":"s1","checkpoint_transcript_start":25,"compact_transcript_start":9}`, + wantCTS: 25, + wantCompact: 9, }, } @@ -117,6 +147,7 @@ func TestState_NormalizeAfterLoad_JSONRoundTrip(t *testing.T) { state.NormalizeAfterLoad(context.Background()) assert.Equal(t, tt.wantCTS, state.CheckpointTranscriptStart) + assert.Equal(t, tt.wantCompact, state.CompactTranscriptStart) assert.Equal(t, tt.wantStep, state.StepCount) assert.Equal(t, 0, state.CondensedTranscriptLines, "deprecated field should be cleared") assert.Equal(t, 0, state.TranscriptLinesAtStart, "deprecated field should be cleared") diff --git a/cmd/entire/cli/strategy/manual_commit_condensation.go b/cmd/entire/cli/strategy/manual_commit_condensation.go index 1b0f9ed75..872a69246 100644 --- a/cmd/entire/cli/strategy/manual_commit_condensation.go +++ b/cmd/entire/cli/strategy/manual_commit_condensation.go @@ -1131,8 +1131,7 @@ func compactTranscriptForV2(ctx context.Context, ag agent.Agent, transcript []by return compacted } -// countCompactLines returns the number of lines in a compact transcript. -// Each line is newline-terminated by compact.appendLine, so we count '\n' bytes. +// countCompactLines returns line count for compact transcript JSONL. func countCompactLines(compactTranscript []byte) int { return bytes.Count(compactTranscript, []byte{'\n'}) } diff --git a/cmd/entire/cli/strategy/manual_commit_test.go b/cmd/entire/cli/strategy/manual_commit_test.go index 53a079d3b..24443dff1 100644 --- a/cmd/entire/cli/strategy/manual_commit_test.go +++ b/cmd/entire/cli/strategy/manual_commit_test.go @@ -4028,9 +4028,8 @@ func TestCondenseSession_V2DualWrite(t *testing.T) { require.NoError(t, err, "full.jsonl should exist on /full/current") } -// TestCondenseSession_V2CompactTranscriptStart verifies that the v2 /main metadata -// uses the compact transcript.jsonl line offset (CompactTranscriptStart) for -// checkpoint_transcript_start, not the full.jsonl line offset. +// TestCondenseSession_V2CompactTranscriptStart verifies v2 /main writes +// checkpoint_transcript_start from compact transcript offset, not full.jsonl offset. func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { dir := t.TempDir() repo, err := git.PlainInit(dir, false) @@ -4086,13 +4085,13 @@ func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { state.BaseCommit = commitHash.String()[:7] state.AgentType = agent.AgentTypeClaudeCode - // First condensation — CompactTranscriptStart should be 0 + // First condensation starts at compact offset 0. checkpointID := id.MustCheckpointID("cc11dd22ee33") result, err := s.CondenseSession(context.Background(), repo, checkpointID, state, nil) require.NoError(t, err) require.NotNil(t, result) - // Verify v2 /main metadata has checkpoint_transcript_start = 0 (first checkpoint) + // v2 /main should have checkpoint_transcript_start = 0 for first checkpoint. v2MainRef, err := repo.Reference(plumbing.ReferenceName(paths.V2MainRefName), true) require.NoError(t, err) v2MainCommit, err := repo.CommitObject(v2MainRef.Hash()) @@ -4113,7 +4112,7 @@ func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { require.Equal(t, 0, v2Metadata.CheckpointTranscriptStart, "first checkpoint v2 metadata should have checkpoint_transcript_start=0") - // Read the v1 metadata for comparison + // Read v1 metadata for comparison. v1Ref, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true) require.NoError(t, err) v1Commit, err := repo.CommitObject(v1Ref.Hash()) From 6702fafd3d63430f3ee43f7f072519bb80447d59 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:28:17 -0700 Subject: [PATCH 03/11] Ensure migrate also supports calculating start lines Entire-Checkpoint: 15712c7a9051 --- cmd/entire/cli/migrate.go | 29 ++++++++++++++++++++++++++++ cmd/entire/cli/session/state_test.go | 8 ++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/cmd/entire/cli/migrate.go b/cmd/entire/cli/migrate.go index 38e84ecac..62518fb8e 100644 --- a/cmd/entire/cli/migrate.go +++ b/cmd/entire/cli/migrate.go @@ -1,6 +1,7 @@ package cli import ( + "bytes" "context" "errors" "fmt" @@ -203,6 +204,7 @@ func migrateOneCheckpoint(ctx context.Context, repo *git.Repository, v1Store *ch compacted := tryCompactTranscript(ctx, content.Transcript, content.Metadata) if compacted != nil { opts.CompactTranscript = compacted + opts.CompactTranscriptStart = computeCompactOffset(content.Transcript, compacted, content.Metadata) } else if len(content.Transcript) > 0 { compactFailed = true } @@ -446,6 +448,33 @@ func tryCompactTranscript(ctx context.Context, transcript []byte, m checkpoint.C return compacted } +// computeCompactOffset determines the transcript.jsonl line offset for a checkpoint +// by comparing a full compact (startLine=0) against the scoped compact. The difference +// is the number of compact lines before this checkpoint's data. +func computeCompactOffset(fullTranscript, scopedCompact []byte, m checkpoint.CommittedMetadata) int { + startLine := m.GetTranscriptStart() + if startLine == 0 || len(fullTranscript) == 0 || m.Agent == "" { + return 0 + } + + fullCompacted, err := compact.Compact(fullTranscript, compact.MetadataFields{ + Agent: string(m.Agent), + CLIVersion: versioninfo.Version, + StartLine: 0, + }) + if err != nil || len(fullCompacted) == 0 { + return 0 + } + + fullLines := bytes.Count(fullCompacted, []byte{'\n'}) + scopedLines := bytes.Count(scopedCompact, []byte{'\n'}) + offset := fullLines - scopedLines + if offset < 0 { + return 0 + } + return offset +} + // copyTaskMetadataToV2 copies task metadata files (subagent transcripts, checkpoint JSONs) // from the v1 branch to the v2 /full/current ref via tree surgery. func copyTaskMetadataToV2(repo *git.Repository, _ *checkpoint.GitStore, v2Store *checkpoint.V2GitStore, cpID id.CheckpointID, summary *checkpoint.CheckpointSummary) error { diff --git a/cmd/entire/cli/session/state_test.go b/cmd/entire/cli/session/state_test.go index 7fee6e7cf..e96e31c83 100644 --- a/cmd/entire/cli/session/state_test.go +++ b/cmd/entire/cli/session/state_test.go @@ -107,11 +107,11 @@ func TestState_NormalizeAfterLoad_JSONRoundTrip(t *testing.T) { wantStep int // StepCount }{ { - name: "migrates old condensed_transcript_lines", - json: `{"session_id":"s1","condensed_transcript_lines":42,"checkpoint_count":5}`, - wantCTS: 42, + name: "migrates old condensed_transcript_lines", + json: `{"session_id":"s1","condensed_transcript_lines":42,"checkpoint_count":5}`, + wantCTS: 42, wantCompact: 42, - wantStep: 5, + wantStep: 5, }, { name: "migrates old transcript_lines_at_start", From 603a9c2b40c8adf1d8122b22874946ca87a128d0 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:00:39 -0700 Subject: [PATCH 04/11] Address review feedback Entire-Checkpoint: 92926498c799 --- cmd/entire/cli/migrate_test.go | 57 +++++++++++++++++++ cmd/entire/cli/session/state.go | 11 ++-- cmd/entire/cli/session/state_test.go | 12 ++-- .../strategy/manual_commit_condensation.go | 37 +++++++++++- cmd/entire/cli/strategy/manual_commit_test.go | 20 +++---- 5 files changed, 112 insertions(+), 25 deletions(-) diff --git a/cmd/entire/cli/migrate_test.go b/cmd/entire/cli/migrate_test.go index 6e23eef68..68fae4b14 100644 --- a/cmd/entire/cli/migrate_test.go +++ b/cmd/entire/cli/migrate_test.go @@ -3,10 +3,12 @@ package cli import ( "bytes" "context" + "encoding/json" "strconv" "strings" "testing" + "github.com/entireio/cli/cmd/entire/cli/agent" "github.com/entireio/cli/cmd/entire/cli/checkpoint" "github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/cmd/entire/cli/paths" @@ -326,6 +328,61 @@ func TestMigrateCheckpointsV2_BackfillCompactTranscript(t *testing.T) { assert.NotEmpty(t, summary2.Sessions[0].Transcript, "should have compact transcript after backfill") } +func TestMigrateCheckpointsV2_UsesComputedCompactTranscriptStart(t *testing.T) { + t.Parallel() + repo := initMigrateTestRepo(t) + v1Store, v2Store := newMigrateStores(repo) + ctx := context.Background() + + cpID := id.MustCheckpointID("5566778899aa") + transcript := []byte( + "{\"type\":\"human\",\"message\":{\"content\":\"prompt 1\"}}\n" + + "{\"type\":\"assistant\",\"message\":{\"content\":\"reply 1\"}}\n" + + "{\"type\":\"human\",\"message\":{\"content\":\"prompt 2\"}}\n" + + "{\"type\":\"assistant\",\"message\":{\"content\":\"reply 2\"}}\n", + ) + err := v1Store.WriteCommitted(ctx, checkpoint.WriteCommittedOptions{ + CheckpointID: cpID, + SessionID: "session-compact-start-migrate", + Strategy: "manual-commit", + Transcript: transcript, + Prompts: []string{"prompt 2"}, + Agent: agent.AgentTypeClaudeCode, + CheckpointTranscriptStart: 2, // full transcript line domain + AuthorName: "Test", + AuthorEmail: "test@test.com", + }) + require.NoError(t, err) + + v1Content, err := v1Store.ReadSessionContent(ctx, cpID, 0) + require.NoError(t, err) + compacted := tryCompactTranscript(ctx, v1Content.Transcript, v1Content.Metadata) + require.NotNil(t, compacted) + expectedOffset := computeCompactOffset(v1Content.Transcript, compacted, v1Content.Metadata) + require.Positive(t, expectedOffset, "expected non-zero compact transcript start") + + var stdout bytes.Buffer + result, migrateErr := migrateCheckpointsV2(ctx, repo, v1Store, v2Store, &stdout) + require.NoError(t, migrateErr) + assert.Equal(t, 1, result.migrated) + + v2MainRef, err := repo.Reference(plumbing.ReferenceName(paths.V2MainRefName), true) + require.NoError(t, err) + v2MainCommit, err := repo.CommitObject(v2MainRef.Hash()) + require.NoError(t, err) + v2MainTree, err := v2MainCommit.Tree() + require.NoError(t, err) + + metadataFile, err := v2MainTree.File(cpID.Path() + "/0/" + paths.MetadataFileName) + require.NoError(t, err) + metadataContent, err := metadataFile.Contents() + require.NoError(t, err) + + var metadata checkpoint.CommittedMetadata + require.NoError(t, json.Unmarshal([]byte(metadataContent), &metadata)) + assert.Equal(t, expectedOffset, metadata.CheckpointTranscriptStart) +} + func TestMigrateCheckpointsV2_RepairsMissingFullTranscriptBeforeBackfill(t *testing.T) { t.Parallel() repo := initMigrateTestRepo(t) diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index a83c493f3..554c64f61 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -239,12 +239,11 @@ func (s *State) NormalizeAfterLoad(ctx context.Context) { s.CheckpointTranscriptStart = s.TranscriptLinesAtStart } } - // Backfill CompactTranscriptStart for legacy sessions so v2 incremental scoping - // does not regress to zero on first post-upgrade condensation. - // Legacy state has no compact-specific offset, so we reuse checkpoint start. - if s.CompactTranscriptStart == 0 && s.CheckpointTranscriptStart > 0 { - s.CompactTranscriptStart = s.CheckpointTranscriptStart - } + // Do not backfill CompactTranscriptStart from CheckpointTranscriptStart. + // They are different domains: CheckpointTranscriptStart is full.jsonl lines, + // while CompactTranscriptStart is compact transcript.jsonl lines (which may + // merge/drop events). Leaving legacy values at zero fails open and avoids + // skipping compact transcript content due to inflated offsets. // Clear deprecated fields so they aren't re-persisted. // Note: this is a one-way migration. If the state is re-saved, older CLI versions // will see 0 for these fields and fall back to scoping from the transcript start. diff --git a/cmd/entire/cli/session/state_test.go b/cmd/entire/cli/session/state_test.go index e96e31c83..dac4d5b65 100644 --- a/cmd/entire/cli/session/state_test.go +++ b/cmd/entire/cli/session/state_test.go @@ -78,13 +78,13 @@ func TestState_NormalizeAfterLoad(t *testing.T) { assert.Equal(t, 0, state.TranscriptLinesAtStart) }) - t.Run("backfills_CompactTranscriptStart_from_CheckpointTranscriptStart", func(t *testing.T) { + t.Run("leaves_CompactTranscriptStart_zero_when_missing", func(t *testing.T) { t.Parallel() state := &State{ CheckpointTranscriptStart: 120, } state.NormalizeAfterLoad(context.Background()) - assert.Equal(t, 120, state.CompactTranscriptStart) + assert.Equal(t, 0, state.CompactTranscriptStart) }) t.Run("preserves_existing_CompactTranscriptStart", func(t *testing.T) { @@ -110,26 +110,26 @@ func TestState_NormalizeAfterLoad_JSONRoundTrip(t *testing.T) { name: "migrates old condensed_transcript_lines", json: `{"session_id":"s1","condensed_transcript_lines":42,"checkpoint_count":5}`, wantCTS: 42, - wantCompact: 42, + wantCompact: 0, wantStep: 5, }, { name: "migrates old transcript_lines_at_start", json: `{"session_id":"s1","transcript_lines_at_start":75}`, wantCTS: 75, - wantCompact: 75, + wantCompact: 0, }, { name: "preserves new field over old", json: `{"session_id":"s1","condensed_transcript_lines":10,"checkpoint_transcript_start":50}`, wantCTS: 50, - wantCompact: 50, + wantCompact: 0, }, { name: "handles clean new format", json: `{"session_id":"s1","checkpoint_transcript_start":25,"checkpoint_count":3}`, wantCTS: 25, - wantCompact: 25, + wantCompact: 0, wantStep: 3, }, { diff --git a/cmd/entire/cli/strategy/manual_commit_condensation.go b/cmd/entire/cli/strategy/manual_commit_condensation.go index 872a69246..5a0e2563f 100644 --- a/cmd/entire/cli/strategy/manual_commit_condensation.go +++ b/cmd/entire/cli/strategy/manual_commit_condensation.go @@ -250,7 +250,7 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re redactedForCompact = nil } writeOpts.CompactTranscript = compactTranscriptForV2(ctx, ag, redactedForCompact, state.CheckpointTranscriptStart) - writeOpts.CompactTranscriptStart = state.CompactTranscriptStart + writeOpts.CompactTranscriptStart = computeCompactTranscriptStart(ctx, ag, state, redactedForCompact, writeOpts.CompactTranscript) } // Write checkpoint metadata to v1 branch @@ -1136,6 +1136,41 @@ func countCompactLines(compactTranscript []byte) int { return bytes.Count(compactTranscript, []byte{'\n'}) } +// computeCompactTranscriptStart chooses the compact transcript start line offset +// for v2 /main metadata. +// +// Preferred source is session state CompactTranscriptStart. For legacy sessions +// that have only full-transcript offsets persisted, this recalculates the compact +// offset from transcript bytes when possible. On any failure, returns 0 (fail-open). +func computeCompactTranscriptStart(ctx context.Context, ag agent.Agent, state *SessionState, transcript []byte, scopedCompact []byte) int { + if state.CompactTranscriptStart > 0 { + return state.CompactTranscriptStart + } + if state.CheckpointTranscriptStart == 0 || ag == nil || len(transcript) == 0 || len(scopedCompact) == 0 { + return 0 + } + + fullCompacted, err := compact.Compact(transcript, compact.MetadataFields{ + Agent: string(ag.Name()), + CLIVersion: versioninfo.Version, + StartLine: 0, + }) + if err != nil || len(fullCompacted) == 0 { + logging.Warn(ctx, "failed to recalculate compact transcript start, using 0", + slog.String("session_id", state.SessionID), + ) + return 0 + } + + fullLines := countCompactLines(fullCompacted) + scopedLines := countCompactLines(scopedCompact) + offset := fullLines - scopedLines + if offset < 0 { + return 0 + } + return offset +} + // writeCommittedV2IfEnabled writes checkpoint data to v2 refs when checkpoints_v2 // is enabled in settings. Failures are logged as warnings — v2 writes are // best-effort during the dual-write period and must not block the v1 path. diff --git a/cmd/entire/cli/strategy/manual_commit_test.go b/cmd/entire/cli/strategy/manual_commit_test.go index 24443dff1..3bf318dbb 100644 --- a/cmd/entire/cli/strategy/manual_commit_test.go +++ b/cmd/entire/cli/strategy/manual_commit_test.go @@ -15,6 +15,7 @@ import ( "github.com/entireio/cli/cmd/entire/cli/checkpoint" "github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/cmd/entire/cli/paths" + "github.com/entireio/cli/cmd/entire/cli/testutil" "github.com/entireio/cli/cmd/entire/cli/trailers" "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" @@ -4032,19 +4033,14 @@ func TestCondenseSession_V2DualWrite(t *testing.T) { // checkpoint_transcript_start from compact transcript offset, not full.jsonl offset. func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { dir := t.TempDir() - repo, err := git.PlainInit(dir, false) - require.NoError(t, err) + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, "main.go", "package main") + testutil.GitAdd(t, dir, "main.go") + testutil.GitCommit(t, dir, "Initial commit") - worktree, err := repo.Worktree() - require.NoError(t, err) - - require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o644)) - _, err = worktree.Add("main.go") - require.NoError(t, err) - commitHash, err := worktree.Commit("Initial commit", &git.CommitOptions{ - Author: &object.Signature{Name: "Test", Email: "test@test.com", When: time.Now()}, - }) + repo, err := git.PlainOpen(dir) require.NoError(t, err) + commitHash := testutil.GetHeadHash(t, dir) t.Chdir(dir) @@ -4082,7 +4078,7 @@ func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { state, err := s.loadSessionState(context.Background(), sessionID) require.NoError(t, err) state.TranscriptPath = filepath.Join(metadataDirAbs, paths.TranscriptFileName) - state.BaseCommit = commitHash.String()[:7] + state.BaseCommit = commitHash[:7] state.AgentType = agent.AgentTypeClaudeCode // First condensation starts at compact offset 0. From 3b6d62c21bcfc1af5d8817907e79b495dba42642 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:28:59 -0700 Subject: [PATCH 05/11] Use testutil for add'l test Entire-Checkpoint: 3e7abfc2d4a5 --- cmd/entire/cli/checkpoint/v2_store_test.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/cmd/entire/cli/checkpoint/v2_store_test.go b/cmd/entire/cli/checkpoint/v2_store_test.go index 41971a8d7..4dc58197a 100644 --- a/cmd/entire/cli/checkpoint/v2_store_test.go +++ b/cmd/entire/cli/checkpoint/v2_store_test.go @@ -4,14 +4,13 @@ import ( "context" "encoding/json" "fmt" - "os" - "path/filepath" "strings" "testing" "github.com/entireio/cli/cmd/entire/cli/agent" "github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/cmd/entire/cli/paths" + "github.com/entireio/cli/cmd/entire/cli/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,18 +24,12 @@ func initTestRepo(t *testing.T) *git.Repository { t.Helper() dir := t.TempDir() - repo, err := git.PlainInit(dir, false) - require.NoError(t, err) - - wt, err := repo.Worktree() - require.NoError(t, err) + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, "README.md", "init") + testutil.GitAdd(t, dir, "README.md") + testutil.GitCommit(t, dir, "initial") - require.NoError(t, os.WriteFile(filepath.Join(dir, "README.md"), []byte("init"), 0o644)) - _, err = wt.Add("README.md") - require.NoError(t, err) - _, err = wt.Commit("initial", &git.CommitOptions{ - Author: &object.Signature{Name: "Test", Email: "test@test.com"}, - }) + repo, err := git.PlainOpen(dir) require.NoError(t, err) return repo From 9f1fdecd62c50082f8fe66562dd11c2c25fb5f52 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:34:20 -0700 Subject: [PATCH 06/11] Remove extra comments Entire-Checkpoint: 1b4ddd35692a --- cmd/entire/cli/session/state.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index 554c64f61..ec8eb2428 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -239,11 +239,6 @@ func (s *State) NormalizeAfterLoad(ctx context.Context) { s.CheckpointTranscriptStart = s.TranscriptLinesAtStart } } - // Do not backfill CompactTranscriptStart from CheckpointTranscriptStart. - // They are different domains: CheckpointTranscriptStart is full.jsonl lines, - // while CompactTranscriptStart is compact transcript.jsonl lines (which may - // merge/drop events). Leaving legacy values at zero fails open and avoids - // skipping compact transcript content due to inflated offsets. // Clear deprecated fields so they aren't re-persisted. // Note: this is a one-way migration. If the state is re-saved, older CLI versions // will see 0 for these fields and fall back to scoping from the transcript start. From 555ff73b2c1a35b7c8a42fe9d1dd4a40842dfa79 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:34:55 -0700 Subject: [PATCH 07/11] Ensure append for transcript.jsonl is happening Entire-Checkpoint: b82393d3cbc2 --- .../strategy/manual_commit_condensation.go | 14 +++- cmd/entire/cli/strategy/manual_commit_test.go | 81 +++++++++++++++++++ 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/cmd/entire/cli/strategy/manual_commit_condensation.go b/cmd/entire/cli/strategy/manual_commit_condensation.go index 5a0e2563f..bd7452821 100644 --- a/cmd/entire/cli/strategy/manual_commit_condensation.go +++ b/cmd/entire/cli/strategy/manual_commit_condensation.go @@ -249,8 +249,13 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re ) redactedForCompact = nil } - writeOpts.CompactTranscript = compactTranscriptForV2(ctx, ag, redactedForCompact, state.CheckpointTranscriptStart) - writeOpts.CompactTranscriptStart = computeCompactTranscriptStart(ctx, ag, state, redactedForCompact, writeOpts.CompactTranscript) + // Generate scoped compact (only new content) for line counting and offset calculation. + scopedCompact := compactTranscriptForV2(ctx, ag, redactedForCompact, state.CheckpointTranscriptStart) + // Generate full compact (cumulative) for storage — v2 /main replaces + // the session's transcript.jsonl on each write, so we must include all + // prior content, not just the new portion. + writeOpts.CompactTranscript = compactTranscriptForV2(ctx, ag, redactedForCompact, 0) + writeOpts.CompactTranscriptStart = computeCompactTranscriptStart(ctx, ag, state, redactedForCompact, scopedCompact) } // Write checkpoint metadata to v1 branch @@ -260,9 +265,12 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re writeCommittedV2IfEnabled(ctx, repo, writeOpts) + // Count scoped (new-only) compact lines, not full compact lines, + // so state.CompactTranscriptStart accumulates correctly. compactLines := 0 if writeOpts.CompactTranscript != nil { - compactLines = countCompactLines(writeOpts.CompactTranscript) + fullLines := countCompactLines(writeOpts.CompactTranscript) + compactLines = fullLines - writeOpts.CompactTranscriptStart } return &CondenseResult{ diff --git a/cmd/entire/cli/strategy/manual_commit_test.go b/cmd/entire/cli/strategy/manual_commit_test.go index 3bf318dbb..4c2f491d7 100644 --- a/cmd/entire/cli/strategy/manual_commit_test.go +++ b/cmd/entire/cli/strategy/manual_commit_test.go @@ -1,6 +1,7 @@ package strategy import ( + "bytes" "context" "encoding/json" "errors" @@ -4130,6 +4131,86 @@ func TestCondenseSession_V2CompactTranscriptStart(t *testing.T) { // Verify compact transcript lines were counted in the result require.Positive(t, result.CompactTranscriptLines, "CondenseResult should report compact transcript lines") + + // Read compact transcript.jsonl from v2 /main for the first checkpoint. + compactFile1, err := sessionTree.File(paths.CompactTranscriptFileName) + require.NoError(t, err, "transcript.jsonl should exist on v2 /main") + compactContent1, err := compactFile1.Contents() + require.NoError(t, err) + firstCompactLines := bytes.Count([]byte(compactContent1), []byte{'\n'}) + require.Positive(t, firstCompactLines, "first checkpoint compact transcript should have lines") + + // --- Second condensation: add more transcript content --- + transcript2 := transcript + `{"type":"human","message":{"content":"next question"}} +{"type":"assistant","message":{"content":"next answer"}} +` + require.NoError(t, os.WriteFile(filepath.Join(metadataDirAbs, paths.TranscriptFileName), []byte(transcript2), 0o644)) + + // Update state after first condensation (mimic what CondenseSessionByID does) + state.StepCount = 0 + state.CheckpointTranscriptStart = result.TotalTranscriptLines + state.CompactTranscriptStart += result.CompactTranscriptLines + + // SaveStep for second checkpoint + testutil.WriteFile(t, dir, "main.go", "package main\n// v2") + err = s.SaveStep(context.Background(), StepContext{ + SessionID: sessionID, + ModifiedFiles: []string{"main.go"}, + MetadataDir: metadataDir, + MetadataDirAbs: metadataDirAbs, + CommitMessage: "Checkpoint 2", + AuthorName: "Test", + AuthorEmail: "test@test.com", + }) + require.NoError(t, err) + + state2, err := s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + state2.TranscriptPath = filepath.Join(metadataDirAbs, paths.TranscriptFileName) + state2.BaseCommit = commitHash[:7] + state2.AgentType = agent.AgentTypeClaudeCode + state2.CheckpointTranscriptStart = state.CheckpointTranscriptStart + state2.CompactTranscriptStart = state.CompactTranscriptStart + + checkpointID2 := id.MustCheckpointID("dd22ee33ff44") + result2, err := s.CondenseSession(context.Background(), repo, checkpointID2, state2, nil) + require.NoError(t, err) + require.NotNil(t, result2) + + // v2 /main metadata for second checkpoint should have compact start = firstCompactLines. + v2MainRef2, err := repo.Reference(plumbing.ReferenceName(paths.V2MainRefName), true) + require.NoError(t, err) + v2MainCommit2, err := repo.CommitObject(v2MainRef2.Hash()) + require.NoError(t, err) + v2MainTree2, err := v2MainCommit2.Tree() + require.NoError(t, err) + + cpPath2 := checkpointID2.Path() + sessionTree2, err := v2MainTree2.Tree(cpPath2 + "/0") + require.NoError(t, err) + metadataFile2, err := sessionTree2.File(paths.MetadataFileName) + require.NoError(t, err) + metadataContent2, err := metadataFile2.Contents() + require.NoError(t, err) + + var v2Metadata2 checkpoint.CommittedMetadata + require.NoError(t, json.Unmarshal([]byte(metadataContent2), &v2Metadata2)) + require.Equal(t, firstCompactLines, v2Metadata2.CheckpointTranscriptStart, + "second checkpoint v2 metadata should have checkpoint_transcript_start = first checkpoint's compact line count") + + // The compact transcript.jsonl for checkpoint 2 should be CUMULATIVE: + // it should contain both checkpoint 1's and checkpoint 2's compact lines. + compactFile2, err := sessionTree2.File(paths.CompactTranscriptFileName) + require.NoError(t, err, "transcript.jsonl should exist for second checkpoint") + compactContent2, err := compactFile2.Contents() + require.NoError(t, err) + secondCompactTotalLines := bytes.Count([]byte(compactContent2), []byte{'\n'}) + require.Greater(t, secondCompactTotalLines, firstCompactLines, + "second checkpoint compact transcript should include all prior content plus new content") + + // The first checkpoint's content should be a prefix of the second checkpoint's content. + require.True(t, strings.HasPrefix(compactContent2, compactContent1), + "second checkpoint compact transcript should start with first checkpoint's content") } // TestCondenseSession_V2Disabled_NoV2Refs verifies that when checkpoints_v2 is From 6607a7e33ad1c96bf34fda82e59f46d6050939fd Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:37:40 -0700 Subject: [PATCH 08/11] Split up CondenseSession to reduce cyclomatic complexity Entire-Checkpoint: d19b19c878be --- .../strategy/manual_commit_condensation.go | 143 ++++++++++-------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/cmd/entire/cli/strategy/manual_commit_condensation.go b/cmd/entire/cli/strategy/manual_commit_condensation.go index e7b8c997b..2af1e6825 100644 --- a/cmd/entire/cli/strategy/manual_commit_condensation.go +++ b/cmd/entire/cli/strategy/manual_commit_condensation.go @@ -119,18 +119,8 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re logCtx := logging.WithComponent(ctx, "checkpoint") condenseStart := time.Now() - // Get shadow branch — use pre-resolved ref if available, otherwise resolve from repo. shadowBranchName := getShadowBranchNameForCommit(state.BaseCommit, state.WorktreeID) - ref := o.shadowRef - var hasShadowBranch bool - if ref != nil { - hasShadowBranch = true - } else { - refName := plumbing.NewBranchReferenceName(shadowBranchName) - var err error - ref, err = repo.Reference(refName, true) - hasShadowBranch = err == nil - } + ref, hasShadowBranch := resolveShadowRef(repo, shadowBranchName, o.shadowRef) // Re-resolve transcript path before any reads — handles agents that relocate // transcripts mid-session (e.g., Cursor CLI flat → nested layout change). @@ -176,28 +166,7 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re state.TokenUsage = backfillUsage } - // For 1:1 checkpoint model: filter files_touched to only include files actually - // committed in this specific commit. This ensures each checkpoint represents - // exactly the files in that commit, not all files mentioned in the transcript. - if len(committedFiles) > 0 { - hadFilesBeforeFiltering := len(sessionData.FilesTouched) > 0 - - if hadFilesBeforeFiltering { - filtered := make([]string, 0, len(sessionData.FilesTouched)) - for _, f := range sessionData.FilesTouched { - if _, ok := committedFiles[f]; ok { - filtered = append(filtered, f) - } - } - sessionData.FilesTouched = filtered - } else { - // Mid-turn commits can happen before SaveStep records FilesTouched. - // In that case, fall back to the actual committed files, excluding - // Entire's own metadata paths, so the checkpoint still reflects the - // files captured by this commit. - sessionData.FilesTouched = committedFilesExcludingMetadata(committedFiles) - } - } + filterFilesTouched(sessionData, committedFiles) // Get checkpoint store store, err := s.getCheckpointStore() @@ -261,38 +230,7 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re Summary: summary, } - compactRedactStart := time.Now() - compactCtx, compactRedactSpan := perf.Start(ctx, "redact_transcript_for_compact") - var redactedForCompact []byte - var compactRedactDuration time.Duration - var compactTranscriptDuration time.Duration - if settings.IsCheckpointsV2Enabled(ctx) { - var compactRedactErr error - redactedForCompact, compactRedactErr = redact.JSONLBytes(sessionData.Transcript) - if compactRedactErr != nil { - compactRedactSpan.RecordError(compactRedactErr) - logging.Warn(ctx, "compact transcript redaction failed, skipping transcript.jsonl on /main", - slog.String("session_id", state.SessionID), - slog.String("error", compactRedactErr.Error()), - ) - redactedForCompact = nil - } - } - compactRedactSpan.End() - compactRedactDuration = time.Since(compactRedactStart) - compactTranscriptStart := time.Now() - compactCtx, compactTranscriptSpan := perf.Start(compactCtx, "compact_transcript_v2") - if settings.IsCheckpointsV2Enabled(ctx) { - // Generate scoped compact (only new content) for line counting and offset calculation. - scopedCompact := compactTranscriptForV2(compactCtx, ag, redactedForCompact, state.CheckpointTranscriptStart) - // Generate full compact (cumulative) for storage — v2 /main replaces - // the session's transcript.jsonl on each write, so we must include all - // prior content, not just the new portion. - writeOpts.CompactTranscript = compactTranscriptForV2(compactCtx, ag, redactedForCompact, 0) - writeOpts.CompactTranscriptStart = computeCompactTranscriptStart(compactCtx, ag, state, redactedForCompact, scopedCompact) - } - compactTranscriptSpan.End() - compactTranscriptDuration = time.Since(compactTranscriptStart) + compactRedactDuration, compactTranscriptDuration := buildCompactTranscript(ctx, ag, sessionData, state, &writeOpts) // Write checkpoint metadata to v1 branch writeV1Start := time.Now() @@ -345,6 +283,81 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re }, nil } +// resolveShadowRef returns the shadow branch reference, preferring a pre-resolved +// ref when available and falling back to a repo lookup. +func resolveShadowRef(repo *git.Repository, branchName string, preResolved *plumbing.Reference) (ref *plumbing.Reference, exists bool) { + if preResolved != nil { + return preResolved, true + } + refName := plumbing.NewBranchReferenceName(branchName) + resolved, err := repo.Reference(refName, true) + if err != nil { + return nil, false + } + return resolved, true +} + +// filterFilesTouched narrows sessionData.FilesTouched to only files present in +// committedFiles. When no prior files were recorded (mid-turn commit), it falls +// back to the committed set minus Entire metadata paths. +func filterFilesTouched(sessionData *ExtractedSessionData, committedFiles map[string]struct{}) { + if len(committedFiles) == 0 { + return + } + if len(sessionData.FilesTouched) > 0 { + filtered := make([]string, 0, len(sessionData.FilesTouched)) + for _, f := range sessionData.FilesTouched { + if _, ok := committedFiles[f]; ok { + filtered = append(filtered, f) + } + } + sessionData.FilesTouched = filtered + } else { + // Mid-turn commits can happen before SaveStep records FilesTouched. + // In that case, fall back to the actual committed files, excluding + // Entire's own metadata paths, so the checkpoint still reflects the + // files captured by this commit. + sessionData.FilesTouched = committedFilesExcludingMetadata(committedFiles) + } +} + +// buildCompactTranscript redacts the transcript and produces compact (v2) forms +// when v2 checkpoints are enabled. Returns per-phase durations for timing logs. +func buildCompactTranscript(ctx context.Context, ag agent.Agent, sessionData *ExtractedSessionData, state *SessionState, writeOpts *cpkg.WriteCommittedOptions) (redactDuration, compactDuration time.Duration) { + redactStart := time.Now() + compactCtx, redactSpan := perf.Start(ctx, "redact_transcript_for_compact") + var redacted []byte + if settings.IsCheckpointsV2Enabled(ctx) { + var err error + redacted, err = redact.JSONLBytes(sessionData.Transcript) + if err != nil { + redactSpan.RecordError(err) + logging.Warn(ctx, "compact transcript redaction failed, skipping transcript.jsonl on /main", + slog.String("session_id", state.SessionID), + slog.String("error", err.Error()), + ) + redacted = nil + } + } + redactSpan.End() + redactDuration = time.Since(redactStart) + + compactStart := time.Now() + compactCtx, compactSpan := perf.Start(compactCtx, "compact_transcript_v2") + if settings.IsCheckpointsV2Enabled(ctx) { + // Generate scoped compact (only new content) for line counting and offset calculation. + scopedCompact := compactTranscriptForV2(compactCtx, ag, redacted, state.CheckpointTranscriptStart) + // Generate full compact (cumulative) for storage — v2 /main replaces + // the session's transcript.jsonl on each write, so we must include all + // prior content, not just the new portion. + writeOpts.CompactTranscript = compactTranscriptForV2(compactCtx, ag, redacted, 0) + writeOpts.CompactTranscriptStart = computeCompactTranscriptStart(compactCtx, ag, state, redacted, scopedCompact) + } + compactSpan.End() + compactDuration = time.Since(compactStart) + return redactDuration, compactDuration +} + // generateSummary produces an LLM-generated summary of the session transcript. // Returns nil if the scoped transcript is empty or generation fails. func generateSummary(ctx context.Context, sessionData *ExtractedSessionData, state *SessionState) *cpkg.Summary { From fe44c436c5fe514dbb1e906f63d93f396df23612 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:15:00 -0700 Subject: [PATCH 09/11] Fix migration path line calculation Entire-Checkpoint: 25bfa690b2f8 --- cmd/entire/cli/migrate.go | 24 ++++++++++++++++++------ cmd/entire/cli/migrate_test.go | 13 ++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cmd/entire/cli/migrate.go b/cmd/entire/cli/migrate.go index 62518fb8e..b35ca9d1b 100644 --- a/cmd/entire/cli/migrate.go +++ b/cmd/entire/cli/migrate.go @@ -414,6 +414,14 @@ func buildMigrateWriteOpts(content *checkpoint.SessionContent, info checkpoint.C } func tryCompactTranscript(ctx context.Context, transcript []byte, m checkpoint.CommittedMetadata) []byte { + return compactTranscriptForStartLine(ctx, transcript, m, 0) +} + +func tryCompactTranscriptScoped(ctx context.Context, transcript []byte, m checkpoint.CommittedMetadata) []byte { + return compactTranscriptForStartLine(ctx, transcript, m, m.GetTranscriptStart()) +} + +func compactTranscriptForStartLine(ctx context.Context, transcript []byte, m checkpoint.CommittedMetadata, startLine int) []byte { if len(transcript) == 0 { return nil } @@ -427,7 +435,7 @@ func tryCompactTranscript(ctx context.Context, transcript []byte, m checkpoint.C compacted, err := compact.Compact(transcript, compact.MetadataFields{ Agent: string(m.Agent), CLIVersion: versioninfo.Version, - StartLine: m.GetTranscriptStart(), + StartLine: startLine, }) if err != nil { logging.Warn(ctx, "compact transcript generation failed during migration", @@ -451,22 +459,26 @@ func tryCompactTranscript(ctx context.Context, transcript []byte, m checkpoint.C // computeCompactOffset determines the transcript.jsonl line offset for a checkpoint // by comparing a full compact (startLine=0) against the scoped compact. The difference // is the number of compact lines before this checkpoint's data. -func computeCompactOffset(fullTranscript, scopedCompact []byte, m checkpoint.CommittedMetadata) int { +func computeCompactOffset(fullTranscript, fullCompact []byte, m checkpoint.CommittedMetadata) int { startLine := m.GetTranscriptStart() if startLine == 0 || len(fullTranscript) == 0 || m.Agent == "" { return 0 } - fullCompacted, err := compact.Compact(fullTranscript, compact.MetadataFields{ + if len(fullCompact) == 0 { + return 0 + } + + scopedCompact, err := compact.Compact(fullTranscript, compact.MetadataFields{ Agent: string(m.Agent), CLIVersion: versioninfo.Version, - StartLine: 0, + StartLine: startLine, }) - if err != nil || len(fullCompacted) == 0 { + if err != nil || len(scopedCompact) == 0 { return 0 } - fullLines := bytes.Count(fullCompacted, []byte{'\n'}) + fullLines := bytes.Count(fullCompact, []byte{'\n'}) scopedLines := bytes.Count(scopedCompact, []byte{'\n'}) offset := fullLines - scopedLines if offset < 0 { diff --git a/cmd/entire/cli/migrate_test.go b/cmd/entire/cli/migrate_test.go index ba7f62c6e..934bea007 100644 --- a/cmd/entire/cli/migrate_test.go +++ b/cmd/entire/cli/migrate_test.go @@ -356,9 +356,12 @@ func TestMigrateCheckpointsV2_UsesComputedCompactTranscriptStart(t *testing.T) { v1Content, err := v1Store.ReadSessionContent(ctx, cpID, 0) require.NoError(t, err) - compacted := tryCompactTranscript(ctx, v1Content.Transcript, v1Content.Metadata) - require.NotNil(t, compacted) - expectedOffset := computeCompactOffset(v1Content.Transcript, compacted, v1Content.Metadata) + fullCompacted := tryCompactTranscript(ctx, v1Content.Transcript, v1Content.Metadata) + require.NotNil(t, fullCompacted) + scopedCompacted := tryCompactTranscriptScoped(ctx, v1Content.Transcript, v1Content.Metadata) + require.NotNil(t, scopedCompacted) + require.Greater(t, bytes.Count(fullCompacted, []byte{'\n'}), bytes.Count(scopedCompacted, []byte{'\n'})) + expectedOffset := computeCompactOffset(v1Content.Transcript, fullCompacted, v1Content.Metadata) require.Positive(t, expectedOffset, "expected non-zero compact transcript start") var stdout bytes.Buffer @@ -381,6 +384,10 @@ func TestMigrateCheckpointsV2_UsesComputedCompactTranscriptStart(t *testing.T) { var metadata checkpoint.CommittedMetadata require.NoError(t, json.Unmarshal([]byte(metadataContent), &metadata)) assert.Equal(t, expectedOffset, metadata.CheckpointTranscriptStart) + + storedCompact, err := v2Store.ReadSessionCompactTranscript(ctx, cpID, 0) + require.NoError(t, err) + assert.Equal(t, fullCompacted, storedCompact, "migration should persist cumulative compact transcript") } func TestMigrateCheckpointsV2_RepairsMissingFullTranscriptBeforeBackfill(t *testing.T) { From b612813521794a5963abbea86598a1f0c5cde7e9 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:40:05 -0700 Subject: [PATCH 10/11] Add more logging, tests, fix comments Entire-Checkpoint: ef44f95b6973 --- cmd/entire/cli/migrate.go | 19 ++++++++++++++++--- cmd/entire/cli/migrate_test.go | 2 +- cmd/entire/cli/strategy/manual_commit_test.go | 1 + .../cli/strategy/manual_commit_types.go | 4 ++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cmd/entire/cli/migrate.go b/cmd/entire/cli/migrate.go index b35ca9d1b..2b339e96b 100644 --- a/cmd/entire/cli/migrate.go +++ b/cmd/entire/cli/migrate.go @@ -204,7 +204,7 @@ func migrateOneCheckpoint(ctx context.Context, repo *git.Repository, v1Store *ch compacted := tryCompactTranscript(ctx, content.Transcript, content.Metadata) if compacted != nil { opts.CompactTranscript = compacted - opts.CompactTranscriptStart = computeCompactOffset(content.Transcript, compacted, content.Metadata) + opts.CompactTranscriptStart = computeCompactOffset(ctx, content.Transcript, compacted, content.Metadata) } else if len(content.Transcript) > 0 { compactFailed = true } @@ -459,7 +459,7 @@ func compactTranscriptForStartLine(ctx context.Context, transcript []byte, m che // computeCompactOffset determines the transcript.jsonl line offset for a checkpoint // by comparing a full compact (startLine=0) against the scoped compact. The difference // is the number of compact lines before this checkpoint's data. -func computeCompactOffset(fullTranscript, fullCompact []byte, m checkpoint.CommittedMetadata) int { +func computeCompactOffset(ctx context.Context, fullTranscript, fullCompact []byte, m checkpoint.CommittedMetadata) int { startLine := m.GetTranscriptStart() if startLine == 0 || len(fullTranscript) == 0 || m.Agent == "" { return 0 @@ -474,7 +474,15 @@ func computeCompactOffset(fullTranscript, fullCompact []byte, m checkpoint.Commi CLIVersion: versioninfo.Version, StartLine: startLine, }) - if err != nil || len(scopedCompact) == 0 { + if err != nil { + logging.Warn(ctx, "compact transcript offset calculation failed during migration", + slog.String("checkpoint_id", string(m.CheckpointID)), + slog.String("agent", string(m.Agent)), + slog.String("error", err.Error()), + ) + return 0 + } + if len(scopedCompact) == 0 { return 0 } @@ -482,6 +490,11 @@ func computeCompactOffset(fullTranscript, fullCompact []byte, m checkpoint.Commi scopedLines := bytes.Count(scopedCompact, []byte{'\n'}) offset := fullLines - scopedLines if offset < 0 { + logging.Warn(ctx, "compact transcript offset was negative during migration, defaulting to 0", + slog.String("checkpoint_id", string(m.CheckpointID)), + slog.Int("full_lines", fullLines), + slog.Int("scoped_lines", scopedLines), + ) return 0 } return offset diff --git a/cmd/entire/cli/migrate_test.go b/cmd/entire/cli/migrate_test.go index 934bea007..034c2451f 100644 --- a/cmd/entire/cli/migrate_test.go +++ b/cmd/entire/cli/migrate_test.go @@ -361,7 +361,7 @@ func TestMigrateCheckpointsV2_UsesComputedCompactTranscriptStart(t *testing.T) { scopedCompacted := tryCompactTranscriptScoped(ctx, v1Content.Transcript, v1Content.Metadata) require.NotNil(t, scopedCompacted) require.Greater(t, bytes.Count(fullCompacted, []byte{'\n'}), bytes.Count(scopedCompacted, []byte{'\n'})) - expectedOffset := computeCompactOffset(v1Content.Transcript, fullCompacted, v1Content.Metadata) + expectedOffset := computeCompactOffset(ctx, v1Content.Transcript, fullCompacted, v1Content.Metadata) require.Positive(t, expectedOffset, "expected non-zero compact transcript start") var stdout bytes.Buffer diff --git a/cmd/entire/cli/strategy/manual_commit_test.go b/cmd/entire/cli/strategy/manual_commit_test.go index 1dedf11cf..aa8020b9d 100644 --- a/cmd/entire/cli/strategy/manual_commit_test.go +++ b/cmd/entire/cli/strategy/manual_commit_test.go @@ -4271,6 +4271,7 @@ func TestCondenseSession_V2Disabled_NoV2Refs(t *testing.T) { result, err := s.CondenseSession(context.Background(), repo, checkpointID, state, nil) require.NoError(t, err) require.NotNil(t, result) + require.Equal(t, 0, result.CompactTranscriptLines, "v2-disabled condensation should not report compact transcript line deltas") // v1 should exist _, err = repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true) diff --git a/cmd/entire/cli/strategy/manual_commit_types.go b/cmd/entire/cli/strategy/manual_commit_types.go index 96d92e36f..a9b459f06 100644 --- a/cmd/entire/cli/strategy/manual_commit_types.go +++ b/cmd/entire/cli/strategy/manual_commit_types.go @@ -53,8 +53,8 @@ type CondenseResult struct { CheckpointsCount int FilesTouched []string Prompts []string // User prompts from the condensed session - TotalTranscriptLines int // Total lines in full.jsonl after this condensation - CompactTranscriptLines int // Lines in the compact transcript written for this checkpoint (0 if v2 disabled) + TotalTranscriptLines int // Total transcript units after this condensation (JSONL line count or message count by agent format) + CompactTranscriptLines int // New compact transcript lines added by this checkpoint (0 if v2 disabled); used to advance CompactTranscriptStart Transcript []byte // Raw transcript bytes for downstream consumers (trail title generation) } From 2ebbed326f49d56e8012e8661e3273f81285a722 Mon Sep 17 00:00:00 2001 From: computermode <2917645+computermode@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:49:21 -0700 Subject: [PATCH 11/11] Move test-only helper --- cmd/entire/cli/migrate.go | 4 ---- cmd/entire/cli/migrate_test.go | 9 ++++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/entire/cli/migrate.go b/cmd/entire/cli/migrate.go index 2b339e96b..5a610560d 100644 --- a/cmd/entire/cli/migrate.go +++ b/cmd/entire/cli/migrate.go @@ -417,10 +417,6 @@ func tryCompactTranscript(ctx context.Context, transcript []byte, m checkpoint.C return compactTranscriptForStartLine(ctx, transcript, m, 0) } -func tryCompactTranscriptScoped(ctx context.Context, transcript []byte, m checkpoint.CommittedMetadata) []byte { - return compactTranscriptForStartLine(ctx, transcript, m, m.GetTranscriptStart()) -} - func compactTranscriptForStartLine(ctx context.Context, transcript []byte, m checkpoint.CommittedMetadata, startLine int) []byte { if len(transcript) == 0 { return nil diff --git a/cmd/entire/cli/migrate_test.go b/cmd/entire/cli/migrate_test.go index 034c2451f..edd16d31a 100644 --- a/cmd/entire/cli/migrate_test.go +++ b/cmd/entire/cli/migrate_test.go @@ -13,6 +13,8 @@ import ( "github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/cmd/entire/cli/paths" "github.com/entireio/cli/cmd/entire/cli/testutil" + "github.com/entireio/cli/cmd/entire/cli/transcript/compact" + "github.com/entireio/cli/cmd/entire/cli/versioninfo" "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/filemode" @@ -358,7 +360,12 @@ func TestMigrateCheckpointsV2_UsesComputedCompactTranscriptStart(t *testing.T) { require.NoError(t, err) fullCompacted := tryCompactTranscript(ctx, v1Content.Transcript, v1Content.Metadata) require.NotNil(t, fullCompacted) - scopedCompacted := tryCompactTranscriptScoped(ctx, v1Content.Transcript, v1Content.Metadata) + scopedCompacted, err := compact.Compact(v1Content.Transcript, compact.MetadataFields{ + Agent: string(v1Content.Metadata.Agent), + CLIVersion: versioninfo.Version, + StartLine: v1Content.Metadata.GetTranscriptStart(), + }) + require.NoError(t, err) require.NotNil(t, scopedCompacted) require.Greater(t, bytes.Count(fullCompacted, []byte{'\n'}), bytes.Count(scopedCompacted, []byte{'\n'})) expectedOffset := computeCompactOffset(ctx, v1Content.Transcript, fullCompacted, v1Content.Metadata)