Documentation
¶
Overview ¶
doctor.go — whole-board integrity checking and repair.
Doctor scans every task file and reports problems the normal per-command paths only warn about (or cannot see at all): unparseable files, duplicate ids (e.g. after a git merge of two branches that each created the same id), filename↔frontmatter id drift, dangling depends_on references, dependency cycles, and CRLF line endings. With fix enabled it repairs what is safe to repair mechanically.
frontmatter.go — task-file parsing and serialisation.
Task files are YAML frontmatter ("---" fenced) followed by a free-form Markdown body. Parsing works on yaml.Node so that unknown frontmatter keys (and their order) survive a read-modify-write cycle untouched — North only overlays the keys it owns. CRLF line endings are tolerated on read; files are always written with LF.
next.go — work-picking for agents: peek at, or atomically claim, the next workable task.
"Workable" means: active, status ready, unassigned, every dependency resolved, and carrying every requested label. Order is deterministic (lowest id first) so concurrent pickers agree on what "next" means.
Take exists because the two-call claim (`list` then `move in_progress`) has a TOCTOU race: the board lock makes each mutation atomic on its own, but nothing spans the read-decide-write, and SetStatus treats an unchanged status as a silent no-op — so two agents can both believe they claimed the same task. Take closes the window by selecting and claiming under a single lock hold. Do not "optimise" it back into list+move.
snapshot.go — tolerant whole-board loading.
A Snapshot parses every task file on the board exactly once. Files that fail to parse (or carry a duplicate id) become Warnings instead of aborting the load, so one bad file can never take down list/board/TUI. All read paths — listing, lookups, counts, dependents — derive from a Snapshot; mutations still operate per-file.
Package tasks holds task operations over the board: create, read, list, edit, status changes, state changes (draft/active/archive), delete.
Every task is one Markdown file. North uses two orthogonal axes: the task's state is the folder it lives in (drafts/ tasks/ archive/), and its status is a frontmatter key (ready/in_progress/blocked/done/failed). Movement is free within each fixed value set — any value to any other in one step, in any state. Mutations serialise through the advisory board lock and optionally make a local git commit when auto_commit is set.
Index ¶
- Variables
- func Cleanup(boardDir string, olderThanDays int, dryRun bool) ([]*models.Task, error)
- func Create(boardDir, title, assignee string, labels, dependsOn []string, body string) (*models.Task, []string, error)
- func Delete(boardDir, taskID string) ([]string, error)
- func Dependents(boardDir, taskID string) ([]*models.Task, error)
- func Edit(boardDir, taskID string, opts EditOpts) (*models.Task, []string, error)
- func Get(boardDir, taskID string) (*models.Task, error)
- func HasLabels(t *models.Task, labels []string) bool
- func ParseState(value string) (models.TaskState, error)
- func ParseStatus(value string) (models.TaskStatus, error)
- func RenderEditorDoc(d EditorDoc) (string, error)
- func SetState(boardDir, taskID string, newState string) (*models.Task, error)
- func SetStatus(boardDir, taskID string, newStatus string) (*models.Task, []string, error)
- func Sort(ts []*models.Task, key SortKey, desc bool)
- func TemplateBody(boardDir string) string
- type EditOpts
- type EditorDoc
- type Issue
- type Snapshot
- func (s *Snapshot) Dependents(id string) []*models.Task
- func (s *Snapshot) Filter(states []models.TaskState, status string) []*models.Task
- func (s *Snapshot) Get(id string) *models.Task
- func (s *Snapshot) StateCount(state models.TaskState) int
- func (s *Snapshot) StatusCounts() []StatusCount
- func (s *Snapshot) TransitiveDependents(id string) map[string]bool
- func (s *Snapshot) UnmetDeps(t *models.Task) []string
- type SortKey
- type StatusCount
- type Warning
Constants ¶
This section is empty.
Variables ¶
var SortKeys = []SortKey{SortID, SortUpdated, SortTitle, SortAssignee}
SortKeys lists every ordering in display order.
Functions ¶
func Cleanup ¶
Cleanup archives active done tasks (optionally only those older than N days; olderThanDays <= 0 means all). The lock is held for the whole run — snapshot and every archive write — so a task moved off done by a concurrent process mid-run can never be archived stale. With dryRun the candidates are returned without locking or writing anything.
func Create ¶
func Create(boardDir, title, assignee string, labels, dependsOn []string, body string) (*models.Task, []string, error)
Create makes a task in drafts/ with status ready. The returned warnings are advisory dependency notes (hint level only — the op still succeeded).
func Delete ¶
Delete removes a task file. At validated/strict the dependents are healed (the deleted id is dropped from their depends_on, under the same lock hold); at hint the dangling references stay, warned. Returned warnings describe either outcome.
func Dependents ¶
Dependents returns every task whose DependsOn slice contains taskID (exact match), scanning all state folders. Returns an empty non-nil slice if none are found.
func Edit ¶
Edit changes a task's fields/body. UpdatedAt is bumped. The returned warnings are advisory dependency notes (hint level only).
func HasLabels ¶ added in v0.8.0
HasLabels reports whether the task carries every requested label (exact match; an empty request matches everything). The one label matcher shared by next/take selection and the CLI's --label filter.
func ParseState ¶
ParseState coerces a string to a TaskState, raising Invalid on unknown values.
func ParseStatus ¶
func ParseStatus(value string) (models.TaskStatus, error)
ParseStatus coerces a string to a TaskStatus, raising Invalid on unknown values.
func RenderEditorDoc ¶
RenderEditorDoc serialises an EditorDoc to frontmatter+body file content.
func SetState ¶
SetState moves a task's file between state folders (draft/active/archive), preserving status. Freeform: any valid state is reachable from any other.
func SetStatus ¶
SetStatus changes a task's workflow status (frontmatter only; the file stays in its state folder). Movement is free within the status set — though status is only visible on the board while the task is active, and finishing/starting with unmet dependencies warns (hint/validated) or is refused (strict). The returned warnings are advisory (op succeeded).
func Sort ¶
Sort orders tasks by key, descending when desc is set. Ties fall back to ascending numeric id. Unassigned tasks sort last under the assignee key regardless of direction.
func TemplateBody ¶ added in v0.7.0
TemplateBody returns the board's task-template.md content, used to fill bodyless creates. A missing or empty template means a blank body — the template is an init-time scaffold, not a runtime fallback.
Types ¶
type EditOpts ¶
type EditOpts struct {
Title *string
Assignee *string
Labels *[]string
DependsOn *[]string
Body *string
AppendBody *string // appended to the body with a blank-line separator
}
EditOpts carries optional field changes for Edit. Nil fields are unchanged.
type EditorDoc ¶
type EditorDoc struct {
Title string
Assignee string
Labels []string
DependsOn []string
Body string
}
EditorDoc is the editable subset of a task exchanged with the TUI's $EDITOR flow: the same on-disk format (YAML frontmatter + body), restricted to the fields the editor may change. id/state/status are managed by their own commands and are deliberately absent.
func ParseEditorDoc ¶
ParseEditorDoc parses editor output written in the task-file format.
type Issue ¶
type Issue struct {
Kind string // unparseable | duplicate-id | id-drift | dangling-dep | cycle | crlf | gitattributes | gitignore
File string // filename (base) the issue is about ("" for board-wide issues)
Detail string
Fixed bool // true when fix mode repaired it
}
Issue is one problem found by Doctor.
func Doctor ¶
Doctor checks the whole board and returns every issue found, most severe first. With fix true it also repairs: CRLF files are rewritten with LF, duplicate ids are renumbered to fresh ids (the first holder keeps the id, so existing depends_on references stay valid), drifted filenames are renamed to match their frontmatter id, dangling depends_on references are removed, and a missing north/.gitattributes or north/.gitignore is restored. Unparseable files and cycles are report-only.
type Snapshot ¶
type Snapshot struct {
Tasks []*models.Task // sorted by state (board order), then id
Warnings []Warning
// contains filtered or unexported fields
}
Snapshot is a one-shot, tolerant parse of the whole board.
func Load ¶
Load reads every task file on the board. Only filesystem-level failures return an error; per-file parse problems and duplicate ids become Warnings.
func (*Snapshot) Dependents ¶
Dependents returns every task whose DependsOn contains id (exact match). Returns an empty non-nil slice if none are found.
func (*Snapshot) Filter ¶
Filter returns tasks in the given states (all when empty), optionally narrowed to one status ("" for any). Order follows Snapshot.Tasks.
func (*Snapshot) StateCount ¶
StateCount reports how many tasks are in a given state.
func (*Snapshot) StatusCounts ¶
func (s *Snapshot) StatusCounts() []StatusCount
StatusCounts returns counts of active tasks per status, in board order.
func (*Snapshot) TransitiveDependents ¶ added in v0.7.0
TransitiveDependents returns every task id that transitively depends on id (directly or through other tasks). Adding any of them as a dependency of id's task would create a cycle.
func (*Snapshot) UnmetDeps ¶ added in v0.7.0
UnmetDeps returns t's unresolved dependencies: ids whose task is missing from the snapshot or neither done nor archived (archive is terminal and counts as done). Nil when every dependency is resolved. This is the one definition of "waiting" shared by the CLI's --deps filter, enforcement, and the TUI's ! tag.
type SortKey ¶
type SortKey string
SortKey names a task ordering.
func ParseSortKey ¶
ParseSortKey coerces a string to a SortKey, raising Invalid on unknown values.
type StatusCount ¶
StatusCount is one row of the board summary.
type Warning ¶
type Warning struct {
File string // filename (base) the warning is about
Err string // human-readable problem
}
Warning describes a task file the snapshot could not fully use.
func Next ¶ added in v0.8.0
Next returns up to limit workable tasks in take order without touching anything (a pure read; limit <= 0 means all). An empty result with a nil error means nothing is workable — a normal outcome, not a failure. The snapshot warnings are returned alongside.
func Take ¶ added in v0.8.0
Take atomically claims a task for assignee: selection/validation, status=in_progress, and the assignee are all applied under one board-lock hold, so concurrent Takes hand out different tasks. With taskID empty the next workable task is selected — a nil task with a nil error means nothing is workable. With taskID set, that specific task is claimed instead: unknown ids are NotFound, and a task take could not have selected (not active, not ready, already assigned, unmet deps) is refused with a Conflict naming the reason — no steal, no overrides. When the board's max_wip is set (> 0) and assignee already holds that many active in_progress tasks, Take refuses with a conflict. Only deps-met tasks are ever claimed, so no additional dependency enforcement applies.