checkpoint: extract the persistent contract to api/checkpoint · Entire

Home

Log in

checkpoint: extract the persistent contract to api/checkpoint

a74a801·

Soph·3w ago·10 files·+753 added/-662 removed

Move the persistent-checkpoint contract — the persisted document types (Metadata, CheckpointSummary, Summary, Attribution, ...), the option types (WriteOptions/UpdateOptions/PrecomputedTranscriptBlobs), the reader/writer interfaces, the Write request union, the sentinel errors, and the CheckpointVersionBranchV1 const — into a new github.com/entireio/cli/api/checkpoint package, born with the persistent vocabulary.

The contract depends only on leaf packages (agent/types, checkpoint/id, redact, go-git plumbing), so a storage backend can implement it without the CLI's agent/TUI/git machinery. The git implementation (GitStore, Open, the Stores facade, AuthorReader, and the ephemeral shadow-branch surface) stays in cmd/entire/cli/checkpoint, which imports the contract and re-exports every moved symbol via aliases (aliases.go) — all existing call sites compile unchanged.

Notes: - PrecomputedTranscriptBlobs.isUsable is now exported (IsUsable); its one caller is across the new package boundary. - The Write union is sealed to api/checkpoint, so the impl-package unknown-request test was removed (no longer expressible); per-request dispatch coverage remains. - normalizeCheckpointSummary stays in the impl (read-time normalization).

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

Sessions

a808b47c0633View transcript

?\ Review Checkpoint Commit f16b7101Codex·GPT-5.5·1 step

Changes

10

1
2
3
4
5
6
7
8
9
10
11
12
13

// Package checkpoint defines the persistent-checkpoint storage contract: the
// persisted metadata documents, the option types, the reader/writer
// interfaces, and the Write request union.
//
// It is the pluggable surface from issue #1433: a storage backend implements
// these interfaces and operates on these types without depending on the CLI's
// heavy agent runtime, TUI, or git-implementation packages. (It depends only on
// leaf value packages — agent/types, checkpoint/id — redact, and go-git
// plumbing.) The git-backed implementation (GitStore, Open, the facade, and the
// ephemeral shadow-branch surface) lives in cmd/entire/cli/checkpoint, which
// imports this package and re-exports these symbols as aliases so existing CLI
// call sites are unaffected.
package checkpoint

Aapi/checkpoint/doc.go+13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

package checkpoint

import "errors"

// Errors returned by checkpoint operations.
var (
    // ErrCheckpointNotFound is returned when a checkpoint ID doesn't exist.
    ErrCheckpointNotFound = errors.New("checkpoint not found")

// ErrNoTranscript is returned when a checkpoint exists but has no transcript.
    ErrNoTranscript = errors.New("no transcript found for checkpoint")
)

// CheckpointVersionBranchV1 identifies the branch-backed checkpoint metadata format.
const CheckpointVersionBranchV1 = "branch-v1"

Aapi/checkpoint/errors.go+15

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

package checkpoint

import (
    "context"
    "fmt"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)

// CheckpointReader provides read access to checkpoint-level persistent data.
//
//nolint:revive // CheckpointReader stutter is accepted — the name marks the checkpoint (vs session) read tier.
type CheckpointReader interface {
    Read(ctx context.Context, checkpointID id.CheckpointID) (*CheckpointSummary, error)
    List(ctx context.Context) ([]CheckpointInfo, error)
}

