todo

package
v0.0.13 Latest Latest
Warning

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

Go to latest
Published: May 8, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package todo implements a Jira-like todo tracker for jujutsu repositories.

Todos are stored in a special orphan change (parented at root()) that is bookmarked as "incr/tasks". This allows todos to sync across workspaces without polluting the code history.

The public API mirrors the CLI commands:

  • Create, Update, Start, Close, Finish, Reopen for todo lifecycle
  • Show, List, Ready for querying
  • DepAdd, DepTree for dependency management

Index

Constants

View Source
const (
	// BookmarkName is the jj bookmark used to identify the todo store change.
	BookmarkName = "incr/tasks"

	// TodosFile is the name of the JSONL file containing todos.
	TodosFile = "todos.jsonl"

	// DependenciesFile is the name of the JSONL file containing dependencies.
	DependenciesFile = "dependencies.jsonl"
)
View Source
const (
	PriorityCritical = 0
	PriorityHigh     = 1
	PriorityMedium   = 2 // default
	PriorityLow      = 3
	PriorityBacklog  = 4

	PriorityMin = 0
	PriorityMax = 4
)

Priority constants for todos.

View Source
const MaxTitleLength = 500

MaxTitleLength is the maximum allowed length for a todo title.

Variables

View Source
var (
	// ErrEmptyTitle is returned when a todo title is empty.
	ErrEmptyTitle = errors.New("title cannot be empty")

	// ErrTitleTooLong is returned when a todo title exceeds MaxTitleLength.
	ErrTitleTooLong = errors.New("title exceeds maximum length")

	// ErrInvalidStatus is returned when an invalid status is provided.
	ErrInvalidStatus = errors.New("invalid status")

	// ErrInvalidPriority is returned when priority is outside valid range.
	ErrInvalidPriority = errors.New("priority must be between 0 and 4")

	// ErrInvalidType is returned when an invalid todo type is provided.
	ErrInvalidType = errors.New("invalid todo type")

	// ErrTodoNotFound is returned when a todo with the given ID doesn't exist.
	ErrTodoNotFound = errors.New("todo not found")

	// ErrAmbiguousTodoIDPrefix is returned when an ID prefix matches multiple todos.
	ErrAmbiguousTodoIDPrefix = errors.New("ambiguous todo ID prefix")

	// ErrSelfDependency is returned when trying to create a dependency on itself.
	ErrSelfDependency = errors.New("todo cannot depend on itself")

	// ErrEmptyDependencyTodoID is returned when a dependency lacks a todo ID.
	ErrEmptyDependencyTodoID = errors.New("todo_id cannot be empty")

	// ErrEmptyDependencyDependsOnID is returned when a dependency lacks a depends-on ID.
	ErrEmptyDependencyDependsOnID = errors.New("depends_on_id cannot be empty")

	// ErrDuplicateDependency is returned when the dependency already exists.
	ErrDuplicateDependency = errors.New("dependency already exists")

	// ErrNoTodoStore is returned when the todo store bookmark doesn't exist.
	ErrNoTodoStore = errors.New("no todo store found (bookmark incr/tasks does not exist)")

	// ErrReadOnlyStore is returned when attempting to write using a read-only store.
	ErrReadOnlyStore = errors.New("todo store opened read-only")

	// ErrClosedTodoMissingClosedAt is returned when a closed or done todo has no closed_at timestamp.
	ErrClosedTodoMissingClosedAt = errors.New("closed or done todo must have closed_at timestamp")

	// ErrNotClosedTodoHasClosedAt is returned when a non-closed todo has a closed_at timestamp.
	ErrNotClosedTodoHasClosedAt = errors.New("non-closed todo cannot have closed_at timestamp")

	// ErrDeleteReasonRequiresDeletedAt is returned when a delete reason is provided without deleted_at.
	ErrDeleteReasonRequiresDeletedAt = errors.New("delete reason requires deleted_at timestamp")

	// ErrTombstoneMissingDeletedAt is returned when a tombstone todo has no deleted_at timestamp.
	ErrTombstoneMissingDeletedAt = errors.New("tombstone todo must have deleted_at timestamp")

	// ErrDeletedAtRequiresTombstoneStatus is returned when deleted_at is set without tombstone status.
	ErrDeletedAtRequiresTombstoneStatus = errors.New("deleted_at requires tombstone status")

	// ErrDeleteReasonRequiresTombstoneStatus is returned when delete reason is set without tombstone status.
	ErrDeleteReasonRequiresTombstoneStatus = errors.New("delete reason requires tombstone status")

	// ErrTombstoneHasClosedAt is returned when a tombstone todo has a closed_at timestamp.
	ErrTombstoneHasClosedAt = errors.New("tombstone todo cannot have closed_at timestamp")

	// ErrStartedAtRequiresActiveStatus is returned when started_at is set for a non-active todo.
	ErrStartedAtRequiresActiveStatus = errors.New("started_at requires in_progress, queued_for_merge, merging, merge_failed, or done status")

	// ErrCompletedAtRequiresDoneStatus is returned when completed_at is set for a non-complete todo.
	ErrCompletedAtRequiresDoneStatus = errors.New("completed_at requires queued_for_merge, merging, merge_failed, or done status")

	// ErrMergeStatusMissingCompletedAt is returned when a merge status is missing completed_at.
	ErrMergeStatusMissingCompletedAt = errors.New("merge statuses require completed_at")

	// ErrJobIDRequiresMergeStatus is returned when job_id is set for a non-merge todo.
	ErrJobIDRequiresMergeStatus = errors.New("job_id requires queued_for_merge, merging, merge_failed, or done status")
)

