issues

package
v1.0.56 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package issues provides a built-in issue tracking system for SpecLedger. Issues are stored as JSONL files per spec at specledger/<spec>/issues.jsonl.

Key features:

  • Globally unique SHA-256 based issue IDs (SL-xxxxxx format)
  • No daemon required - direct file I/O only
  • File locking for concurrent access
  • Migration support from Beads format
  • Dependency tracking with cycle detection
  • Definition of Done validation

Index

Constants

View Source
const DefaultSimilarityThreshold = 0.8

DefaultSimilarityThreshold is the default threshold for duplicate detection (80%)

Variables

View Source
var (
	ErrNotFeatureBranch = errors.New("not on a feature branch. Use --spec flag or checkout a ###-branch")
	ErrNoGitRepo        = errors.New("not in a git repository")
)

Context-related errors

View Source
var (
	ErrCyclicDependency   = errors.New("would create a circular dependency")
	ErrSelfDependency     = errors.New("cannot create dependency on self")
	ErrDependencyNotFound = errors.New("dependency target issue not found")
)

Dependency-related errors

View Source
var (
	ErrInvalidIDFormat = errors.New("issue ID must be in format SL-xxxxxx (6 hex characters)")
	ErrIDPrefix        = errors.New("issue ID must start with 'SL-'")
)

ID-related errors

View Source
var (
	ErrInvalidID          = errors.New("issue ID must match format SL-xxxxxx")
	ErrInvalidTitle       = errors.New("title is required and must be 1-200 characters")
	ErrInvalidStatus      = errors.New("status must be one of: open, in_progress, closed")
	ErrInvalidPriority    = errors.New("priority must be between 0 and 5")
	ErrInvalidIssueType   = errors.New("issue type must be one of: epic, feature, task, bug")
	ErrInvalidSpecContext = errors.New("spec context must match pattern ###-name")
)

Validation errors

View Source
var (
	ErrBeadsNotFound     = errors.New(".beads/issues.jsonl not found")
	ErrMigrationFailed   = errors.New("migration failed")
	ErrNoIssuesToMigrate = errors.New("no issues to migrate")
)

Migration-related errors

View Source
var (
	ErrIssueNotFound      = errors.New("issue not found")
	ErrIssueAlreadyExists = errors.New("issue already exists")
	ErrStoreLocked        = errors.New("store is locked by another process")
	ErrSpecDirNotFound    = errors.New("spec directory not found")
)

Store-related errors

View Source
var NowFunc = time.Now

NowFunc is a variable for testing purposes

Functions

func CalculateCollisionProbability

func CalculateCollisionProbability(n int) float64

CalculateCollisionProbability estimates the probability of at least one collision given n issues, using the birthday problem approximation. With 6 hex characters (16,777,216 possible values), this provides collision probability < 0.01% for up to 100,000 issues.

func CalculateSimilarity

func CalculateSimilarity(s1, s2 string) float64

CalculateSimilarity calculates the Levenshtein similarity ratio between two strings

func DetectCycles added in v1.0.22

func DetectCycles(trees []*DependencyTree) [][]string

DetectCycles detects cycles in the dependency graph and returns them

func DetectSpecContextFromPath

func DetectSpecContextFromPath(path string) (string, error)

DetectSpecContextFromPath attempts to detect spec context from the current directory

func FormatCycleWarning added in v1.0.22

func FormatCycleWarning(cycles [][]string) string

FormatCycleWarning formats a cycle warning message

func FormatDuplicateWarning

func FormatDuplicateWarning(duplicates []DuplicateResult) string

FormatDuplicateWarning formats a warning message for duplicate issues

func FormatNotFeatureBranchError

func FormatNotFeatureBranchError(currentBranch string) string

FormatNotFeatureBranchError returns a formatted error message with helpful context

func GenerateIssueID

func GenerateIssueID(specContext, title string, createdAt time.Time) string

GenerateIssueID creates a deterministic, globally unique issue ID using SHA-256 hash of (spec_context + title + created_at). The ID format is SL-<6-char-hex> where the hex is the first 6 characters of the SHA-256 digest.

func IsValidIssueID

func IsValidIssueID(id string) bool

IsValidIssueID checks if an ID is in valid format

func IsValidIssueType

func IsValidIssueType(t IssueType) bool

IsValidIssueType checks if an issue type is valid

func IsValidLinkType

func IsValidLinkType(t LinkType) bool

IsValidLinkType checks if a link type is valid

func IsValidStatus

func IsValidStatus(s IssueStatus) bool

IsValidStatus checks if a status is valid

func ParseIssueID

func ParseIssueID(id string) (string, error)

ParseIssueID validates an issue ID format and returns it if valid. Returns an error if the ID doesn't match the expected format. Note: Only lowercase hex characters are valid since GenerateIssueID always produces lowercase.

func ParseSpecFromBranch

func ParseSpecFromBranch(branchName string) (string, bool)

ParseSpecFromBranch extracts the spec context from a branch name Returns the spec context and true if valid, empty string and false otherwise

func ValidateSpecContext

func ValidateSpecContext(specContext string) error

ValidateSpecContext validates a spec context string

Types

type BeadsIssue

type BeadsIssue struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Description string   `json:"description,omitempty"`
	Status      string   `json:"status"`
	Priority    int      `json:"priority"`
	Type        string   `json:"type"`
	Labels      []string `json:"labels,omitempty"`
	CreatedAt   string   `json:"created_at"`
	UpdatedAt   string   `json:"updated_at"`
	ClosedAt    string   `json:"closed_at,omitempty"`
	Notes       string   `json:"notes,omitempty"`
	Design      string   `json:"design,omitempty"`
	Acceptance  string   `json:"acceptance_criteria,omitempty"`
	BlockedBy   []string `json:"blocked_by,omitempty"`
	Blocks      []string `json:"blocks,omitempty"`
	Assignee    string   `json:"assignee,omitempty"`
}

BeadsIssue represents the Beads JSONL format for issues

type BeadsMigration

type BeadsMigration struct {
	OriginalID string    `json:"original_id"`
	MigratedAt time.Time `json:"migrated_at"`
}

BeadsMigration contains metadata for issues migrated from Beads

type Blocker added in v1.0.22

type Blocker struct {
	ID     string      `json:"id"`
	Title  string      `json:"title"`
	Status IssueStatus `json:"status"`
}

Blocker represents a blocking issue with its key details

type CheckDuplicateResult

type CheckDuplicateResult struct {
	HasDuplicates bool
	Duplicates    []DuplicateResult
}

CheckDuplicateResult contains the result of a duplicate check

func CheckDuplicatesAcrossAllSpecs

func CheckDuplicatesAcrossAllSpecs(title string, threshold float64) (*CheckDuplicateResult, error)

CheckDuplicatesAcrossAllSpecs checks for duplicates across all specs

func CheckDuplicatesForCreate

func CheckDuplicatesForCreate(title, specContext string, threshold float64) (*CheckDuplicateResult, error)

CheckDuplicatesForCreate checks for duplicates when creating a new issue

type ChecklistItem

type ChecklistItem struct {
	Item       string     `json:"item"`
	Checked    bool       `json:"checked"`
	VerifiedAt *time.Time `json:"verified_at,omitempty"`
}

ChecklistItem represents a single item in a definition of done checklist

type ContextDetector

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

ContextDetector detects the current spec context from git branch

func NewContextDetector

func NewContextDetector(repoPath string) *ContextDetector

NewContextDetector creates a new context detector

func (*ContextDetector) DetectSpecContext

func (d *ContextDetector) DetectSpecContext() (string, error)

DetectSpecContext returns the current spec context from the git branch name

func (*ContextDetector) GetBranchName

func (d *ContextDetector) GetBranchName() (string, error)

GetBranchName returns the current git branch name

func (*ContextDetector) IsFeatureBranch

func (d *ContextDetector) IsFeatureBranch() (bool, error)

IsFeatureBranch checks if the current branch is a feature branch

type DefinitionOfDone

type DefinitionOfDone struct {
	Items []ChecklistItem `json:"items"`
}

DefinitionOfDone represents a checklist that must be completed before closing

func (*DefinitionOfDone) CheckItem

func (d *DefinitionOfDone) CheckItem(itemText string) bool

CheckItem marks an item as checked

func (*DefinitionOfDone) GetUncheckedItems

func (d *DefinitionOfDone) GetUncheckedItems() []string

GetUncheckedItems returns all unchecked items

func (*DefinitionOfDone) IsComplete

func (d *DefinitionOfDone) IsComplete() bool

IsComplete returns true if all checklist items are checked

func (*DefinitionOfDone) UncheckItem

func (d *DefinitionOfDone) UncheckItem(itemText string) bool

UncheckItem marks an item as unchecked

type DependencyTree

type DependencyTree struct {
	Issue     Issue
	BlockedBy []*DependencyTree
	Blocks    []*DependencyTree
	Children  []*DependencyTree // Parent-child hierarchy
}

DependencyTree represents the dependency tree for an issue

type DuplicateResult

type DuplicateResult struct {
	Issue      Issue
	Similarity float64
}

DuplicateResult contains information about a potential duplicate

func FindSimilarIssues

func FindSimilarIssues(title string, issues []Issue, threshold float64) []DuplicateResult

FindSimilarIssues finds issues with similar titles to the given title

func FindSimilarIssuesAcrossSpecs

func FindSimilarIssuesAcrossSpecs(title string, store *Store, threshold float64) ([]DuplicateResult, error)

FindSimilarIssuesAcrossSpecs finds similar issues across all specs

type Issue

type Issue struct {
	// Required fields
	ID          string      `json:"id"`
	Title       string      `json:"title"`
	Description string      `json:"description,omitempty"`
	Status      IssueStatus `json:"status"`
	Priority    int         `json:"priority"` // 0=highest, 5=lowest
	IssueType   IssueType   `json:"issue_type"`
	SpecContext string      `json:"spec_context"`
	CreatedAt   time.Time   `json:"created_at"`
	UpdatedAt   time.Time   `json:"updated_at"`

	// Optional fields
	ClosedAt           *time.Time        `json:"closed_at,omitempty"`
	DefinitionOfDone   *DefinitionOfDone `json:"definition_of_done,omitempty"`
	BlockedBy          []string          `json:"blocked_by,omitempty"` // Issue IDs
	Blocks             []string          `json:"blocks,omitempty"`     // Issue IDs
	Labels             []string          `json:"labels,omitempty"`
	Assignee           string            `json:"assignee,omitempty"`
	Notes              string            `json:"notes,omitempty"`
	Design             string            `json:"design,omitempty"`
	AcceptanceCriteria string            `json:"acceptance_criteria,omitempty"`
	ParentID           *string           `json:"parentId,omitempty"` // Parent issue ID

	// Migration metadata (optional, for Beads migration)
	BeadsMigration *BeadsMigration `json:"beads_migration,omitempty"`
}

Issue represents a tracking unit for work items

func GetIssueAcrossSpecs

func GetIssueAcrossSpecs(id, basePath string) (*Issue, string, error)

GetIssueAcrossSpecs searches for an issue across all specs

func ListAllSpecs

func ListAllSpecs(basePath string, filter ListFilter) ([]Issue, error)

ListAllSpecs lists issues across all spec directories

func NewIssue

func NewIssue(title, description, specContext string, issueType IssueType, priority int) *Issue

NewIssue creates a new issue with defaults

func (*Issue) GetBlockers added in v1.0.22

func (i *Issue) GetBlockers(allIssues map[string]*Issue) []Blocker

GetBlockers returns details about blocking issues for display purposes. Returns a slice of Blocker structs with ID, Title, and Status.

func (*Issue) IsReady added in v1.0.22

func (i *Issue) IsReady(allIssues map[string]*Issue) bool

IsReady returns true if the issue is ready to work on (not blocked by open dependencies). An issue is ready when: - Status is open or in_progress (not closed) - AND BlockedBy array is empty OR ALL issues in BlockedBy have status closed

func (*Issue) Validate

func (i *Issue) Validate() error

Validate validates all fields of an issue

type IssueStatus

type IssueStatus string

IssueStatus represents the current state of an issue

const (
	StatusOpen       IssueStatus = "open"
	StatusInProgress IssueStatus = "in_progress"
	StatusClosed     IssueStatus = "closed"
)

type IssueType

type IssueType string

IssueType represents the category of an issue

const (
	TypeEpic    IssueType = "epic"
	TypeFeature IssueType = "feature"
	TypeTask    IssueType = "task"
	TypeBug     IssueType = "bug"
)

type IssueUpdate