// SessionReader provides read access to session-level data within a checkpoint.
type SessionReader interface {
    ReadSessionContent(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*SessionContent, error)
    ReadSessionMetadata(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*Metadata, error)
    ReadSessionPrompts(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (string, error)
    ReadSessionMetadataAndPrompts(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*SessionContent, error)
}

// PersistentStore provides the production persistent checkpoint storage surface:
// checkpoint-level reads, session-level reads, and the unified Write. Writes go
// through Writer.Write(ctx, WriteRequest); the concrete per-operation methods
// live on the git implementation as the methods Write dispatches to.
type PersistentStore interface {
    CheckpointReader
    SessionReader
    Writer
}

// WriteRequest is a single persistent-store write command. The set is closed to
// other packages: only types in this package can implement it, sealed via the
// unexported isWriteRequest marker. A store dispatches on the concrete type; a
// mirror/fan-out store forwards the same value to each backend's Write.
//
// Three requests are session-level (Session, SessionTranscript, SessionSummary)
// and one is checkpoint-level (CheckpointAttribution). Adding a write operation
// is a new request type plus one dispatch case — the Store interface stays
// unchanged and existing backends keep compiling.
type WriteRequest interface {
    isWriteRequest()
}

// Session creates or replaces a session document within a checkpoint,
// materializing the checkpoint on its first session. (session-level)
type Session WriteOptions

// SessionTranscript replaces a session's transcript, prompts, and skill events
// at stop time without clobbering sibling fields. (session-level)
type SessionTranscript UpdateOptions

// SessionSummary rewrites only the summary of the checkpoint's latest session.
// (session-level)
type SessionSummary struct {
    CheckpointID id.CheckpointID
    Summary      *Summary
}

// CheckpointAttribution rewrites the checkpoint root's combined attribution
// across all sessions. (checkpoint-level)
//
//nolint:revive // CheckpointAttribution stutter is accepted — the name makes the checkpoint (vs session) tier explicit.
type CheckpointAttribution struct {
    CheckpointID id.CheckpointID
    Attribution  *Attribution
}

func (Session) isWriteRequest()               {}
func (SessionTranscript) isWriteRequest()     {}
func (SessionSummary) isWriteRequest()        {}
func (CheckpointAttribution) isWriteRequest() {}

// Writer is the persistent-store write surface: a single Write that accepts any
// WriteRequest. It is the natural type for mirror fan-out.
type Writer interface {
    Write(ctx context.Context, req WriteRequest) error
}

// ReadCheckpoint reads a checkpoint summary and normalizes a nil store response
// into ErrCheckpointNotFound.
func ReadCheckpoint(ctx context.Context, reader CheckpointReader, checkpointID id.CheckpointID) (*CheckpointSummary, error) {
    if err := ctx.Err(); err != nil {
        return nil, err //nolint:wrapcheck // Propagating context cancellation
    }

summary, err := reader.Read(ctx, checkpointID)
    if err != nil {
        return nil, fmt.Errorf("read persistent checkpoint: %w", err)
    }
    if summary == nil {
        return nil, ErrCheckpointNotFound
    }
    return summary, nil
}

// ReadLatestSessionContent reads the latest session from an already-resolved
// session reader and summary.
func ReadLatestSessionContent(ctx context.Context, reader SessionReader, checkpointID id.CheckpointID, summary *CheckpointSummary) (*SessionContent, error) {
    if summary == nil || len(summary.Sessions) == 0 {
        return nil, ErrCheckpointNotFound
    }
    latestIndex := len(summary.Sessions) - 1
    content, err := reader.ReadSessionContent(ctx, checkpointID, latestIndex)
    if err != nil {
        return nil, fmt.Errorf("read session %d content: %w", latestIndex, err)
    }
    return content, nil
}

// ReadRawSessionLogForCheckpoint reads a checkpoint's latest-session transcript;
// it needs both reader tiers (resolve the checkpoint, then its latest session).
func ReadRawSessionLogForCheckpoint(ctx context.Context, reader interface {
    CheckpointReader
    SessionReader
}, checkpointID id.CheckpointID) ([]byte, string, error) {
    if err := ctx.Err(); err != nil {
        return nil, "", err //nolint:wrapcheck // Propagating context cancellation
    }

summary, err := ReadCheckpoint(ctx, reader, checkpointID)
    if err != nil {
        return nil, "", err
    }

content, err := ReadLatestSessionContent(ctx, reader, checkpointID, summary)
    if err != nil {
        return nil, "", err
    }
    return content.Transcript, content.Metadata.SessionID, nil
}

Aapi/checkpoint/interfaces.go+135

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492

package checkpoint

import (
    "encoding/json"
    "time"

"github.com/entireio/cli/cmd/entire/cli/agent/types"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/redact"

"github.com/go-git/go-git/v6/plumbing"
)

// WriteOptions contains options for writing a committed checkpoint.
type WriteOptions struct {
    // CheckpointID is the stable 12-hex-char identifier
    CheckpointID id.CheckpointID

// SessionID is the session identifier
    SessionID string

// CreatedAt is when the checkpoint was originally created.
    // When zero, writers use the current time.
    CreatedAt time.Time

// Strategy is the name of the strategy that created this checkpoint
    Strategy string

// Branch is the branch name where the checkpoint was created (empty if detached HEAD)
    Branch string

// Transcript is the session transcript content (full.jsonl).
    // Must be pre-redacted (via redact.JSONLBytes or redact.AlreadyRedacted for trusted sources).
    Transcript redact.RedactedBytes

// Prompts contains the raw user prompts from the session. Run through
    // redactedJoinedPrompts before persisting — the writer does this
    // inside writeSessionToSubdirectory.
    Prompts []string

// FilesTouched are files modified during the session
    FilesTouched []string

// CheckpointsCount is the displayed "steps" count for this session: the number
    // of user prompts attributed to this checkpoint (floored at 1). Despite the
    // historical name/JSON tag, it is no longer a count of checkpoints.
    CheckpointsCount int

// SaveStepCount is the number of SaveStep-recorded steps (shadow-branch
    // commits) for this session. Distinct from CheckpointsCount (the displayed
    // prompt count): this is the honest "did real checkpoint work happen" signal
    // used to gate combined attribution. 0 means a commit-only / fallback session.
    SaveStepCount int

// EphemeralBranch is the shadow branch name (for manual-commit strategy)
    EphemeralBranch string

// AuthorName is the name to use for commits
    AuthorName string

// AuthorEmail is the email to use for commits
    AuthorEmail string

// MetadataDir is a directory containing additional metadata files to copy
    // If set, all files in this directory will be copied to the checkpoint path
    // This is useful for copying task metadata files, subagent transcripts, etc.
    MetadataDir string

// Task checkpoint fields (for task/subagent checkpoints)
    IsTask    bool   // Whether this is a task checkpoint
    ToolUseID string // Tool use ID for task checkpoints

// Additional task checkpoint fields for subagent checkpoints
    AgentID                string // Subagent identifier
    CheckpointUUID         string // UUID for transcript truncation when rewinding
    TranscriptPath         string // Path to session transcript file (alternative to in-memory Transcript)
    SubagentTranscriptPath string // Path to subagent's transcript file

// Incremental checkpoint fields
    IsIncremental       bool   // Whether this is an incremental checkpoint
    IncrementalSequence int    // Checkpoint sequence number
    IncrementalType     string // Tool type that triggered this checkpoint
    IncrementalData     []byte // Tool input payload for this checkpoint

// Commit message fields (used for task checkpoints)
    CommitSubject string // Subject line for the metadata commit (overrides default)

// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
    Agent types.AgentType

// Model is the LLM model used during the session (e.g., "claude-sonnet-4-20250514")
    Model string

// TurnID correlates checkpoints from the same agent turn.
    TurnID string

// Transcript position at checkpoint start - tracks what was added during this checkpoint
    TranscriptIdentifierAtStart string // Last identifier when checkpoint started (UUID for Claude, message ID for Gemini)
    CheckpointTranscriptStart   int    // Transcript line offset at start of this checkpoint's data

// CheckpointTranscriptStart is written to both Metadata.CheckpointTranscriptStart
    // and the deprecated Metadata.TranscriptLinesAtStart for backward compatibility.

// TokenUsage contains the token usage for this checkpoint
    TokenUsage *types.TokenUsage

// SkillEvents records explicit native skill signals observed in this session.
    SkillEvents []types.SkillEvent

// SessionMetrics contains hook-provided session metrics (duration, turns, context usage)
    SessionMetrics *SessionMetrics

// Attribution is line-level attribution calculated at commit time
    // comparing checkpoint tree (agent work) to committed tree (may include human edits)
    Attribution *Attribution

// PromptAttributionsJSON is the raw PromptAttributions data, JSON-encoded.
    // Persisted for diagnostic purposes — shows exactly which prompt recorded
    // which "user" lines, enabling root cause analysis of attribution bugs.
    // Uses json.RawMessage to avoid importing session package.
    PromptAttributionsJSON json.RawMessage

// CombinedAttribution is holistic attribution across all sessions.
    // Used during migration to preserve v1 root summary attribution.
    // During normal condensation this is nil (computed post-commit via a CheckpointAttribution write).
    CombinedAttribution *Attribution

// Summary is an optional AI-generated summary for this checkpoint.
    // This field may be nil when:
    //   - summarization is disabled in settings
    //   - summary generation failed (non-blocking, logged as warning)
    //   - the transcript was empty or too short to summarize
    //   - the checkpoint predates the summarization feature
    Summary *Summary

// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
    Kind string

// ReviewSkills is the snapshot of skills used (only meaningful when Kind is a review kind).
    // May be empty when a review is attached post-hoc without declared skills.
    ReviewSkills []string

// ReviewPrompt is the actual text of the review request (composed prompt
    // for spawn, first user prompt for attach). Only meaningful when Kind is
    // a review kind.
    ReviewPrompt string

// HasReview is set by the caller when this session should mark its
    // checkpoint as reviewed. The caller computes this (e.g. via
    // session.Kind.IsReview) because checkpoint can't import session
    // — the session package imports checkpoint, creating a cycle.
    HasReview bool

// InvestigateRunID is the 12-hex-char ID of the parent investigation
    // run (only meaningful when Kind is an investigate kind).
    InvestigateRunID string

// InvestigateTopic is the human-readable topic the investigation was
    // asked to investigate (only meaningful when Kind is an investigate
    // kind).
    InvestigateTopic string

// HasInvestigation is set by the caller when this session should mark
    // its checkpoint as part of an investigation. The caller computes this
    // (e.g. via session.Kind.IsInvestigate) because checkpoint can't import
    // session — the session package imports checkpoint, creating a cycle.
    HasInvestigation bool
}

// UpdateOptions contains options for updating an existing committed checkpoint.
// Uses replace semantics: the transcript and prompts are fully replaced,
// not appended. At stop time we have the complete session transcript and want every
// checkpoint to contain it identically.
type UpdateOptions struct {
    // CheckpointID identifies the checkpoint to update
    CheckpointID id.CheckpointID

// SessionID identifies which session slot to update within the checkpoint
    SessionID string

// Transcript is the full session transcript (replaces existing).
    // Must be pre-redacted (via redact.JSONLBytes or redact.AlreadyRedacted for trusted sources).
    Transcript redact.RedactedBytes

// Prompts contains the raw user prompts (replaces existing).
    // See WriteOptions.Prompts.
    Prompts []string

// Agent identifies the agent type (needed for transcript chunking)
    Agent types.AgentType

// SkillEvents replaces the session metadata skill_events when non-empty.
    SkillEvents []types.SkillEvent

// PrecomputedBlobs, if non-nil, provides chunk blob hashes and the
    // content-hash blob hash computed once for this transcript. When set,
    // transcript backfill skips the per-call ChunkTranscript + zlib work and
    // reuses these hashes. Used by finalizeAllTurnCheckpoints to avoid
    // re-compressing identical content N times.
    PrecomputedBlobs *PrecomputedTranscriptBlobs
}

// PrecomputedTranscriptBlobs holds blob hashes for a transcript that was
// chunked and written to the object store once, for reuse across multiple
// transcript-backfill writes sharing the same transcript content.
// Callers should avoid constructing this for empty transcripts; agent.ChunkTranscript
// would otherwise produce a single zero-length chunk and a hash for an empty
// blob, which downstream stores would never reference.
type PrecomputedTranscriptBlobs struct {
    // ChunkHashes are the blob hashes for each transcript chunk, in order.
    // Always non-empty when built via PrecomputeTranscriptBlobs (a non-empty
    // transcript chunks to at least one entry; callers should skip precompute
    // for empty transcripts).
    ChunkHashes []plumbing.Hash

// ContentHashBlob is the blob hash of the "sha256:<hex>" content-hash
    // string for the transcript.
    ContentHashBlob plumbing.Hash

// ContentHash is the "sha256:<hex>" string itself, so the short-circuit
    // path can compare without re-reading the blob.
    ContentHash string
}

// IsUsable reports whether the precomputed blobs satisfy the invariants that
// consumers depend on: a non-zero content-hash blob and at least one chunk
// hash. Callers should fall back to the fresh-write path when this is false.
func (p *PrecomputedTranscriptBlobs) IsUsable() bool {
    return p != nil && !p.ContentHashBlob.IsZero() && len(p.ChunkHashes) > 0
}

// CheckpointInfo contains summary information about a persisted checkpoint.
//
//nolint:revive // Named CheckpointInfo to avoid conflict with the generic Info type; the checkpoint.CheckpointInfo stutter is accepted (matches CheckpointSummary).
type CheckpointInfo struct {
    // CheckpointID is the stable 12-hex-char identifier
    CheckpointID id.CheckpointID

// SessionID is the session identifier (most recent session for multi-session checkpoints)
    SessionID string

// CreatedAt is when the checkpoint was created
    CreatedAt time.Time

// CheckpointsCount is the aggregate displayed "steps" count across sessions:
    // the sum of per-session prompt-window counts. Despite the historical name,
    // it is not a count of checkpoint records.
    CheckpointsCount int

// FilesTouched are files modified during all sessions
    FilesTouched []string

// Agent identifies the agent that created this checkpoint
    Agent types.AgentType

// IsTask indicates if this is a task checkpoint
    IsTask bool

// ToolUseID is the tool use ID for task checkpoints
    ToolUseID string

// Multi-session support
    SessionCount int      // Number of sessions (1 if single session)
    SessionIDs   []string // All session IDs that contributed
}

// SessionContent contains the actual content for a session.
// This is used when reading full session data (transcript, prompts, context)
// as opposed to just the metadata/summary.
type SessionContent struct {
    // Metadata contains the session-specific metadata
    Metadata Metadata

// Transcript is the session transcript content
    Transcript []byte

// TranscriptBlobHashes are the stored raw transcript blob hashes in chunk
    // order. Callers that rewrite the same transcript under a different path can
    // reuse these content-addressed blobs instead of storing duplicate blobs.
    TranscriptBlobHashes []plumbing.Hash

// Prompts contains user prompts from this session
    Prompts string
}

// Metadata contains the metadata stored in metadata.json for each checkpoint.
type Metadata struct {
    CLIVersion       string          `json:"cli_version,omitempty"`
    CheckpointID     id.CheckpointID `json:"checkpoint_id"`
    SessionID        string          `json:"session_id"`
    Strategy         string          `json:"strategy"`
    CreatedAt        time.Time       `json:"created_at"`
    Branch           string          `json:"branch,omitempty"` // Branch where checkpoint was created (empty if detached HEAD)
    CheckpointsCount int             `json:"checkpoints_count"`
    // SaveStepCount is the number of SaveStep-recorded steps for this session.
    // Honest "real checkpoint work happened" signal (0 = commit-only/fallback
    // session), kept separate from the displayed CheckpointsCount prompt count.
    // Added after CheckpointsCount stopped being a reliable did-SaveStep-run signal.
    SaveStepCount int      `json:"save_step_count,omitempty"`
    FilesTouched  []string `json:"files_touched"`

// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
    Agent types.AgentType `json:"agent,omitempty"`

// Model is the LLM model used during the session (e.g., "claude-sonnet-4-20250514").
    // Always written to metadata (empty string when unknown) so consumers can rely on the field's presence.
    Model string `json:"model"`

// TurnID correlates checkpoints from the same agent turn.
    // When a turn's work spans multiple commits, each gets its own checkpoint
    // but they share the same TurnID for future aggregation/deduplication.
    TurnID string `json:"turn_id,omitempty"`

// Task checkpoint fields (only populated for task checkpoints)
    IsTask    bool   `json:"is_task,omitempty"`
    ToolUseID string `json:"tool_use_id,omitempty"`

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

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

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

// SkillEvents records explicit native skill signals observed in this session.
    // Consumers use these anchors to collapse skill-related raw transcript events.
    SkillEventsVersion int                `json:"skill_events_version,omitempty"`
    SkillEvents        []types.SkillEvent `json:"skill_events,omitempty"`

// SessionMetrics contains hook-provided session metrics (duration, turns, context usage).
    // Populated for agents that provide these metrics via hooks (e.g., Cursor).
    SessionMetrics *SessionMetrics `json:"session_metrics,omitempty"`

// AI-generated summary of the checkpoint
    Summary *Summary `json:"summary,omitempty"`

// Attribution is line-level attribution calculated at commit time
    Attribution *Attribution `json:"initial_attribution,omitempty"`

// PromptAttributions is the raw per-prompt attribution data used to compute Attribution.
    // Diagnostic field — shows which prompt recorded which "user" lines.
    PromptAttributions json.RawMessage `json:"prompt_attributions,omitempty"`

// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
    Kind string `json:"kind,omitempty"`

// ReviewSkills lists the review skills that were run (only set when Kind is a review kind).
    // May be empty when a review was attached post-hoc without declared skills.
    ReviewSkills []string `json:"review_skills,omitempty"`

// ReviewPrompt is the actual text of the review request (composed prompt
    // for spawn, first user prompt for attach). Only set when Kind is a
    // review kind.
    ReviewPrompt string `json:"review_prompt,omitempty"`

// InvestigateRunID is the 12-hex-char ID of the parent investigation
    // run. Only set when Kind is an investigate kind.
    InvestigateRunID string `json:"investigate_run_id,omitempty"`

// InvestigateTopic is the human-readable topic the investigation was
    // asked to investigate. Only set when Kind is an investigate kind.
    InvestigateTopic string `json:"investigate_topic,omitempty"`
}

// GetTranscriptStart returns the transcript line offset at which this checkpoint's data begins.
// Returns 0 for new checkpoints (start from beginning). For data written by older CLI versions,
// falls back to the deprecated TranscriptLinesAtStart field.
func (m Metadata) GetTranscriptStart() int {
    if m.CheckpointTranscriptStart > 0 {
        return m.CheckpointTranscriptStart
    }
    return m.TranscriptLinesAtStart
}

// 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.
type SessionFilePaths struct {
    Metadata string `json:"metadata"`
    // Transcript points at the compact transcript.jsonl when one was
    // generated, otherwise at the raw full.jsonl. Checkpoints written by
    // older CLI versions always point at full.jsonl.
    Transcript  string `json:"transcript,omitempty"`
    ContentHash string `json:"content_hash,omitempty"`
    Prompt      string `json:"prompt"`
}

// CheckpointSummary is the root-level metadata.json for a checkpoint.
// It contains aggregated statistics from all sessions and a map of session IDs
// to their file paths. Session-specific data (including initial_attribution)
// is stored in the session's subdirectory metadata.json.
//
// Structure on entire/checkpoints/v1 branch:
//
//	<checkpoint-id[:2]>/<checkpoint-id[2:]>/
//	├── metadata.json         # This CheckpointSummary
//	├── 1/                    # First session
//	│   ├── metadata.json     # Session-specific Metadata
//	│   ├── full.jsonl        # Raw agent transcript
//	│   ├── transcript.jsonl  # Compact transcript scoped to this checkpoint
//	│   ├── prompt.txt
//	│   └── content_hash.txt
//	├── 2/                    # Second session
//	└── 3/                    # Third session...
//
//nolint:revive // Named CheckpointSummary to avoid conflict with existing Summary struct
type CheckpointSummary struct {
    CLIVersion          string             `json:"cli_version,omitempty"`
    CheckpointVersion   string             `json:"checkpoint_version,omitempty"`
    CheckpointID        id.CheckpointID    `json:"checkpoint_id"`
    Strategy            string             `json:"strategy"`
    Branch              string             `json:"branch,omitempty"`
    CheckpointsCount    int                `json:"checkpoints_count"`
    FilesTouched        []string           `json:"files_touched"`
    Sessions            []SessionFilePaths `json:"sessions"`
    TokenUsage          *types.TokenUsage  `json:"token_usage,omitempty"`
    CombinedAttribution *Attribution       `json:"combined_attribution,omitempty"`

// HasReview is the umbrella "any review happened" flag: true when at least
    // one session in this checkpoint has a review-kind Kind (currently
    // "agent_review"). When new review kinds are introduced they should also
    // cause this flag to be set so callers can keep asking "was this reviewed
    // in any way?" without caring about the variant.
    HasReview bool `json:"has_review,omitempty"`

// HasInvestigation is the umbrella "any investigation happened" flag:
    // true when at least one session in this checkpoint has an
    // investigate-kind Kind (currently "agent_investigate"). When new
    // investigate kinds are introduced they should also cause this flag to
    // be set so callers can keep asking "was this investigated in any way?"
    // without caring about the variant.
    HasInvestigation bool `json:"has_investigation,omitempty"`
}

// SessionMetrics contains hook-provided session metrics from agents that report
// them via lifecycle hooks (e.g., Cursor). These supplement transcript-derived
// metrics for agents whose transcripts lack usage/timing data.
type SessionMetrics struct {
    DurationMs        int64 `json:"duration_ms,omitempty"`
    TurnCount         int   `json:"turn_count,omitempty"`
    ContextTokens     int   `json:"context_tokens,omitempty"`
    ContextWindowSize int   `json:"context_window_size,omitempty"`
}

// Summary contains AI-generated summary of a checkpoint.
type Summary struct {
    Intent    string           `json:"intent"`     // What user wanted to accomplish
    Outcome   string           `json:"outcome"`    // What was achieved
    Learnings LearningsSummary `json:"learnings"`  // Categorized learnings
    Friction  []string         `json:"friction"`   // Problems/annoyances encountered
    OpenItems []string         `json:"open_items"` // Tech debt, unfinished work
}

// LearningsSummary contains learnings grouped by scope.
type LearningsSummary struct {
    Repo     []string       `json:"repo"`     // Codebase-specific patterns/conventions
    Code     []CodeLearning `json:"code"`     // File/module specific findings
    Workflow []string       `json:"workflow"` // General dev practices
}

// CodeLearning captures a learning tied to a specific code location.
type CodeLearning struct {
    Path    string `json:"path"`               // File path
    Line    int    `json:"line,omitempty"`     // Start line number
    EndLine int    `json:"end_line,omitempty"` // End line for ranges (optional)
    Finding string `json:"finding"`            // What was learned
}

// Attribution captures line-level attribution metrics at commit time.
// This is a point-in-time snapshot comparing the checkpoint tree (agent work)
// against the committed tree (may include human edits).
//
// Attribution Metrics:
//   - TotalCommitted keeps the historical "net additions" view for compatibility
//   - TotalLinesChanged measures total committed line changes (adds + modifies + removes)
//   - AgentPercentage represents "of the lines changed in this commit, what percentage came from the agent"
//   - AgentRemoved tracks committed deletions performed by the agent
type Attribution struct {
    CalculatedAt      time.Time `json:"calculated_at"`
    AgentLines        int       `json:"agent_lines"`              // Lines added by agent that remain in the commit
    AgentRemoved      int       `json:"agent_removed"`            // Lines removed by agent that remain removed in the commit
    HumanAdded        int       `json:"human_added"`              // Lines added by human (excluding modifications)
    HumanModified     int       `json:"human_modified"`           // Lines modified by human (estimate: min(added, removed))
    HumanRemoved      int       `json:"human_removed"`            // Lines removed by human (excluding modifications)
    TotalCommitted    int       `json:"total_committed"`          // Net additions in commit (legacy additions-focused metric)
    TotalLinesChanged int       `json:"total_lines_changed"`      // Total committed line changes (adds + modifies + removes)
    AgentPercentage   float64   `json:"agent_percentage"`         // (agent_lines + agent_removed) / total_lines_changed * 100
    MetricVersion     int       `json:"metric_version,omitempty"` // 0/absent = legacy (additions-only %), 2 = changed-lines %
}

Aapi/checkpoint/metadata.go+492

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

package checkpoint

import (
    "context"

apicheckpoint "github.com/entireio/cli/api/checkpoint"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)

// The persistent-checkpoint contract (persisted document types, option types,
// reader/writer interfaces, and the Write request union) lives in the
// api/checkpoint package so storage backends can depend on it without the CLI's
// agent/git machinery. These aliases re-export it under this package so existing
// CLI call sites are unaffected; the git implementation (GitStore, Open, the
// facade, and the ephemeral shadow-branch surface) stays here.
type (
    // Persisted document types.
    Metadata = apicheckpoint.Metadata
    //nolint:revive // CheckpointSummary stutter is accepted (named to avoid conflict with Summary).
    CheckpointSummary = apicheckpoint.CheckpointSummary
    //nolint:revive // CheckpointInfo stutter is accepted (Info is taken by the generic checkpoint.Info type).
    CheckpointInfo   = apicheckpoint.CheckpointInfo
    SessionContent   = apicheckpoint.SessionContent
    SessionFilePaths = apicheckpoint.SessionFilePaths
    SessionMetrics   = apicheckpoint.SessionMetrics
    Summary          = apicheckpoint.Summary
    LearningsSummary = apicheckpoint.LearningsSummary
    CodeLearning     = apicheckpoint.CodeLearning
    Attribution      = apicheckpoint.Attribution

// Operation option types.
    WriteOptions               = apicheckpoint.WriteOptions
    UpdateOptions              = apicheckpoint.UpdateOptions
    PrecomputedTranscriptBlobs = apicheckpoint.PrecomputedTranscriptBlobs

// Reader/writer interfaces and the Write request union. Reads are tiered by
    // scope: CheckpointReader (checkpoint-level) and SessionReader (session-level),
    // composed with Writer into PersistentStore.
    //nolint:revive // CheckpointReader stutter is accepted — marks the checkpoint (vs session) read tier.
    CheckpointReader = apicheckpoint.CheckpointReader
    SessionReader    = apicheckpoint.SessionReader
    PersistentStore  = apicheckpoint.PersistentStore
    Writer           = apicheckpoint.Writer
    WriteRequest     = apicheckpoint.WriteRequest
    // Write request union: session-level (Session, SessionTranscript,
    // SessionSummary) and checkpoint-level (CheckpointAttribution).
    Session           = apicheckpoint.Session
    SessionTranscript = apicheckpoint.SessionTranscript
    SessionSummary    = apicheckpoint.SessionSummary
    //nolint:revive // CheckpointAttribution stutter is accepted — makes the checkpoint (vs session) tier explicit.
    CheckpointAttribution = apicheckpoint.CheckpointAttribution
)

// CheckpointVersionBranchV1 identifies the branch-backed checkpoint metadata format.
const CheckpointVersionBranchV1 = apicheckpoint.CheckpointVersionBranchV1

// Sentinel errors (re-exported so errors.Is keeps working across packages).
var (
    ErrCheckpointNotFound = apicheckpoint.ErrCheckpointNotFound
    ErrNoTranscript       = apicheckpoint.ErrNoTranscript
)

// Contract helper functions, re-exported as thin wrappers rather than vars so
// the facade symbols can't be reassigned by consumers.

func ReadCheckpoint(ctx context.Context, reader CheckpointReader, checkpointID id.CheckpointID) (*CheckpointSummary, error) {
    return apicheckpoint.ReadCheckpoint(ctx, reader, checkpointID) //nolint:wrapcheck // thin re-export of the api/checkpoint helper
}

func ReadLatestSessionContent(ctx context.Context, reader SessionReader, checkpointID id.CheckpointID, summary *CheckpointSummary) (*SessionContent, error) {
    return apicheckpoint.ReadLatestSessionContent(ctx, reader, checkpointID, summary) //nolint:wrapcheck // thin re-export of the api/checkpoint helper
}

func ReadRawSessionLogForCheckpoint(ctx context.Context, reader interface {
    CheckpointReader
    SessionReader
}, checkpointID id.CheckpointID) ([]byte, string, error) {
    return apicheckpoint.ReadRawSessionLogForCheckpoint(ctx, reader, checkpointID) //nolint:wrapcheck // thin re-export of the api/checkpoint helper
}

Acmd/entire/cli/checkpoint/aliases.go+79

7 unmodified lines

8
9
10
11
12
11
12
13
16
17
14
15
16
17
22
23
24
25
26
27
28
29
30
31
32
33
18
19
20
124 unmodified lines

145
146
147
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
148
149
150

7 unmodified lines

import (
    "context"
    "encoding/json"
    "errors"
    "time"

"github.com/entireio/cli/cmd/entire/cli/agent/types"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/redact"

"github.com/go-git/go-git/v6/plumbing"
)

// ErrNoTranscript is returned when a checkpoint exists but has no transcript.
    ErrNoTranscript = errors.New("no transcript found for checkpoint")
)

