unfinished

package
v1.23.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package unfinished provides background scanning for git worktrees that have uncommitted changes, commits ahead of the default branch, or commits behind.

Index

Constants

View Source
const (
	// EventUnfinishedWorkUpdated is published when a worktree scan result changes.
	EventUnfinishedWorkUpdated events.EventType = "unfinished.work_updated"
	// EventUnfinishedWorkRemoved is published when a worktree is dismissed/snoozed/gone.
	EventUnfinishedWorkRemoved events.EventType = "unfinished.work_removed"
	// EventUnfinishedScanCompleted is published after each full scan pass.
	EventUnfinishedScanCompleted events.EventType = "unfinished.scan_completed"
)

Variables

This section is empty.

Functions

func ComputeDiffHash

func ComputeDiffHash(worktreePath string) (string, error)

ComputeDiffHash runs `git -C path diff HEAD` and SHA256-hashes the output.

func SortByLastModified

func SortByLastModified(results []ScanResult)

SortByLastModified sorts a slice of ScanResult descending by LastModified. Equal times are broken by RepoPath+Branch for stability.

Types

type ScanResult

type ScanResult struct {
	RepoPath     string
	Branch       string
	WorktreePath string
	RepoName     string
	DisplayPath  string

	HasUncommitted bool
	AheadCount     int
	BehindCount    int
	DefaultBranch  string

	ChangedFiles  int
	LinesAdded    int
	LinesRemoved  int
	AheadMessages []string

	LastModified time.Time
	ScanTime     time.Time

	Status   ScanResultStatus
	ErrorMsg string

	// SessionID is non-empty if an active stapler-squad session tracks this branch.
	SessionID string
}

ScanResult holds the complete unfinished-work state for a single git worktree.

func (ScanResult) IsUnfinished

func (r ScanResult) IsUnfinished() bool

IsUnfinished returns true when at least one unfinished-work criterion is met.

type ScanResultStatus

type ScanResultStatus int

ScanResultStatus describes the quality of a scan result.

const (
	ScanResultStatusOK         ScanResultStatus = 0
	ScanResultStatusTimeout    ScanResultStatus = 1
	ScanResultStatusPermission ScanResultStatus = 2
	ScanResultStatusError      ScanResultStatus = 3
)

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner is the central coordinator for the unfinished-work background scan.

func NewScanner

func NewScanner(eventBus *events.EventBus, stateStore *StateStore) *Scanner

NewScanner constructs a Scanner. Call Start(ctx) to begin background processing.

func (*Scanner) AddPinnedRepo

func (s *Scanner) AddPinnedRepo(repoPath string) error

AddPinnedRepo validates that path is a git repo, then adds it.

func (*Scanner) AddRepo

func (s *Scanner) AddRepo(repoPath string)

AddRepo adds a repo path to the scan set and immediately enqueues it.

func (*Scanner) EnqueueRepo

func (s *Scanner) EnqueueRepo(repoPath string)

EnqueueRepo queues a repo for scanning if it's not cached recently.

func (*Scanner) GetAllResults

func (s *Scanner) GetAllResults() []ScanResult

GetAllResults returns a snapshot of all stored scan results (excluding dismissed/snoozed).

func (*Scanner) GetResultByKey

func (s *Scanner) GetResultByKey(repoPath, branch string) (ScanResult, bool)

GetResultByKey returns a single stored result by (repoPath, branch).

func (*Scanner) InvalidateCache

func (s *Scanner) InvalidateCache(worktreePath string)

InvalidateCache invalidates the cache for a given worktree path.

func (*Scanner) RemovePinnedRepo

func (s *Scanner) RemovePinnedRepo(repoPath string)

RemovePinnedRepo removes a pinned repo from scanning.

func (*Scanner) RemoveRepo

func (s *Scanner) RemoveRepo(repoPath string)

RemoveRepo removes a repo from the scan set and purges its results.

func (*Scanner) RemoveResult

func (s *Scanner) RemoveResult(repoPath, branch string)

RemoveResult removes a result from the store (called after dismiss/snooze).

func (*Scanner) ScanDone

func (s *Scanner) ScanDone() <-chan time.Time

ScanDone returns a channel that receives the completion time of each full scan.

func (*Scanner) SetAutoSpider

func (s *Scanner) SetAutoSpider(enabled bool)

SetAutoSpider enables or disables auto-spider of session paths.

func (*Scanner) SetTickInterval

func (s *Scanner) SetTickInterval(d time.Duration)

SetTickInterval overrides the default 30-second scan tick (for tests).

func (*Scanner) Start

func (s *Scanner) Start(ctx context.Context)

Start launches the coordinator goroutine and 4 worker goroutines. All goroutines exit cleanly when ctx is cancelled.

func (*Scanner) TriggerScan

func (s *Scanner) TriggerScan()

TriggerScan signals the coordinator to run a full scan immediately.

