test(import): end-to-end integration coverage; lint fixups · Entire
test(import): end-to-end integration coverage; lint fixups
d4715a1→main·
computermode·3w ago·7 files·+100 added/-22 removed
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Sessions
412f636a521eView transcript
[?
Generalize Agent Import Across PlatformsClaude Code·Opus 4.8·1 step](/content/gh/entireio/cli/session/b1e13e56-e9ae-469a-a30d-7c57d70e2f1f#timeline-412f636a521e/index.html)
Changes
7
cmd/entire/cli
Mimport_cmd.go+1/-1
importclaude
Mimporter.go+6/-3
Mturns.go+18/-8
integration_test
Aimport_claude_test.go+63
Mrewind.go+3/-3
Msetup.go+4/-4
strategy
Mcommon.go+5/-3
51 unmodified lines
52
53
54
55
55
56
57
58
51 unmodified lines
Now: time.Now(), DryRun: dryRun,
})
if err != nil {
return err
return fmt.Errorf("import claude-code: %w", err)
}
verb := "Imported"
if dryRun {
Mcmd/entire/cli/import_cmd.go+1/-1
58 unmodified lines
59
60
61
62
62
63
64
65
33 unmodified lines
99
100
101
102
102
103
104
105
7 unmodified lines
113
114
115
116
116
117
118
119
120
121
122
58 unmodified lines
for _, path := range files {
res.SessionsScanned++
sessionID := strings.TrimSuffix(filepath.Base(path), ".jsonl")
full, readErr := os.ReadFile(path)
full, readErr := os.ReadFile(path) //nolint:gosec // G304: path is a discovered .jsonl in the user's own Claude transcript dir
if readErr != nil {
return res, fmt.Errorf("read %s: %w", path, readErr)
}
33 unmodified lines
LineStart: turn.LineStart, LineEnd: turn.LineEnd,
ContentHash: turn.ContentHash, ImportVersion: importVersion,
}
return stores.Persistent.Write(ctx, cp.Session(cp.WriteOptions{
if err := stores.Persistent.Write(ctx, cp.Session(cp.WriteOptions{
CheckpointID: cid,
SessionID: sessionID,
CreatedAt: turn.CreatedAt,
7 unmodified lines
CheckpointTranscriptStart: turn.LineStart,
TokenUsage: turn.Tokens,
Provenance: prov,
}))
})); err != nil {
return fmt.Errorf("write imported checkpoint %s: %w", cid, err)
}
return nil
}
func ptrRefs(r cp.PersistentRefs) *cp.PersistentRefs { return &r }
Mcmd/entire/cli/importclaude/importer.go+6/-3
64 unmodified lines
65
66
67
68
69
70
71
72
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
1 unmodified line
87
88
89
80
81
82
90
91
92
93
94
95
64 unmodified lines
return nil, fmt.Errorf("token usage for turn %d: %w", k, err)
}
var line transcript.Line
_ = json.Unmarshal(rawLines[start], &line)
var ex extraFields
_ = json.Unmarshal(rawLines[start], &ex)
ts, _ := time.Parse(time.RFC3339, ex.Timestamp)
var rec struct {
UUID string `json:"uuid"`
Message json.RawMessage `json:"message"`
ParentUUID string `json:"parentUuid"`
Timestamp string `json:"timestamp"`
}
if err := json.Unmarshal(rawLines[start], &rec); err != nil {
// Already validated as a user-prompt line in isUserPromptLine; skip
// defensively if it somehow fails to parse here.
continue
}
ts, parseErr := time.Parse(time.RFC3339, rec.Timestamp)
if parseErr != nil {
ts = time.Time{}
}
slice := joinLines(rawLines[start:end])
sum := sha256.Sum256(slice)
1 unmodified line
turns = append(turns, Turn{
LineStart: start,
LineEnd: end,
UUID: line.UUID,
ParentUUID: ex.ParentUUID,
Prompt: transcript.ExtractUserContent(line.Message),
UUID: rec.UUID,
ParentUUID: rec.ParentUUID,
Prompt: transcript.ExtractUserContent(rec.Message),
Model: modelInRange(rawLines, start, end),
CreatedAt: ts,
Tokens: tokens,
Mcmd/entire/cli/importclaude/turns.go+18/-8
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
//go:build integration
package integration
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/stretchr/testify/require"
"github.com/entireio/cli/cmd/entire/cli/importclaude"
"github.com/entireio/cli/cmd/entire/cli/paths"
)
func TestImportClaudeCode_EndToEnd(t *testing.T) {
t.Parallel()
env := NewFeatureBranchEnv(t)
// Write a two-turn Claude transcript into the (overridden) Claude project dir.
sessionID := "sess1"
content := strings.Join([]string{
`{"type":"user","uuid":"u1","timestamp":"2026-06-20T00:00:00Z","message":{"role":"user","content":"first"}}`,
`{"type":"assistant","uuid":"a1","message":{"id":"m1","model":"claude-x","content":[{"type":"text","text":"ok"}],"usage":{"output_tokens":5}}}`,
`{"type":"user","uuid":"u2","timestamp":"2026-06-20T00:01:00Z","message":{"role":"user","content":"second"}}`,
}, "\n") + "\n"
require.NoError(t, os.WriteFile(filepath.Join(env.ClaudeProjectDir, sessionID+".jsonl"), []byte(content), 0o644))
importsRef := plumbing.NewBranchReferenceName(paths.ImportsBranchName)
// 1. Dry-run reports counts and writes nothing.
out := env.RunCLI("import", "claude-code", "--dry-run")
require.Contains(t, out, "Would import 2", "dry-run should count 2 turns; got: %s", out)
repo, err := git.PlainOpen(env.RepoDir)
require.NoError(t, err)
if _, err := repo.Reference(importsRef, false); err == nil {
t.Fatal("dry-run must not create the imports ref")
}
// 2. Real import creates the imports ref.
out = env.RunCLI("import", "claude-code")
require.Contains(t, out, "Imported 2", "got: %s", out)
ref, err := repo.Reference(importsRef, false)
require.NoError(t, err, "imports ref should exist after import")
require.False(t, ref.Hash().IsZero())
// 3. checkpoint list surfaces imported entries labeled [imported].
listOut := env.RunCLI("checkpoint", "list")
require.Contains(t, listOut, "[imported]", "checkpoint list should label imported checkpoints; got: %s", listOut)
// 4. Re-running import is idempotent.
out = env.RunCLI("import", "claude-code")
require.Contains(t, out, "(2 already imported)", "re-run should skip already-imported turns; got: %s", out)
// 5. Rewinding to an imported checkpoint is refused with a clear message.
importedID := importclaude.DeriveCheckpointID(sessionID, "u1").String()
rewindOut, rewindErr := env.RunCLIWithError("checkpoint", "rewind", "--to", importedID)
require.Error(t, rewindErr, "rewind to imported checkpoint should fail")
require.Contains(t, rewindOut, "read-only and not rewindable", "got: %s", rewindOut)
}
Acmd/entire/cli/integration_test/import_claude_test.go+63
418 unmodified lines
419
420
421
422
422
423
424
425
426
427
428
429
429
430
431
432
433
433
434
435
436
418 unmodified lines
func refuseIfImportedCheckpoint(ctx context.Context, errW io.Writer, commitID string) error {
repo, err := strategy.OpenRepository(ctx)
if err != nil {
return nil //nolint:nilerr // fall through to normal rewind resolution
return nil
}
defer repo.Close()
importsRefs := checkpoint.ImportsRefs();
stores, err := checkpoint.Open(ctx, repo, checkpoint.OpenOptions{Refs: &importsRefs})
if err != nil {
return nil //nolint:nilerr
return nil
}
infos, err := stores.Persistent.List(ctx)
if err != nil {
return nil //nolint:nilerr
return nil
}
for _, in := range infos {
idStr := in.CheckpointID.String()
Mcmd/entire/cli/rewind.go+3/-3
1878 unmodified lines
1879
1880
1881
1882
1882
1883
1884
1885
1886
1887
1887
1888
1889
1890
1891
1891
1892
1893
1894
31 unmodified lines
1926
1927
1928
1929
1929
1930
1931
1932
1878 unmodified lines
}
repoRoot, err := paths.WorktreeRoot(ctx)
if err != nil {
return nil //nolint:nilerr // not a git repo / nothing to import
return nil
}
now := time.Now()
files, err := importclaude.DiscoverSessions(repoRoot, "", now, nil)
if err != nil || len(files) == 0 {
return nil //nolint:nilerr // best-effort discovery
return nil
}
turnCount := 0
for _, f := range files {
data, rerr := os.ReadFile(f)
data, rerr := os.ReadFile(f) //nolint:gosec // G304: f is a discovered .jsonl in the user's own Claude transcript dir
if rerr != nil {
continue
}
31 unmodified lines
defer repo.Close()
res, err := importclaude.Run(ctx, repo, importclaude.Options{RepoRoot: repoRoot, Now: now})
if err != nil {
return err
return fmt.Errorf("import claude history: %w", err)
}
fmt.Fprintf(w, " ✓ Imported %d turn(s) of Claude history (local, read-only)\n", res.TurnsImported)
return nil
}
Mcmd/entire/cli/setup.go+4/-4
356 unmodified lines
357
358
359
360
360
361
362
363
364
364
365
366
367
368
369
370
370
371
372
373
374
375
356 unmodified lines
importsRefs := checkpoint.ImportsRefs();
imports, err := checkpoint.Open(ctx, repo, checkpoint.OpenOptions{Refs: &importsRefs})
if err != nil {
return base, nil //nolint:nilerr // imports are best-effort; v1 results stay valid
return base, nil
}
committed, err := imports.Persistent.List(ctx)
if err != nil {
return base, nil //nolint:nilerr // imports are best-effort
return base, nil
}
imp := checkpointInfosFromCommitted(committed)
for i := range imp {
imp[i].Imported = true
}
all := append(base, imp...)
all := make([]CheckpointInfo, 0, len(base)+len(imp))
all = append(all, base...)
all = append(all, imp...)
sort.Slice(all, func(i, j int) bool { return all[i].CreatedAt.After(all[j].CreatedAt) })
return all, nil
}
Mcmd/entire/cli/strategy/common.go+5/-3