// CheckpointVersionBranchV1 identifies the branch-backed checkpoint metadata format.
const CheckpointVersionBranchV1 = "branch-v1"

// Checkpoint represents a save point within a session.
type Checkpoint struct {
    // ID is the unique checkpoint identifier
124 unmodified lines

Timestamp time.Time
}

// SessionID is the session identifier
    SessionID string

// CreatedAt is when the checkpoint was originally created.
    // When zero, writers use the current time.
    CreatedAt time.Time

// Strategy is the name of the strategy that created this checkpoint
    Strategy string

// Branch is the branch name where the checkpoint was created (empty if detached HEAD)
    Branch string

// FilesTouched are files modified during the session
    FilesTouched []string

// EphemeralBranch is the shadow branch name (for manual-commit strategy)
    EphemeralBranch string

// AuthorName is the name to use for commits
    AuthorName string

// AuthorEmail is the email to use for commits
    AuthorEmail string

// Task checkpoint fields (for task/subagent checkpoints)
    IsTask    bool   // Whether this is a task checkpoint
    ToolUseID string // Tool use ID for task checkpoints

// Commit message fields (used for task checkpoints)
    CommitSubject string // Subject line for the metadata commit (overrides default)

// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
    Agent types.AgentType

// Model is the LLM model used during the session (e.g., "claude-sonnet-4-20250514")
    Model string

// TurnID correlates checkpoints from the same agent turn.
    TurnID string

// TokenUsage contains the token usage for this checkpoint
    TokenUsage *types.TokenUsage

// SkillEvents records explicit native skill signals observed in this session.
    SkillEvents []types.SkillEvent

// SessionMetrics contains hook-provided session metrics (duration, turns, context usage)
    SessionMetrics *SessionMetrics

// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
    Kind string

// InvestigateRunID is the 12-hex-char ID of the parent investigation
    // run (only meaningful when Kind is an investigate kind).
    InvestigateRunID string

// SessionID identifies which session slot to update within the checkpoint
    SessionID string

// Prompts contains the raw user prompts (replaces existing).
    // See WriteOptions.Prompts.
    Prompts []string

// Agent identifies the agent type (needed for transcript chunking)
    Agent types.AgentType

// SkillEvents replaces the session metadata skill_events when non-empty.
    SkillEvents []types.SkillEvent

// ContentHashBlob is the blob hash of the "sha256:<hex>" content-hash
    // string for the transcript.
    ContentHashBlob plumbing.Hash

// ContentHash is the "sha256:<hex>" string itself, so the short-circuit
    // path can compare without re-reading the blob.
    ContentHash string
}

// SessionID is the session identifier (most recent session for multi-session checkpoints)
    SessionID string