type StateStore

type StateStore struct {
	// contains filtered or unexported fields
}

StateStore manages persistent state for the unfinished-work feature. All public methods are thread-safe.

func NewStateStore

func NewStateStore(path string) (*StateStore, error)

NewStateStore loads (or creates) the state file at the given path.

func (*StateStore) AutoSpiderEnabled

func (s *StateStore) AutoSpiderEnabled() bool

AutoSpiderEnabled returns whether auto-spider is enabled.

func (*StateStore) CacheSummary

func (s *StateStore) CacheSummary(repoPath, branch, diffHash, summary string) error

CacheSummary stores an AI summary in the cache.

func (*StateStore) Dismiss

func (s *StateStore) Dismiss(repoPath, branch string) error

Dismiss permanently hides (repoPath, branch) from unfinished-work results.

func (*StateStore) GetCachedSummary

func (s *StateStore) GetCachedSummary(repoPath, branch, diffHash string) (string, bool)

GetCachedSummary returns a cached AI summary for (repoPath, branch, diffHash). Returns ("", false) on cache miss or expiry.

func (*StateStore) IsDismissed

func (s *StateStore) IsDismissed(repoPath, branch string) bool

IsDismissed returns true when (repoPath, branch) has been permanently dismissed.

func (*StateStore) IsSnoozed

func (s *StateStore) IsSnoozed(repoPath, branch, currentHeadSHA string) bool

IsSnoozed returns true if the worktree is snoozed and the HEAD SHA has not changed. When currentSHA differs from the snooze-time SHA, the snooze is auto-cleared. Note: currentSHA here is the worktree path (we'll look up HEAD SHA on demand if needed). For simplicity, we accept the HEAD SHA directly.

func (*StateStore) Load

func (s *StateStore) Load() error

Load reads the state file from disk. Returns os.ErrNotExist if the file is missing.

func (*StateStore) PinnedRepos

func (s *StateStore) PinnedRepos() []string

PinnedRepos returns the configured pinned repos.

func (*StateStore) SetConfig

func (s *StateStore) SetConfig(autoSpider bool, watchDirs, pinnedRepos []string) error

SetConfig atomically replaces config fields and saves.

func (*StateStore) Snooze

func (s *StateStore) Snooze(repoPath, branch, headSHA string) error

Snooze hides (repoPath, branch) until its HEAD SHA changes.

func (*StateStore) Undismiss

func (s *StateStore) Undismiss(repoPath, branch string) error

Undismiss removes the dismiss record for (repoPath, branch).

func (*StateStore) Unsnooze

func (s *StateStore) Unsnooze(repoPath, branch string) error

Unsnooze removes the snooze record.

func (*StateStore) WatchDirs

func (s *StateStore) WatchDirs() []string

WatchDirs returns the configured watch directories.

type UnfinishedEvent

type UnfinishedEvent struct {
	events.Event
	ScanResult  ScanResult
	CompletedAt time.Time
}

UnfinishedEvent extends events.Event with extra fields for unfinished-work events. It reuses the existing events.Event type by embedding the ScanResult in Context.

type WatchDirWatcher

type WatchDirWatcher struct {
	// contains filtered or unexported fields
}

WatchDirWatcher discovers git repos under configured watch directories and triggers scans via fsnotify on .git/ changes.

func NewWatchDirWatcher

func NewWatchDirWatcher(scanner *Scanner, stateStore *StateStore) *WatchDirWatcher

NewWatchDirWatcher creates a WatchDirWatcher. It attempts to create an fsnotify watcher and falls back to polling if the system doesn't support it.

func (*WatchDirWatcher) AddPinnedRepo

func (w *WatchDirWatcher) AddPinnedRepo(repo string)

AddPinnedRepo adds a pinned repo and triggers an immediate scan.

func (*WatchDirWatcher) AddWatchDir

func (w *WatchDirWatcher) AddWatchDir(dir string)

AddWatchDir adds a new watch directory at runtime and walks it immediately.

func (*WatchDirWatcher) RemoveWatchDir

func (w *WatchDirWatcher) RemoveWatchDir(dir string)

RemoveWatchDir removes a watch directory (repos only removed if not covered by other sources).

func (*WatchDirWatcher) Start

func (w *WatchDirWatcher) Start(ctx context.Context)

Start begins watching all configured watch dirs and pinned repos. It performs an initial walk then starts the event loop.

type WorktreeInfo

type WorktreeInfo struct {
	Path       string
	HEAD       string
	Branch     string
	IsBare     bool
	IsDetached bool
	IsPrunable bool
	IsLocked   bool
}

WorktreeInfo is parsed from `git worktree list --porcelain`.

func ParseAllWorktrees

func ParseAllWorktrees(output string) []WorktreeInfo

ParseAllWorktrees parses `git worktree list --porcelain` output into WorktreeInfo slices. It does NOT filter—the caller decides what to skip.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL