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

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