// CreatedAt is when the checkpoint was created
    CreatedAt time.Time

// FilesTouched are files modified during all sessions
    FilesTouched []string

// Agent identifies the agent that created this checkpoint
    Agent types.AgentType

// IsTask indicates if this is a task checkpoint
    IsTask bool

// ToolUseID is the tool use ID for task checkpoints
    ToolUseID string

// Multi-session support
    SessionCount int      // Number of sessions (1 if single session)
    SessionIDs   []string // All session IDs that contributed
}

// Transcript is the session transcript content
    Transcript []byte

// Prompts contains user prompts from this session
    Prompts string
}

// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
    Agent types.AgentType `json:"agent,omitempty"`

// Task checkpoint fields (only populated for task checkpoints)
    IsTask    bool   `json:"is_task,omitempty"`
    ToolUseID string `json:"tool_use_id,omitempty"`

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

// AI-generated summary of the checkpoint
    Summary *Summary `json:"summary,omitempty"`

// Attribution is line-level attribution calculated at commit time
    Attribution *Attribution `json:"initial_attribution,omitempty"`

// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
    Kind string `json:"kind,omitempty"`

func normalizeCheckpointSummary(summary *CheckpointSummary) *CheckpointSummary {
    if summary == nil {
        return nil
    }
    if summary.CheckpointVersion == "" {
        summary.CheckpointVersion = CheckpointVersionBranchV1
    }
    return summary
}

// Info provides summary information for listing checkpoints.
// This is the generic checkpoint info type.
type Info struct {

Mcmd/entire/cli/checkpoint/checkpoint.go-506

1690 unmodified lines

1691
1692
1693
1694
1694
1695
1696
1697

1690 unmodified lines

// checkpoint.
func (s *GitStore) replaceTranscript(ctx context.Context, transcript redact.RedactedBytes, agentType types.AgentType, startLine int, precomputed *PrecomputedTranscriptBlobs, sessionPath string, entries map[string]object.TreeEntry) error {
    // Ignore precompute if invariants are violated — fall back to fresh chunking.
    if precomputed != nil && !precomputed.isUsable() {
    if precomputed != nil && !precomputed.IsUsable() {
        precomputed = nil
    }

Mcmd/entire/cli/checkpoint/persistent.go+1/-1

1 unmodified line

2
3
4
5
5
6
7
8
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
9
10
11
12
13
14
15
42
43
44
45
46
47
48
49
50
51
52
16
17
18
19
54
20
21
56
57
58
59
60
61
62
63
22
23
24
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
25
26

1 unmodified line

import (
    "context"
    "fmt"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)

// PersistentStore provides the production persistent checkpoint storage surface:
// checkpoint-level reads, session-level reads, and the unified Write. Writes go
// through Writer.Write(ctx, WriteRequest); the concrete per-operation methods
// (writeSession/backfillTranscript/...) live on the git implementation as the
// methods Write dispatches to.
type PersistentStore interface {
    CheckpointReader
    SessionReader
    Writer
}

// AuthorReader provides optional checkpoint author lookup.
// AuthorReader provides optional checkpoint author lookup. It stays in the
// implementation package: GetCheckpointAuthor is a git-log operation and Author
// is an implementation type, not part of the storage contract.
type AuthorReader interface {
    GetCheckpointAuthor(ctx context.Context, checkpointID id.CheckpointID) (Author, error)
}

summary, err := reader.Read(ctx, checkpointID)
    if err != nil {
        return nil, fmt.Errorf("read persistent checkpoint: %w", err)
    }
// normalizeCheckpointSummary fills in the checkpoint metadata format version
// for summaries read back without one (older records predate the field).
func normalizeCheckpointSummary(summary *CheckpointSummary) *CheckpointSummary {
    if summary == nil {
        return nil, ErrCheckpointNotFound
        return nil
    }
    return summary, nil
}

// ReadLatestSessionContent reads the latest session from an already-resolved
// session reader and summary.
func ReadLatestSessionContent(ctx context.Context, reader SessionReader, checkpointID id.CheckpointID, summary *CheckpointSummary) (*SessionContent, error) {
    if summary == nil || len(summary.Sessions) == 0 {
        return nil, ErrCheckpointNotFound
    if summary.CheckpointVersion == "" {
        summary.CheckpointVersion = CheckpointVersionBranchV1
    }
    latestIndex := len(summary.Sessions) - 1
    content, err := reader.ReadSessionContent(ctx, checkpointID, latestIndex)
    if err != nil {
        return nil, fmt.Errorf("read session %d content: %w", latestIndex, err)
    }
    return content, nil
}

summary, err := ReadCheckpoint(ctx, reader, checkpointID)
    if err != nil {
        return nil, "", err
    }

content, err := ReadLatestSessionContent(ctx, reader, checkpointID, summary)
    if err != nil {
        return nil, "", err
    }
    return content.Transcript, content.Metadata.SessionID, nil
    return summary
}

Mcmd/entire/cli/checkpoint/persistent_reader.go+10/-77

2 unmodified lines

3
4
5
6
7
6
7
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
8
9
10
11
12
13
14

2 unmodified lines

import (
    "context"
    "fmt"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)

// WriteRequest is a single committed-store write command. The set is closed to
// other packages: only types in package checkpoint can implement it, sealed via
// the unexported isWriteRequest marker. A store dispatches on the concrete type;
// a mirror/fan-out store forwards the same value to each backend's Write.
//
// This replaces the four separate writer methods (writeSession /
// backfillTranscript / backfillSummary / backfillAttribution) with one
// Store.Write(ctx, req) entry point, so adding a write operation is a new
// request type plus one dispatch case — the Store interface stays unchanged
// and existing backends keep compiling.
type WriteRequest interface {
    isWriteRequest()
}

// Session creates or replaces a session document within a checkpoint,
// materializing the checkpoint on its first session. This is condensation's
// write. (Maps to the former writeSession.)
type Session WriteOptions

// SessionTranscript replaces a session's transcript, prompts, and skill
// events at stop time without clobbering sibling fields. (Maps to the former
// backfillTranscript.)
type SessionTranscript UpdateOptions

// SessionSummary rewrites only the summary of the checkpoint's latest
// session. (Maps to the former backfillSummary.)
type SessionSummary struct {
    CheckpointID id.CheckpointID
    Summary      *Summary
}

// CheckpointAttribution rewrites the checkpoint root's combined attribution.
// (Maps to the former backfillAttribution.)
//
//nolint:revive // CheckpointAttribution stutter is accepted — the name makes the checkpoint (vs session) tier explicit alongside the Session* requests.
type CheckpointAttribution struct {
    CheckpointID id.CheckpointID
    Attribution  *Attribution
}

// Writer is the committed-store write surface: a single Write that accepts any
// WriteRequest. It is the natural type for mirror fan-out.
type Writer interface {
    Write(ctx context.Context, req WriteRequest) error
}

// Write dispatches a committed write request to the matching git operation.
// Unknown request types are a programmer error, surfaced rather than ignored.
// Write dispatches a persistent write request to the matching git operation.
// The request types and Writer interface are defined in the api/checkpoint
// contract (re-exported here via aliases). Unknown request types are a
// programmer error, surfaced rather than ignored.
func (s *GitStore) Write(ctx context.Context, req WriteRequest) error {
    switch r := req.(type) {
    case Session:

Mcmd/entire/cli/checkpoint/persistent_write.go+4/-55

2 unmodified lines

3
4
5
6
6
7
8
9
10
11
13
14
15
16
17
18
12
13
14
15
16
17
18
101 unmodified lines

120
121
122
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
123
124
125

2 unmodified lines

import (
    "context"
    "errors"
    "strings"
    "testing"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/redact"
)

// unknownWriteRequest is a WriteRequest the dispatcher does not handle. It is
// sealed into the union via the unexported marker (only possible in-package),
// which lets the test exercise the default branch.
type unknownWriteRequest struct{}

func (unknownWriteRequest) isWriteRequest() {}
// Note: the Write dispatcher's default ("unsupported request") branch is no
// longer reachable from this package — the WriteRequest union is sealed to the
// api/checkpoint contract, so an unhandled request type can only be introduced
// there. The per-request dispatch below is the meaningful coverage.

// TestWrite_DispatchesEachRequest verifies that Store.Write routes each request
// type to the corresponding git operation, observing the effect of each.
101 unmodified lines

}
}

// TestWrite_UnknownRequestErrors verifies the dispatcher surfaces an
// unhandled request type rather than silently ignoring it.
func TestWrite_UnknownRequestErrors(t *testing.T) {
    t.Parallel()
    repo, _ := setupBranchTestRepo(t)
    store := NewGitStore(repo, DefaultV1Refs())

err := store.Write(context.Background(), unknownWriteRequest{})
    if err == nil {
        t.Fatal("Write(unknownWriteRequest) should error")
    }
    if !strings.Contains(err.Error(), "unsupported write request") {
        t.Errorf("error = %v, want mention of unsupported write request", err)
    }
}

// TestWrite_BackfillSummaryNotFound verifies error propagation through dispatch.
func TestWrite_BackfillSummaryNotFound(t *testing.T) {
    t.Parallel()

Mcmd/entire/cli/checkpoint/persistent_write_test.go+4/-23