feat(import): discover repo transcripts with one-month lookback · Entire

feat(import): discover repo transcripts with one-month lookback

b3021b4→main·

computermode·3w ago·2 files·+114 added/-0 removed

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Sessions

487abe46ddadView transcript

[?
Implement Claude History Import FeatureClaude Code·Opus 4.8·3 steps](/content/gh/entireio/cli/session/897809d4-ecab-4dc6-aa21-97661cb62607#timeline-487abe46ddad/index.html)

Changes

2

package importclaude

import (
    "fmt"
    "os"
    "path/filepath"
    "slices"
    "strings"
    "time"

"github.com/entireio/cli/cmd/entire/cli/agent/claudecode"
)

// LookbackDays bounds how far back import reaches. Fixed this pass (no --since).
const LookbackDays = 30

// DiscoverSessions returns absolute paths of Claude transcript files for the
// repo modified within the lookback window. overridePath replaces the default
// ~/.claude/projects/<slug> dir. sessionFilter, when non-empty, keeps only
// files whose stem (name without .jsonl) matches one of the entries.
func DiscoverSessions(repoRoot, overridePath string, now time.Time, sessionFilter []string) ([]string, error) {
    dir := overridePath
    if dir == "" {
        ag := &claudecode.ClaudeCodeAgent{}
        d, err := ag.GetSessionDir(repoRoot)
        if err != nil {
            return nil, fmt.Errorf("resolve claude session dir: %w", err)
        }
        dir = d
    }
    entries, err := os.ReadDir(dir)
    if err != nil {
        if os.IsNotExist(err) {
            return nil, nil // no transcripts for this repo
        }
        return nil, fmt.Errorf("read claude session dir: %w", err)
    }
    cutoff := now.AddDate(0, 0, -LookbackDays)
    var out []string
    for _, e := range entries {
        if e.IsDir() || !strings.HasSuffix(e.Name(), ".jsonl") {
            continue
        }
        stem := strings.TrimSuffix(e.Name(), ".jsonl")
        if len(sessionFilter) > 0 && !slices.Contains(sessionFilter, stem) {
            continue
        }
        info, err := e.Info()
        if err != nil {
            continue
        }
        if info.ModTime().Before(cutoff) {
            continue
        }
        out = append(out, filepath.Join(dir, e.Name()))
    }
    slices.Sort(out)
    return out, nil
}

Acmd/entire/cli/importclaude/discover.go+59

package importclaude

import (
    "os"
    "path/filepath"
    "testing"
    "time"
)

func TestDiscoverSessions_LookbackAndFilter(t *testing.T) {
    t.Parallel()
    dir := t.TempDir()
    now := time.Date(2026, 6, 25, 12, 0, 0, 0, time.UTC)
    writeAged := func(name string, age time.Duration) {
        p := filepath.Join(dir, name)
        if err := os.WriteFile(p, []byte("{}\n"), 0o644); err != nil {
            t.Fatal(err)
        }
        mt := now.Add(-age)
        if err := os.Chtimes(p, mt, mt); err != nil {
            t.Fatal(err)
        }
    }
    writeAged("recent.jsonl", 5*24*time.Hour)
    writeAged("old.jsonl", 60*24*time.Hour)
    writeAged("skip.txt", 1*time.Hour)

got, err := DiscoverSessions("", dir, now, nil)
    if err != nil {
        t.Fatal(err)
    }
    if len(got) != 1 || filepath.Base(got[0]) != "recent.jsonl" {
        t.Fatalf("lookback filter wrong: %v", got)
    }

writeAged("abc123.jsonl", 1*24*time.Hour)
    got, err = DiscoverSessions("", dir, now, []string{"abc123"})
    if err != nil {
        t.Fatal(err)
    }
    if len(got) != 1 || filepath.Base(got[0]) != "abc123.jsonl" {
        t.Fatalf("session filter wrong: %v", got)
    }
}

func TestDiscoverSessions_MissingDirIsEmpty(t *testing.T) {
    t.Parallel()
    got, err := DiscoverSessions("", filepath.Join(t.TempDir(), "does-not-exist"), time.Now(), nil)
    if err != nil {
        t.Fatalf("missing dir should not error: %v", err)
    }
    if len(got) != 0 {
        t.Fatalf("expected empty, got %v", got)
    }
}

Acmd/entire/cli/importclaude/discover_test.go+55