Documentation
¶
Overview ¶
Package clonefind finds the local clones of GitHub repositories without asking: in the folders coding agents worked in and in the usual code folders. It reads local files and runs read-only git commands; it makes no network request, and nothing it learns leaves the computer.
Index ¶
Constants ¶
const ( // SourceAgent is a folder a coding agent worked in, from a hook event or // a session store. SourceAgent = "agent" // SourceScan is a clone found only by walking a code folder. SourceScan = "scan" )
Where a candidate was found.
const ( DefaultMaxDepth = 4 DefaultMaxProbes = 2000 DefaultMaxWalked = 50000 DefaultProbeTimeout = 3 * time.Second )
Default limits; see Options.
const ( // Buffer is how many hook events wait for the service; the record drops // the ones a slow service misses and the service then scans again. Buffer = 64 // DefaultInterval is how often the service scans while a picked // repository has no clone. DefaultInterval = time.Hour )
Service timings and bounds.
Variables ¶
var DefaultFolders = []string{"code", "src", "dev", "projects", "workspace", "repos", "git", filepath.Join("go", "src", "github.com")}
DefaultFolders are the code folders, relative to the home directory, that a scan walks when they exist.
Functions ¶
func AgentDir ¶
func AgentDir(e hookevents.Event) (string, bool)
AgentDir is the directory an agent hook event worked in: the agent's own working directory, else the hook's.
func MatchEvent ¶
func MatchEvent(e hookevents.Event) bool
MatchEvent selects the hook events the service reads: those of coding agents.
Types ¶
type Attached ¶
type Attached struct {
Repository string `json:"repository"`
Path string `json:"path"`
At time.Time `json:"at"`
}
Attached is a clone the service registered by itself.
type Candidate ¶
type Candidate struct {
// Path is the working tree's top-level directory, and CommonDir the
// repository directory its linked worktrees share. Both have symlinks
// resolved, as the registry stores them.
Path string
CommonDir string
// LastActivity is when an agent last worked in the clone (or one of its
// worktrees); zero for a clone only a scan found.
LastActivity time.Time
Source string
}
Candidate is one local clone of a repository.
type Events ¶
type Events interface {
Recent(limit int) ([]hookevents.Record, error)
}
Events is the record of hook events.
type Found ¶
type Found struct {
ScannedAt *time.Time `json:"scanned_at"`
Running bool `json:"running"`
Repositories map[string][]FoundClone `json:"repositories"`
Attached []Attached `json:"attached"`
}
Found is the JSON of GET /api/clones/found.
type FoundClone ¶
type FoundClone struct {
Path string `json:"path"`
CommonDir string `json:"common_dir"`
LastActivity *time.Time `json:"last_activity"`
Source string `json:"source"`
}
FoundClone is one clone in Found.
type Options ¶
type Options struct {
// Home is the user's home directory.
Home string
// Events is the hook event record; nil reads none.
Events Events
// Stores locates the agent session stores. Empty directories default to
// the usual ones below Home.
Stores collector.StoreOptions
// Folders replaces DefaultFolders (relative to Home, or absolute).
Folders []string
// ExtraRoots are further folders to walk.
ExtraRoots []string
// GOOS is the operating system, runtime.GOOS by default. On darwin
// ~/Documents is a protected folder whose first read prompts the user, so
// it is walked only when IncludeDocuments is set; elsewhere it is walked
// like the other code folders.
GOOS string
IncludeDocuments bool
Now func() time.Time
// Probe replaces the git probe, for tests.
Probe Probe
// MaxDepth is how deep a walk goes below its folder (default
// DefaultMaxDepth), MaxProbes how many directories are probed with git
// (DefaultMaxProbes), MaxWalked how many directories a run walks in all
// folders together (DefaultMaxWalked) and ProbeTimeout the time each git command gets
// (DefaultProbeTimeout).
MaxDepth, MaxProbes, MaxWalked int
ProbeTimeout time.Duration
}
Options configure Find. Only Home is required.
type Registry ¶
type Registry interface {
Repositories(context.Context) ([]repos.Repository, error)
// PickedWithoutClones and CloneRows read the registry only, running no
// git; Repositories probes every clone.
PickedWithoutClones(context.Context) ([]string, error)
CloneRows(context.Context) ([]repos.Clone, error)
// AttachFound registers a clone for a repository that is still picked,
// without moving a clone registered to another one.
AttachFound(ctx context.Context, path, repository string) (repos.Clone, error)
}
Registry is the part of the repository registry the service uses.
type Result ¶
type Result struct {
ScannedAt time.Time
// Repositories maps a lowercase owner/name to its clones, best first.
Repositories map[string][]Candidate
}
Result is what one run found.
type Service ¶
type Service struct {
Registry Registry
Options Options
// Changed tells the app the registry changed.
Changed func()
Log *slog.Logger
// Interval overrides DefaultInterval.
Interval time.Duration
// contains filtered or unexported fields
}
Service keeps the last Result in memory, scans in the background, and registers the clone it finds of a picked repository that has none.
func (*Service) Handler ¶
Handler serves /api/clones/found. It has no authentication: the daemon mounts it behind its authenticated host and origin checks, because the paths it returns are local data for the app alone.
func (*Service) RefreshWithDocuments ¶
RefreshWithDocuments is Refresh for one scan that also walks ~/Documents where it is otherwise left out (macOS). The choice is not kept.
func (*Service) Run ¶
Run scans while a picked repository has no clone: at start, when events were missed, and every Interval. Nothing is searched for repositories that are not picked; Trigger scans when one is. It attaches the clone of a picked repository an agent event shows. It returns when ctx ends.