Documentation
¶
Overview ¶
Package ticket provides core types and operations for ticket management.
Index ¶
- func AddDep(t *Ticket, depID string) error
- func AddLink(a, b *Ticket)
- func BlockingDeps(store Store, t *Ticket) []string
- func FindTicketsDir(startDir string) (string, bool)
- func FormatNamespacedID(project, ticketID string) string
- func GenerateID(title string) string
- func GenerateIDFrom(title string, t time.Time) string
- func IsBlocked(store Store, t *Ticket) bool
- func IsReady(store Store, t *Ticket) bool
- func IsReadyOpen(store Store, t *Ticket) bool
- func IsReservedKey(key string) bool
- func ParseNamespacedID(id string) (project, ticketID string)
- func PropagateStatusUp(store Store, child *Ticket) error
- func RemoveDep(t *Ticket, depID string)
- func RemoveLink(a, b *Ticket)
- func Serialize(t *Ticket) ([]byte, error)
- func SortByPriorityID(tickets []*Ticket)
- func SortByStatusPriorityID(tickets []*Ticket)
- func StatusOrder(s Status) int
- func TypeOrder(t TicketType) int
- func UpdateSection(body, heading, content string) string
- func ValidateExtraKey(key string) error
- func ValidateExtraValue(value string) error
- func ValidatePriority(p int) error
- func ValidateStateTransition(store Store, t *Ticket) error
- func ValidateStatus(s Status) error
- func ValidateType(t TicketType) error
- type ActionKind
- type Cycle
- type DepNode
- type FileStore
- func (s *FileStore) Create(t *Ticket) error
- func (s *FileStore) Delete(id string) error
- func (s *FileStore) EnsureDir() error
- func (s *FileStore) Get(id string) (*Ticket, error)
- func (s *FileStore) List() ([]*Ticket, error)
- func (s *FileStore) Resolve(id string) (string, error)
- func (s *FileStore) Update(t *Ticket) error
- type InboxItem
- type ListOptions
- type MoveResult
- type MultiStore
- type Note
- type ProjectSummary
- type Status
- type Store
- type Ticket
- type TicketType
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AddDep ¶
AddDep adds depID to the ticket's deps list. Returns error if it would create a self-dependency.
func BlockingDeps ¶
BlockingDeps returns the IDs of dependencies that are not done/closed.
func FindTicketsDir ¶
FindTicketsDir walks up from startDir looking for a .tickets/ subdirectory. startDir should be an absolute path. Returns the path and true if found, or empty string and false.
func FormatNamespacedID ¶
FormatNamespacedID combines a project name and ticket ID into a namespaced ID ("project/ticket-id"). If project is empty, returns the bare ticket ID.
func GenerateID ¶
GenerateID creates a ticket ID from the title and a hash. Format: slug-hash where slug is up to 3 meaningful words from the title, and hash is 4 hex chars derived from PID + timestamp.
func GenerateIDFrom ¶
GenerateIDFrom creates a ticket ID from explicit inputs (testable).
func IsReady ¶
IsReady returns true if the ticket is actionable: not terminal, not backlog, all deps done, and parent chain is active.
func IsReadyOpen ¶
IsReadyOpen is like IsReady but bypasses parent gating. Shows all unblocked non-terminal tickets regardless of epic status.
func IsReservedKey ¶
IsReservedKey reports whether key is a known frontmatter field name.
func ParseNamespacedID ¶
ParseNamespacedID splits a namespaced ticket ID ("project/ticket-id") into its project and ticket ID components. If the ID contains no slash, the project is empty and the full string is the ticket ID.
func PropagateStatusUp ¶
PropagateStatusUp bumps a parent epic's status in response to a child's status change. Rules:
- child → ready: parent epic backlog → ready (no-op if parent already ready, open, done, or closed).
- child → open: parent epic backlog or ready → open (no-op if parent already open, done, or closed).
- child → done: if every child of the parent is now terminal (done or closed) and the parent is not already terminal, parent → done.
Changes cascade up nested epic chains via store.Update, which calls this function again on each write.
func RemoveDep ¶
RemoveDep removes depID from the ticket's deps list. Matching is tolerant of namespace-prefix mismatches between the stored dep ID and the caller's argument.
func RemoveLink ¶
func RemoveLink(a, b *Ticket)
RemoveLink removes a symmetric link between two tickets. Matching is tolerant of namespace-prefix mismatches.
func SortByPriorityID ¶
func SortByPriorityID(tickets []*Ticket)
SortByPriorityID sorts by priority then ID. Used when grouping by status.
func SortByStatusPriorityID ¶
func SortByStatusPriorityID(tickets []*Ticket)
SortByStatusPriorityID sorts tickets by status order, then priority (asc), then ID. This is the default sort for ls/ready/blocked.
func StatusOrder ¶
StatusOrder returns the sort rank for a status (lower = earlier in display).
func TypeOrder ¶
func TypeOrder(t TicketType) int
TypeOrder returns the sort rank for a ticket type.
func UpdateSection ¶
UpdateSection replaces or inserts a markdown section in the body. If heading is empty, replaces the description (text before first structural heading).
func ValidateExtraKey ¶
ValidateExtraKey checks that key is a valid extra field name. Keys must be non-empty identifiers: letters, digits, hyphens, underscores.
func ValidateExtraValue ¶
ValidateExtraValue checks that value is safe for unquoted YAML serialization. Values allow printable ASCII: letters, digits, spaces, and common punctuation. YAML indicator characters (% ! & * @ ` : # [ ] { } | > ' ") and control characters are rejected to prevent parse failures in writeField output.
func ValidatePriority ¶
ValidatePriority returns an error if p is outside the range 0-4.
func ValidateStateTransition ¶
ValidateStateTransition rejects saving an epic as done while any of its children are still non-terminal (not done, not closed). Only checks when t.Type == TypeEpic and t.Status == StatusDone; otherwise it is a no-op.
The returned error names the offending children and suggests remediation, so CLI and MCP callers (and LLM agents) can self-correct.
func ValidateStatus ¶
ValidateStatus returns an error if s is not a recognized status.
func ValidateType ¶
func ValidateType(t TicketType) error
ValidateType returns an error if t is not a recognized ticket type.
Types ¶
type ActionKind ¶
type ActionKind string
ActionKind describes what type of action is needed on a ticket.
const ( ActionWork ActionKind = "work" ActionBlocked ActionKind = "blocked" ActionReady ActionKind = "ready" )
type Cycle ¶
type Cycle struct {
IDs []string
}
Cycle represents a dependency cycle as an ordered list of ticket IDs.
func FindCycles ¶
FindCycles detects dependency cycles among open (non-done/closed) tickets. Uses DFS with white(0)/gray(1)/black(2) coloring.
type FileStore ¶
type FileStore struct {
Dir string
}
FileStore provides filesystem-backed CRUD operations for tickets.
func NewFileStore ¶
NewFileStore creates a FileStore rooted at the given directory.
func (*FileStore) Create ¶
Create writes a new ticket to disk. The ticket must already have an ID. If the ID collides with an existing ticket, a new ID is generated and the ticket is retried (up to 5 attempts).
func (*FileStore) Resolve ¶
Resolve finds the full file path for an exact or partial ticket ID. Returns an error if the ID is ambiguous (multiple matches) or not found.
type InboxItem ¶
type InboxItem struct {
Ticket *Ticket
Action ActionKind
Detail string // Human-readable description of what's needed.
Since time.Time // When this action became pending.
}
InboxItem represents a ticket needing attention, with context about what's needed.
func NextAction ¶
NextAction computes the next action needed for a single ticket.
type ListOptions ¶
type ListOptions struct {
Status Status
Type TicketType
Priority int // -1 means no filter
Tag string
Parent string
}
ListOptions carries filter parameters for listing tickets.
func DefaultListOptions ¶
func DefaultListOptions() ListOptions
DefaultListOptions returns options with no filters applied.
type MoveResult ¶
MoveResult describes a single ticket move operation.
func MoveTicket ¶
func MoveTicket(src, dst *FileStore, id string, recursive bool) ([]MoveResult, error)
MoveTicket moves a single ticket from src store to dst store. The ticket is closed in src with a note, and created in dst with a new ID.
type MultiStore ¶
type MultiStore struct {
// contains filtered or unexported fields
}
MultiStore provides multi-project ticket storage by wrapping a FileStore per project subdirectory under a shared root. Ticket IDs are namespaced as "project/ticket-id" for cross-project disambiguation.
func NewMultiStore ¶
func NewMultiStore(rootDir string) *MultiStore
NewMultiStore creates a MultiStore rooted at the given directory. Each subdirectory of rootDir is treated as a project with its own tickets.
func (*MultiStore) Create ¶
func (m *MultiStore) Create(t *Ticket) error
Create writes a new ticket. The ticket ID must be namespaced ("project/id").
func (*MultiStore) Delete ¶
func (m *MultiStore) Delete(id string) error
Delete removes a ticket by namespaced or bare ID. Bare IDs are resolved across all projects.
func (*MultiStore) Get ¶
func (m *MultiStore) Get(id string) (*Ticket, error)
Get retrieves a ticket by namespaced ("project/id") or bare ID. Bare IDs are resolved across all projects; ambiguous matches return an error.
func (*MultiStore) List ¶
func (m *MultiStore) List() ([]*Ticket, error)
List returns all tickets from all projects with namespaced IDs.
func (*MultiStore) Update ¶
func (m *MultiStore) Update(t *Ticket) error
Update writes a ticket back to disk. Accepts namespaced or bare IDs. Bare IDs are resolved across all projects.
type ProjectSummary ¶
type ProjectSummary struct {
Epic *Ticket
Total int
StatusBreakdown map[Status]int
NextActions []InboxItem
CompletionPct float64
}
ProjectSummary aggregates progress for an epic/parent ticket.
func Projects ¶
func Projects(store Store) ([]ProjectSummary, error)
Projects returns active epics with their child progress, sorted by priority then completeness (least complete first).
type Store ¶
type Store interface {
Get(id string) (*Ticket, error)
List() ([]*Ticket, error)
Create(t *Ticket) error
Update(t *Ticket) error
Delete(id string) error
}
Store defines the interface for ticket storage backends.
func ResolveStoreForRepo ¶
ResolveStoreForRepo opens the ticket Store configured for the given repo directory. Reads ~/.ticket/config.yaml plus the shared <central_root>/config.yaml, resolves the project name via explicit path mapping, git remote, or directory basename, and returns a FileStore rooted at <central_root>/tickets/<name>.
Returns an error if no config is present or no project name resolves. The returned FileStore may wrap a directory that does not yet exist; List() on a missing directory returns (nil, nil) so callers can treat that as an empty project without special-casing.
type Ticket ¶
type Ticket struct {
ID string `yaml:"id"`
Status Status `yaml:"status"`
Type TicketType `yaml:"type"`
Priority int `yaml:"priority"`
Parent string `yaml:"parent,omitempty"`
Deps []string `yaml:"deps,flow"`
Links []string `yaml:"links,flow"`
Tags []string `yaml:"tags,omitempty,flow"`
ExternalRef string `yaml:"external-ref,omitempty"`
Branch string `yaml:"branch,omitempty"`
Created time.Time `yaml:"created"`
// Custom key/value pairs, handled manually in format.go.
Extra map[string]string `yaml:"-"`
// Parsed from markdown, not stored in frontmatter.
Title string `yaml:"-"`
Body string `yaml:"-"`
Notes []Note `yaml:"-"`
}
Ticket is the core data structure representing a work item. YAML frontmatter fields are mapped via yaml tags. Title and body content are parsed from the markdown outside the frontmatter.
func BlockedTickets ¶
BlockedTickets returns all non-terminal, non-backlog tickets with unresolved deps.
func Filter ¶
func Filter(tickets []*Ticket, opts ListOptions) []*Ticket
Filter returns tickets matching all non-zero fields in opts.
func ReadyTickets ¶
ReadyTickets returns all tickets that pass the IsReady check.
func ReadyTicketsOpen ¶
ReadyTicketsOpen returns all unblocked tickets, bypassing parent gating.
type TicketType ¶
type TicketType string
TicketType represents the kind of work a ticket tracks.
const ( TypeFeature TicketType = "feature" TypeBug TicketType = "bug" TypeEpic TicketType = "epic" )