type IssueUpdate struct {
	Title              *string
	Description        *string
	Status             *IssueStatus
	Priority           *int
	IssueType          *IssueType
	Assignee           *string
	Notes              *string
	Design             *string
	AcceptanceCriteria *string
	Labels             *[]string
	AddLabels          []string
	RemoveLabels       []string
	BlockedBy          *[]string
	Blocks             *[]string
	DefinitionOfDone   *DefinitionOfDone
	CheckDoDItem       string  // Item to mark as checked
	UncheckDoDItem     string  // Item to mark as unchecked
	ParentID           *string // Set or clear parent
}

IssueUpdate represents partial updates to an issue

type LinkType

type LinkType string

LinkType represents the type of relationship between issues

const (
	LinkBlocks  LinkType = "blocks"  // A blocks B (A must complete before B)
	LinkRelated LinkType = "related" // A and B are related
)

type ListFilter

type ListFilter struct {
	Status      *IssueStatus
	IssueType   *IssueType
	Priority    *int
	Labels      []string
	SpecContext string // Empty = all specs
	All         bool   // Search across all specs
	Blocked     bool   // Only show blocked issues
	Orphaned    bool   // Only show non-epic issues without a parent
}

ListFilter represents filtering options for listing issues

type MigrationResult

type MigrationResult struct {
	TotalIssues      int
	MigratedIssues   int
	SkippedIssues    int
	SpecDistribution map[string]int // spec -> count
	UnmappedIssues   []BeadsIssue
	Errors           []error
	Warnings         []string
}

MigrationResult contains the results of a migration

type Migrator

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

Migrator handles migration from Beads to sl issue format

func NewMigrator

func NewMigrator(opts MigratorOptions) *Migrator

NewMigrator creates a new migrator

func (*Migrator) Cleanup

func (m *Migrator) Cleanup() error

Cleanup removes Beads dependencies after successful migration

func (*Migrator) GetIDMapping

func (m *Migrator) GetIDMapping() map[string]string

GetIDMapping returns the ID mapping from old Beads IDs to new SL IDs

func (*Migrator) Migrate

func (m *Migrator) Migrate() (*MigrationResult, error)

Migrate performs the full migration from Beads to sl issue format

type MigratorOptions

type MigratorOptions struct {
	BeadsPath    string // Path to .beads directory (default: ".beads")
	ArtifactPath string // Path to specledger directory (default: "specledger")
	DryRun       bool
	KeepBeads    bool
}

MigratorOptions contains options for the migrator

type ReadyIssue added in v1.0.22

type ReadyIssue struct {
	Issue     Issue     `json:"issue"`
	BlockedBy []Blocker `json:"blocked_by,omitempty"` // Empty for ready issues
}

ReadyIssue represents an issue that is ready to work on

func ListReadyAcrossSpecs added in v1.0.22

func ListReadyAcrossSpecs(basePath string, filter ListFilter) ([]ReadyIssue, error)

ListReadyAcrossSpecs returns ready issues across all spec directories.

type RepairResult

type RepairResult struct {
	ValidLines      int
	InvalidLines    int
	RecoveredIssues int
	SkippedLines    []SkippedLine
	BackupPath      string
}

RepairResult contains the result of repairing an issues file

func RepairIssuesFile

func RepairIssuesFile(path string) (*RepairResult, error)

RepairIssuesFile repairs a corrupted issues.jsonl file

type SkippedLine

type SkippedLine struct {
	LineNum int
	Reason  string
}

SkippedLine contains information about a skipped line during repair

type Store

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

Store manages JSONL file operations with file locking

func NewStore

func NewStore(opts StoreOptions) (*Store, error)

NewStore creates a new issue store for a specific spec context

func (*Store) AddDependency

func (s *Store) AddDependency(fromID, toID string, linkType LinkType) error

AddDependency creates a dependency link between two issues

func (*Store) Create

func (s *Store) Create(issue *Issue) error

Create creates a new issue in the store

func (*Store) Delete

func (s *Store) Delete(id string) error

Delete removes an issue from the store

func (*Store) DetectCycles

func (s *Store) DetectCycles() ([][]string, error)

DetectCycles checks for circular dependencies in the entire issue set

func (*Store) Get

func (s *Store) Get(id string) (*Issue, error)

Get retrieves an issue by ID

func (*Store) GetBlockedIssues

func (s *Store) GetBlockedIssues() ([]Issue, error)

GetBlockedIssues returns all issues that are currently blocked

func (*Store) GetBlockedIssuesWithBlockers added in v1.0.22

func (s *Store) GetBlockedIssuesWithBlockers() ([]ReadyIssue, error)

GetBlockedIssuesWithBlockers returns all issues that are currently blocked, along with their blocker details.

func (*Store) GetChildren added in v1.0.29

func (s *Store) GetChildren(parentID string) ([]Issue, error)

GetChildren returns all issues that have the given issue as parent Results are ordered by priority (descending), then by ID (ascending)

func (*Store) GetDependencyTree

func (s *Store) GetDependencyTree(id string) (*DependencyTree, error)

GetDependencyTree returns the full dependency tree for an issue

func (*Store) GetHierarchyForest added in v1.0.29

func (s *Store) GetHierarchyForest() ([]*DependencyTree, error)

GetHierarchyForest returns a forest of trees based on parent-child relationships. Root nodes are issues without a parent. Each tree includes all descendants.

func (*Store) List

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

List returns issues matching the filter

func (*Store) ListReady added in v1.0.22

func (s *Store) ListReady(filter ListFilter) ([]ReadyIssue, error)

ListReady returns all issues that are ready to work on (not blocked by open dependencies). Ready issues have status open or in_progress and all their blockers are closed.

func (*Store) Path

func (s *Store) Path() string

Path returns the path to the issues.jsonl file

func (*Store) RemoveDependency

func (s *Store) RemoveDependency(fromID, toID string, linkType LinkType) error

RemoveDependency removes a dependency link between two issues

func (*Store) Update

func (s *Store) Update(id string, update IssueUpdate) (*Issue, error)

Update updates an existing issue

func (*Store) WithLock

func (s *Store) WithLock(fn func() error) error

WithLock executes a function while holding the file lock

func (*Store) WithLockResult

func (s *Store) WithLockResult(fn func() (*Issue, error)) (*Issue, error)

WithLockResult executes a function while holding the file lock and returns a result

type StoreOptions

type StoreOptions struct {
	BasePath    string // Base path to specledger directory (default: "specledger")
	SpecContext string // Spec context (e.g., "010-my-feature"), empty for cross-spec mode
}

StoreOptions contains options for creating a new Store

type TreeRenderOptions added in v1.0.22

type TreeRenderOptions struct {
	MaxDepth     int  // Maximum depth to render (default: 10)
	ShowStatus   bool // Include status indicator (default: true)
	TitleWidth   int  // Max title width before truncation (default: 40)
	ShowSpec     bool // Show spec context for cross-spec trees (default: false)
	ShowType     bool // Show issue type (default: true)
	ShowPriority bool // Show priority (default: true)
	Color        bool // Use colors (default: true)
}

TreeRenderOptions configures the tree output format

func DefaultTreeRenderOptions added in v1.0.22

func DefaultTreeRenderOptions() TreeRenderOptions

DefaultTreeRenderOptions returns the default options

type TreeRenderer added in v1.0.22

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

TreeRenderer handles tree output formatting

func NewTreeRenderer added in v1.0.22

func NewTreeRenderer(opts TreeRenderOptions) *TreeRenderer

NewTreeRenderer creates a new tree renderer with the given options

func (*TreeRenderer) FormatIssueSimple added in v1.0.22

func (r *TreeRenderer) FormatIssueSimple(issue Issue) string

FormatIssueSimple is a public method to format an issue for display

func (*TreeRenderer) Render added in v1.0.22

func (r *TreeRenderer) Render(tree *DependencyTree) string

Render renders a single dependency tree

func (*TreeRenderer) RenderForest added in v1.0.22

func (r *TreeRenderer) RenderForest(trees []*DependencyTree) string

RenderForest renders multiple dependency trees

func (*TreeRenderer) RenderHierarchyForest added in v1.0.29

func (r *TreeRenderer) RenderHierarchyForest(rootLabel string, trees []*DependencyTree, totalIssues int) string

RenderHierarchyForest renders a forest of hierarchy trees (parent-child)

func (*TreeRenderer) RenderWithRoot added in v1.0.22

func (r *TreeRenderer) RenderWithRoot(rootLabel string, trees []*DependencyTree, totalIssues int) string

RenderWithRoot renders a tree with a root label

Jump to

Keyboard shortcuts

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