Skip to content

refactor: collapse per-backend source wrappers into a shared store-backed source #92

Description

@sorafujitani

Context

internal/source/codex.go, internal/source/grok.go, and internal/source/opencode.go are near-identical wrappers whose only real job is delegating to the backend store and stamping s.Source = name. Adding a fifth backend means copy-pasting ~65 lines again.

Current state

See internal/source/codex.go (the whole file, 65 lines) — Scan/ScanFiltered call stamp(ss, name), FindByID/FindByLocator set s.Source = name, GrepKeys delegates directly, Messages delegates to the store. grok.go and opencode.go follow the same shape. The genuinely backend-specific parts are only:

  • the store constructor (codex.Open() etc.)
  • ResumeSpec (different binary/args per backend)
  • whether FindByLocator exists and how the locator decodes (file path for codex/grok; check what opencode does — it is SQLite-backed and may not implement LocatorFinder)
  • the store method names (MessagesForSession vs Messages — verify each)

Task

  1. Read all four wrapper files (claude.go, codex.go, grok.go, opencode.go) and list the exact method-set differences first. Do not assume the shapes match; verify.
  2. Define an unexported interface in internal/source, e.g.:
    type sessionStore interface {
        Scan() ([]*session.Session, error)
        ScanFiltered(allow map[string]struct{}) ([]*session.Session, error)
        FindByID(id string) (*session.Session, error)
        GrepKeys(query string, regex bool) (map[string]struct{}, error)
    }
  3. Implement one storeSource struct { name string; store sessionStore; resumeSpec func(*session.Session) (string, []string, error) } that covers the shared methods (including the stamp / s.Source = name logic in exactly one place).
  4. Keep locator support as a separate optional wrapper (e.g. fileLocatorSource embedding storeSource) so source.LocatorFinder is only satisfied by backends that actually support it — this mirrors the existing optional-interface design (internal/source/source.go:32).
  5. Where store method names differ (e.g. MessagesForSession vs Messages), prefer renaming the store method for uniformity over adding adapter closures, unless the rename ripples too far.
  6. claude.go may stay separate if it doesn't fit the store shape — do not force it.

Acceptance criteria

  • internal/source/{codex,grok,opencode}.go shrink to a constructor + ResumeSpec definition each (roughly ≤20 lines).
  • internal/source/source_test.go passes unchanged (it table-tests forName for all backends).
  • Source stamping behavior identical: ccsession list --json --source=all rows carry the correct source field (add/extend a test if none asserts this).
  • go test ./..., go vet ./..., golangci-lint run pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    impact: lowLower or indirect user-visible impactrefactorInternal restructuring with no behavior change

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions