Documentation
¶
Index ¶
- func Apply(ticks []tick.Tick, f Filter) []tick.Tick
- func Blocked(candidates []tick.Tick, allTicks ...[]tick.Tick) []tick.Tick
- func CompletedProjectsNeedingCloseout(allTicks []tick.Tick) []tick.Tick
- func DescendantProgress(t tick.Tick, allTicks []tick.Tick) (closed, total int)
- func EpicsNeedingPlanning(candidates []tick.Tick, allTicks ...[]tick.Tick) []tick.Tick
- func EpicsNeedingPlanningWithMode(candidates []tick.Tick, autonomous bool, allTicks ...[]tick.Tick) []tick.Tick
- func IsContainer(t tick.Tick, index ChildIndex) bool
- func LoadTicksParallel(issuesDir string) ([]tick.Tick, error)
- func NeedsRebuild(indexPath, issuesDir string) (bool, error)
- func NextPlannableEpics(allTicks []tick.Tick) []tick.Tick
- func Ready(candidates []tick.Tick, allTicks ...[]tick.Tick) []tick.Tick
- func ReadyIncludeAwaiting(candidates []tick.Tick, allTicks ...[]tick.Tick) []tick.Tick
- func ReadyWithMode(candidates []tick.Tick, autonomous bool, allTicks ...[]tick.Tick) []tick.Tick
- func SaveIndex(path string, ticks []tick.Tick) error
- func SortByPriorityCreatedAt(ticks []tick.Tick)
- func SortBySoftOrderPriorityCreatedAt(ticks []tick.Tick, allTicks []tick.Tick)
- func SortByTargetDate(ticks []tick.Tick)
- type ChildIndex
- type Filter
- type Index
- type Roadmap
- type RoadmapEpic
- type RoleKind
- type SlipStatus
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Blocked ¶
Blocked returns ticks that are open or in_progress with open blockers. Missing blockers are treated as closed (not blocked) - this handles orphaned references when blockers are deleted. If allTicks is provided, it is used to look up blocker status (for when candidates is a filtered subset and blockers may be outside that subset).
func CompletedProjectsNeedingCloseout ¶ added in v0.18.0
CompletedProjectsNeedingCloseout returns every tick whose structural role is RoleProject, whose own status is NOT closed (open or in_progress), and whose leaf descendants are ALL closed (DescendantProgress: closed == total && total > 0).
This is the project checkpoint boundary from design §5 (recursive continuation engine): when every child epic and leaf inside a project is done but the project tick itself is still open, a close-out checkpoint is due before the orchestrator moves on to the next project-level unit.
allTicks must be the full tick universe — pass a filtered subset and role derivation will misclassify containers whose children are outside the subset.
func DescendantProgress ¶ added in v0.18.0
DescendantProgress computes a recursive progress rollup for t over the full tick set. It returns (closed, total) counts for the LEAF descendants of t — descendants with no children of their own, which are the actual units of work. Containers themselves are not counted.
Convention for the degenerate cases:
- If t is itself a leaf (no children), it is counted as its own unit: total=1, closed=1 if t.Status==StatusClosed, closed=0 otherwise.
- If t is a container with no leaf descendants (all descendants are containers, which would indicate a malformed graph), total=0, closed=0.
The function is cycle-safe: a visited set prevents infinite recursion if a parent loop exists in a malformed tick graph.
allTicks must be the full tick set (not a pre-filtered subset) for role derivation to be accurate. The function builds a ChildIndex and tick-by-ID map internally.
func EpicsNeedingPlanning ¶ added in v0.13.0
EpicsNeedingPlanning returns epics from candidates that have no children and are ready to be planned. An epic needs planning when ALL of the following hold:
- Type is TypeEpic and Status is StatusOpen (in_progress epics are being worked)
- Not blocked: every BlockedBy entry is closed or missing (missing = treated as closed)
- Not deferred (DeferUntil nil or in the past)
- Not awaiting human action (IsAwaitingHuman() is false)
- Has zero children in allTicks (no tick of any status has Parent == epic.ID)
Note: an epic whose children are all closed does NOT need planning — it needs closing. Zero-children is the test, not zero-open-children.
If allTicks is provided it is used to look up blocker status and child presence (for when candidates is a filtered subset and children/blockers may live outside it).
func EpicsNeedingPlanningWithMode ¶ added in v0.18.0
func EpicsNeedingPlanningWithMode(candidates []tick.Tick, autonomous bool, allTicks ...[]tick.Tick) []tick.Tick
EpicsNeedingPlanningWithMode is EpicsNeedingPlanning with an explicit autonomous-mode switch. When autonomous is false the result is identical to EpicsNeedingPlanning. When autonomous is true, an epic whose ONLY reason to be gated is awaiting: checkpoint (a project close-out boundary, design §5/§8) is no longer excluded — continuation flows through the project boundary. No other awaiting type is affected. See gatesHuman for the exact bypass scope.
func IsContainer ¶ added in v0.18.0
func IsContainer(t tick.Tick, index ChildIndex) bool
IsContainer reports whether t has at least one child in index — i.e. some tick points its `parent` at t. Containment is the structural fact underlying every non-leaf role; it is free and passive and implies nothing about execution (see design §1).
func LoadTicksParallel ¶
LoadTicksParallel loads all ticks from the issues directory with bounded concurrency.
func NeedsRebuild ¶
NeedsRebuild checks whether any issue file is newer than the index file.
func NextPlannableEpics ¶ added in v0.18.0
NextPlannableEpics returns the set of childless epics that are ready to be planned right now, sorted by soft-order then priority then created-at — identical semantics to the flat selection that cmd/tk/cmd/next.go performs via EpicsNeedingPlanning + SortBySoftOrderPriorityCreatedAt.
Project-awareness note: callers do NOT need to pre-filter by project or walk parent pointers before calling this function. Epic ordering across project boundaries is already encoded in the roadmap's BlockedBy/After edges: an epic in project B that must come after project A's epics will carry those as After (soft) or BlockedBy (hard) entries, and SortBySoftOrderPriorityCreatedAt / EpicsNeedingPlanning's blocker gate handle them correctly without any tree traversal. This function therefore surfaces the same feasible set as the flat tk-next path and packages it as a reusable entry point for the continuation engine (design §5).
allTicks must be the full tick universe — role derivation and blocker resolution both need the complete graph.
func Ready ¶
Ready returns open ticks that are not blocked by open blockers. Missing blockers are treated as closed (ready) - this handles orphaned references when blockers are deleted. Tasks awaiting human action are excluded (use ReadyIncludeAwaiting to include them). If allTicks is provided, it is used to look up blocker status (for when candidates is a filtered subset and blockers may be outside that subset).
func ReadyIncludeAwaiting ¶ added in v0.1.18
ReadyIncludeAwaiting is like Ready but includes tasks awaiting human action. Use this when displaying all ready tasks including those needing human review.
func ReadyWithMode ¶ added in v0.18.0
ReadyWithMode is Ready with an explicit autonomous-mode switch. When autonomous is false the result is identical to Ready. When autonomous is true, a tick whose ONLY reason to be gated is awaiting: checkpoint (a project close-out boundary, design §5/§8) is no longer excluded. No other awaiting type is affected. See gatesHuman for the exact bypass scope.
func SortByPriorityCreatedAt ¶
SortByPriorityCreatedAt sorts ticks by status (in_progress first), priority, created_at, then id.
func SortBySoftOrderPriorityCreatedAt ¶ added in v0.16.0
SortBySoftOrderPriorityCreatedAt sorts ticks like SortByPriorityCreatedAt, but with soft ordering applied: candidates that are soft-deferred (at least one after-target is still open) sort behind candidates whose after-targets are all closed. Soft order is a preference, never a gate — a soft-deferred tick is still returned when it is the only candidate; it merely yields to feasible-first work.
Key order: status (in_progress first, to resume incomplete work), then soft-deferred flag (false first), then priority ascending, created_at ascending, and id as the final tiebreaker.
allTicks is the full tick universe used to resolve after-target status. Missing targets are treated as closed (handles orphaned references), matching blocker semantics in ready.go.
func SortByTargetDate ¶ added in v0.18.0
SortByTargetDate sorts ticks ascending by target_date. Ticks with no target_date sort last. Within equal dates (including the group of undated ticks) the secondary key is priority then created_at then id, matching the convention from SortByPriorityCreatedAt.
Types ¶
type ChildIndex ¶ added in v0.18.0
ChildIndex maps a parent tick ID to the set of IDs of ticks that point their `parent` at it. It is the structural input the role helpers need: a tick is a container iff it appears as a key with at least one child.
Build it once with BuildChildIndex over the full tick set and reuse it across many Role/IsContainer calls.
func BuildChildIndex ¶ added in v0.18.0
func BuildChildIndex(allTicks []tick.Tick) ChildIndex
BuildChildIndex indexes every tick by its parent. A tick with an empty `parent` contributes no edge. The returned index is keyed by parent ID; the value is the list of that parent's direct child IDs (order follows allTicks).
Pass the full set of ticks: role derivation needs every descendant edge to decide container-ness, so a filtered subset can misclassify a parent.
type Filter ¶
type Filter struct {
Owner string
Status string
Priority *int
Type string
Label string
LabelAny []string
Parent string
TitleContains string
DescContains string
NotesContains string
// Awaiting filters by awaiting state. Use pointer to distinguish:
// - nil: no filter (show all)
// - pointer to empty string: show only ticks with awaiting=null (not awaiting human)
// - pointer to value: show only ticks with that specific awaiting value
Awaiting *string
// AwaitingAny filters to ticks matching any of the listed awaiting values.
// Treats Manual=true as awaiting="work" for backwards compatibility.
AwaitingAny []string
// Overdue, when true, retains only ticks whose derived slip status is
// SlipOverdue. Requires AllTicks and Now to be set for the Slip computation.
Overdue bool
// DueBefore retains only ticks with a target_date strictly before this day.
// Ticks with no target_date are excluded. Zero value means no filter.
DueBefore time.Time
// AllTicks is the full tick set required by Overdue (for DescendantProgress).
// Ignored when Overdue is false.
AllTicks []tick.Tick
// Now is the reference instant for Overdue. Callers should pass time.Now();
// tests pass a fixed value for determinism. Ignored when Overdue is false.
Now time.Time
}
Filter describes filtering criteria for ticks.
type Roadmap ¶ added in v0.13.0
type Roadmap struct {
Waves [][]RoadmapEpic `json:"waves"`
}
Roadmap is the result of epic-chain computation. Waves are ordered from earliest (wave 0 = no epic predecessors, hard or soft) to latest; within each wave epics are sorted by ID for deterministic output. Epics that are part of a cycle (rare) are placed in the final wave.
func ComputeRoadmap ¶ added in v0.13.0
ComputeRoadmap computes an epic-level dependency view over all ticks.
Only epics (Type == TypeEpic) appear as nodes. Layering edges are the union of BlockedBy (hard) and After (soft ordering) entries that point at another epic; task-level and missing targets are ignored (missing blockers are treated as closed, same semantics as ready.go). An ID appearing in both BlockedBy and After counts as a single edge.
Waves are computed with Kahn's topological layering: wave 0 contains epics with no epic predecessors still in the working set; each subsequent wave contains epics whose predecessors are all in earlier waves. Epics involved in cycles over the union graph (if any) are appended as a final wave.
Status is derived from hard (BlockedBy) edges only: an epic whose only open predecessors are After targets is never "queued" — soft ordering shifts wave placement but never feasibility.
type RoadmapEpic ¶ added in v0.13.0
type RoadmapEpic struct {
ID string `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
AwaitingType string `json:"awaiting_type,omitempty"`
// Role is the derived structural role of this node: "epic" for an
// orchestration unit, or "project" for a passive grouping/checkpoint
// container. It is left empty for a project-less roadmap so that the only
// shape that exists today serialises and renders byte-for-byte as before.
// Buckets are never roadmap nodes. Renderers use Role+Parent to nest epics
// under their parent projects (walk the tree).
Role string `json:"role,omitempty"`
// Parent is the ID of this node's enclosing project, or empty when the node
// is at the roadmap root. Only populated when a project is present, so a
// project-less roadmap leaves it empty and renders flat exactly as today.
Parent string `json:"parent,omitempty"`
// BlockedBy carries hard dependency edges (epic IDs only); these gate
// feasibility and feed the "queued" status. After carries soft ordering
// edges (epic IDs only); they influence wave placement but never status,
// so renderers can distinguish the two edge types.
BlockedBy []string `json:"blocked_by,omitempty"`
After []string `json:"after,omitempty"`
ChildrenTotal int `json:"children_total"`
ChildrenClosed int `json:"children_closed"`
}
RoadmapEpic is the consumer-facing view of one epic in the roadmap.
Status values (what a UI should show, not internal predicates):
- "done" — epic is closed.
- "active" — epic is in_progress, OR open with at least one open child, OR open with all children closed (needs closing — surfaces in tk graph).
- "ready" — epic is open, unblocked, and has zero children (needs planning; same predicate as EpicsNeedingPlanning, minus the awaiting/deferred checks since those become "gated").
- "queued" — epic is open but blocked by at least one open epic. Only hard (BlockedBy) edges count; soft (After) ordering never queues.
- "gated" — epic is open and IsAwaitingHuman(); takes priority over all other statuses. AwaitingType carries the badge value.
type RoleKind ¶ added in v0.18.0
type RoleKind string
RoleKind enumerates a tick's structural role in the generic tick -> epic -> project hierarchy. A role is *derived* from structure (does the tick have children, do its children have children) plus the single explicit `type: "epic"` marker; it is never stored. Use Role(t, index) to compute it.
See docs/design/big-picture-tracking-projects-and-hierarchy.md §1-§2 (the grouping-vs-orchestration principle and the role-derivation table).
const ( // RoleTick is a leaf (no children) that is not marked epic: atomic work. RoleTick RoleKind = "tick" // RoleEmptyEpic is a leaf marked type:epic: an epic awaiting planning. RoleEmptyEpic RoleKind = "empty_epic" // RoleEpic is a container marked type:epic: an orchestration unit whose // children are run as coordinated waves. RoleEpic RoleKind = "epic" // RoleBucket is a container NOT marked epic whose children are all leaves: // a passive grouping (e.g. unrelated bugs), never run as a unit. RoleBucket RoleKind = "bucket" // RoleProject is a container NOT marked epic with at least one child that is // itself a container: a grouping plus checkpoint boundary. RoleProject RoleKind = "project" )
The five derived roles. The derivation table:
has children? | epic marker? | children | role --------------|--------------|---------------------|------------- no | no | — | tick no | yes | — | empty epic yes | yes | any | epic yes | no | all leaves | bucket yes | no | includes a container| project
func Role ¶ added in v0.18.0
func Role(t tick.Tick, index ChildIndex) RoleKind
Role returns the derived structural role of t given the full child index.
The explicit epic marker is the existing `type == "epic"`. "Container" means "has at least one child" (IsContainer). A "project" is a container whose children include at least one further container; a "bucket" is a container whose children are all leaves.
type SlipStatus ¶ added in v0.18.0
type SlipStatus string
SlipStatus is the derived schedule-slip signal for a tick. It is NEVER stored — always computed on demand from target_date and descendant status.
const ( // SlipOverdue means the tick has a target_date in the past AND at least one // open (not closed) leaf descendant. A fully-closed container past its date // is NOT overdue — the work landed. SlipOverdue SlipStatus = "overdue" // SlipOnTrack means the tick has a target_date that is today or in the future. SlipOnTrack SlipStatus = "on_track" // SlipNone means the tick has no target_date set; no signal. SlipNone SlipStatus = "none" )
func Slip ¶ added in v0.18.0
Slip computes the schedule-slip signal for t over the full tick set allTicks, relative to the reference instant now. The caller must supply now; Slip never calls time.Now() so tests can be fully deterministic.
Signal rules (precise-day semantics):
- SlipNone — t.TargetDate is empty.
- SlipOnTrack — target_date >= today's date (today is on-track, not overdue).
- SlipOverdue — target_date < today's date AND there is at least one open leaf descendant (total - closed > 0 from DescendantProgress). A past-dated tick with all descendants closed is SlipOnTrack-equivalent: the work landed, it just finished after the target.
allTicks must be the full tick set for DescendantProgress to be accurate.