Functions

func AgeData

func AgeData(item Todo, now time.Time) (time.Duration, bool)

AgeData computes the display age and whether timing data exists.

func DurationData

func DurationData(item Todo, now time.Time) (time.Duration, bool)

DurationData computes the display duration and whether timing data exists.

func GenerateID

func GenerateID(title string, timestamp time.Time) string

GenerateID creates a unique 8-character alphanumeric ID from a title and timestamp. The ID is derived from SHA-256 hash of the title concatenated with the timestamp.

func PriorityName

func PriorityName(p int) string

PriorityName returns a human-readable name for the priority level.

func TodoTypeRank

func TodoTypeRank(t TodoType) int

TodoTypeRank returns the sort rank for a todo type.

func UpdatedData

func UpdatedData(item Todo, now time.Time) (time.Duration, bool)

UpdatedData computes the display updated age and whether timing data exists.

func ValidateDependency

func ValidateDependency(d *Dependency) error

ValidateDependency checks if a dependency is valid.

func ValidatePriority

func ValidatePriority(priority int) error

ValidatePriority checks if the priority is valid.

func ValidateTitle

func ValidateTitle(title string) error

ValidateTitle checks if the title is valid.

func ValidateTodo

func ValidateTodo(t *Todo) error

ValidateTodo checks if a todo struct is valid.

Types

type CreateOptions

type CreateOptions struct {
	// Status is the todo status. Defaults to StatusOpen.
	Status Status

	// Type is the todo type (task, bug, feature, design). Defaults to TypeTask.
	Type TodoType

	// Priority is the importance level (0-4). Defaults to PriorityMedium (2) when nil.
	Priority *int

	// Description provides additional context.
	Description string

	// ImplementationModel selects the model for implementing.
	ImplementationModel string

	// CodeReviewModel selects the model for commit review.
	CodeReviewModel string

	// ProjectReviewModel selects the model for project review.
	ProjectReviewModel string

	// Dependencies is a list of dependency IDs.
	Dependencies []string
}

CreateOptions configures a new todo.

type DepTreeNode

type DepTreeNode struct {
	// Todo is the todo at this node.
	Todo *Todo

	// Children are the todos that this todo depends on.
	Children []*DepTreeNode
}

DepTreeNode represents a node in a dependency tree.

type Dependency

type Dependency struct {
	// TodoID is the todo that has the dependency.
	TodoID string `json:"todo_id"`

	// DependsOnID is the todo that TodoID depends on.
	DependsOnID string `json:"depends_on_id"`

	// CreatedAt is when the dependency was created.
	CreatedAt time.Time `json:"created_at"`
}

Dependency represents a relationship between two todos.

type IDIndex

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

IDIndex indexes todo IDs for prefix matching and display.

func NewIDIndex

func NewIDIndex(todos []Todo) IDIndex

NewIDIndex builds an IDIndex from a slice of todos.

func (IDIndex) PrefixLengths

func (index IDIndex) PrefixLengths() map[string]int

PrefixLengths returns the shortest unique prefix length for each ID.

func (IDIndex) Resolve

func (index IDIndex) Resolve(prefix string) (string, error)

Resolve returns the full todo ID for a prefix.

type ListFilter

type ListFilter struct {
	// Status filters by exact status match.
	Status *Status

	// Priority filters by exact priority match.
	Priority *int

	// Type filters by exact type match.
	Type *TodoType

	// IDs filters to specific IDs.
	IDs []string

	// TitleSubstring filters to todos with this substring in the title.
	TitleSubstring string

	// DescriptionSubstring filters to todos with this substring in the description.
	DescriptionSubstring string

	// IncludeTombstones includes soft-deleted todos. Default is false.
	IncludeTombstones bool
}

ListFilter configures which todos to return.

type OpenOptions

type OpenOptions struct {
	// Prompter is used for user confirmation. If nil, StdioPrompter is used.
	Prompter Prompter

	// CreateIfMissing creates the todo store if it doesn't exist.
	// If false and the store doesn't exist, ErrNoTodoStore is returned.
	CreateIfMissing bool

	// PromptToCreate prompts the user before creating a new store.
	// Only used when CreateIfMissing is true.
	PromptToCreate bool

	// Purpose describes why the store workspace is acquired.
	// If empty, a default purpose is used.
	Purpose string

	// ReadOnly opens the store without acquiring a workspace.
	// Read-only mode cannot create missing stores.
	ReadOnly bool
}

OpenOptions configures how the store is opened.

type Prompter

type Prompter interface {
	// Confirm asks the user a yes/no question and returns true if they say yes.
	Confirm(message string) (bool, error)
}

Prompter is used to ask the user for confirmation.

type Snapshotter

type Snapshotter interface {
	Snapshot(workspacePath string) error
}

Snapshotter records workspace changes.

type Status

type Status string

Status represents the state of a todo.

const (
	// StatusOpen indicates the todo is ready to be worked on.
	StatusOpen Status = "open"

	// StatusProposed indicates the todo is awaiting review before starting.
	StatusProposed Status = "proposed"

	// StatusQueued indicates the todo is queued for batch processing.
	// Used by `job do` when multiple todos are specified to show that work
	// on this todo is scheduled. The CLI is responsible for cleanup:
	// queued todos must be reset to open if the batch exits before processing them.
	StatusQueued Status = "queued"

	// StatusInProgress indicates the todo is currently being worked on.
	StatusInProgress Status = "in_progress"

	// StatusQueuedForMerge indicates implementation finished and the todo is queued for merging.
	StatusQueuedForMerge Status = "queued_for_merge"

	// StatusMerging indicates the todo is being merged onto the target bookmark.
	StatusMerging Status = "merging"

	// StatusMergeFailed indicates the merge failed and needs manual intervention.
	StatusMergeFailed Status = "merge_failed"

	// StatusClosed indicates the todo has been completed.
	StatusClosed Status = "closed"

	// StatusDone indicates the todo is finished without closing it.
	StatusDone Status = "done"

	// StatusWaiting indicates the todo is blocked on external factors.
	// Unlike dependency blocking (for internal task ordering), waiting is for
	// external factors like upstream PRs, API availability, etc. The reason
	// for waiting lives in the description field.
	StatusWaiting Status = "waiting"

	// StatusStuck indicates the todo has been marked as stuck.
	// Stuck todos are not considered ready and indicate that automated work
	// should not continue without human intervention.
	StatusStuck Status = "stuck"

	// StatusTombstone indicates the todo has been soft-deleted.
	StatusTombstone Status = "tombstone"
)

func ValidStatuses

func ValidStatuses() []Status

ValidStatuses returns all valid status values.

func (Status) IsResolved

func (s Status) IsResolved() bool

IsResolved returns true when a status is considered resolved for dependencies.

func (Status) IsValid

func (s Status) IsValid() bool

IsValid returns true if the status is a known valid value.

type StdioPrompter

type StdioPrompter struct{}

StdioPrompter implements Prompter using stdin for input and stderr for output.

func (StdioPrompter) Confirm

func (p StdioPrompter) Confirm(message string) (bool, error)

Confirm asks the user a yes/no question. Writes prompts to stderr for consistency with error messages in interactive error-recovery scenarios; reads responses from stdin via fmt.Scanln.

type Store

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

Store provides access to the todo data for a jujutsu repository. It manages workspace acquisition and file locking for concurrent access.

func Open

func Open(repoPath string, opts OpenOptions) (*Store, error)

Open opens the todo store for the repository at repoPath. If the incr/tasks bookmark doesn't exist and PromptToCreate is true, the user will be prompted to create it.

func (*Store) Close

func (s *Store) Close(ids []string) ([]Todo, error)

Close closes one or more todos.

func (*Store) Create

func (s *Store) Create(title string, opts CreateOptions) (*Todo, error)

Create creates a new todo with the given title.

func (*Store) Delete

func (s *Store) Delete(ids []string, reason string) ([]Todo, error)

Delete tombstones one or more todos with an optional reason.

func (*Store) DepAdd

func (s *Store) DepAdd(todoID, dependsOnID string) (*Dependency, error)

DepAdd adds a dependency between two todos.

func (*Store) DepTree

func (s *Store) DepTree(id string) (*DepTreeNode, error)

DepTree returns the dependency tree for a todo.

func (*Store) Finish

func (s *Store) Finish(ids []string) ([]Todo, error)

Finish marks one or more todos as done.

func (*Store) IDIndex

func (s *Store) IDIndex() (IDIndex, error)

IDIndex returns an index of all todo IDs in the store.

func (*Store) List

func (s *Store) List(filter ListFilter) ([]Todo, error)

List returns todos matching the filter.

func (*Store) ListWithIndex

func (s *Store) ListWithIndex(filter ListFilter) ([]Todo, IDIndex, error)

ListWithIndex returns todos matching the filter plus a full ID index.

func (*Store) Merge

func (s *Store) Merge(ids []string) ([]Todo, error)

Merge marks todos as actively merging.

func (*Store) MergeFailed

func (s *Store) MergeFailed(ids []string) ([]Todo, error)

MergeFailed marks todos as failed merges.

func (*Store) Queue

func (s *Store) Queue(ids []string) ([]Todo, error)

Queue marks one or more todos as queued for batch processing.

func (*Store) QueueForMerge

func (s *Store) QueueForMerge(ids []string, jobID string) ([]Todo, error)

QueueForMerge marks todos as ready to merge and stores the job ID.

func (*Store) Ready

func (s *Store) Ready(limit int) ([]Todo, error)

Ready returns open todos with no unresolved blockers, sorted by priority.

func (*Store) ReadyWithIndex

func (s *Store) ReadyWithIndex(limit int) ([]Todo, IDIndex, error)

ReadyWithIndex returns ready todos plus a full ID index.

func (*Store) Release

func (s *Store) Release() error

Release releases the workspace back to the pool. This should be called when done using the store.

func (*Store) Reopen

func (s *Store) Reopen(ids []string) ([]Todo, error)

Reopen reopens one or more closed todos.

func (*Store) Show

func (s *Store) Show(ids []string) ([]Todo, error)

Show returns the full details of one or more todos.

func (*Store) Start

func (s *Store) Start(ids []string) ([]Todo, error)

Start marks one or more todos as in progress.

func (*Store) Update

func (s *Store) Update(ids []string, opts UpdateOptions) ([]Todo, error)

Update updates one or more todos with the given options. Returns the updated todos.

type Todo

type Todo struct {
	// ID is a unique identifier (8-char alphanumeric, derived from initial title + timestamp).
	ID string `json:"id"`

	// Title is the short summary of the todo (max 500 chars).
	Title string `json:"title"`

	// Description provides additional context about the todo.
	Description string `json:"description"`

	// Status is the current state of the todo.
	Status Status `json:"status"`

	// Priority is the importance level (0=critical, 4=backlog).
	Priority int `json:"priority"`

	// Type categorizes the todo (task, bug, feature, design).
	Type TodoType `json:"type"`

	// ImplementationModel selects the model for implementing this todo.
	ImplementationModel string `json:"implementation_model,omitempty"`

	// CodeReviewModel selects the model for commit review on this todo.
	CodeReviewModel string `json:"code_review_model,omitempty"`

	// ProjectReviewModel selects the model for final project review on this todo.
	ProjectReviewModel string `json:"project_review_model,omitempty"`

	// CreatedAt is when the todo was created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is when the todo was last modified.
	UpdatedAt time.Time `json:"updated_at"`

	// ClosedAt is when the todo was closed or marked done (nil if not closed/done).
	ClosedAt *time.Time `json:"closed_at,omitempty"`

	// StartedAt is when the todo entered in_progress (nil when not tracking).
	StartedAt *time.Time `json:"started_at,omitempty"`

	// CompletedAt is when the todo completed (nil when not completed).
	CompletedAt *time.Time `json:"completed_at,omitempty"`

	// JobID is the ID of the job that produced the mergeable change.
	JobID string `json:"job_id,omitempty"`

	// DeletedAt is when the todo was soft-deleted (nil if not deleted).
	DeletedAt *time.Time `json:"deleted_at,omitempty"`

	// DeleteReason explains why the todo was deleted.
	DeleteReason string `json:"delete_reason,omitempty"`

	// Source tracks the origin of the todo.
	// Empty or omitted means user-created. Values like "habit:<name>" indicate
	// the todo was created by running a habit.
	Source string `json:"source,omitempty"`
}

Todo represents a single task.

type TodoType

type TodoType string

TodoType represents the category of a todo.

const (
	// TypeTask is a general task (default).
	TypeTask TodoType = "task"

	// TypeBug is a bug fix.
	TypeBug TodoType = "bug"

	// TypeFeature is a new feature.
	TypeFeature TodoType = "feature"

	// TypeDesign is a design or specification task requiring interactive work.
	TypeDesign TodoType = "design"
)

func ValidTodoTypes

func ValidTodoTypes() []TodoType

ValidTodoTypes returns all valid todo type values.

func (TodoType) IsInteractive

func (t TodoType) IsInteractive() bool

IsInteractive returns true for todo types that require interactive sessions. Design todos produce specifications and require user collaboration.

func (TodoType) IsValid

func (t TodoType) IsValid() bool

IsValid returns true if the type is a known valid value.

type UpdateOptions

type UpdateOptions struct {
	Title               *string
	Description         *string
	Status              *Status
	Priority            *int
	Type                *TodoType
	ImplementationModel *string
	CodeReviewModel     *string
	ProjectReviewModel  *string
	DeletedAt           *time.Time
	DeleteReason        *string
	Source              *string
	StartedAt           *time.Time
	CompletedAt         *time.Time
	JobID               *string
}

UpdateOptions configures fields to update on todos. Nil pointers mean "don't update this field".

Jump to

Keyboard shortcuts

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