store

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package store is worklog's persistence layer: a per-project SQLite database (`.worklog/tasks.db`) holding the task tree, its blocking edges, and the durable record of what was decided and done across agent sessions.

Index

Constants

View Source
const DirName = ".worklog"

DirName is the per-project directory that holds the database.

View Source
const FileName = "tasks.db"

FileName is the database file within DirName.

Variables

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned when a task or other record does not exist.

View Source
var Statuses = []string{"pending", "in_progress", "blocked", "done", "dropped"}

Statuses are the five values a task may hold, aligned with the harness's own task vocabulary. "blocked" is a manual override; blocking is also computed from dependency edges (see ActiveBlockers).

Functions

func DetectKind

func DetectKind(url string) string

DetectKind infers a link kind from a URL so callers can just pass the URL.

func GetSectionFromBody

func GetSectionFromBody(body, path string) (string, bool)

GetSectionFromBody returns the content under a heading path (its subsections included), excluding the heading line itself.

func Resolve

func Resolve(dir string) (string, error)

Resolve returns the database path for the project containing dir, walking up from dir to find an existing DirName; if none is found it returns the path dir/DirName/FileName so a fresh database is created in place.

func SetSectionInBody

func SetSectionInBody(body, path, content string) (string, bool)

SetSectionInBody replaces the content under a heading path, preserving the heading line, and returns the new body.

Types

type ChildRef

type ChildRef struct {
	Slug   string `json:"slug"`
	Title  string `json:"title"`
	Status string `json:"status"`
}

ChildRef is a lightweight reference to a child task.

type CreateTaskInput

type CreateTaskInput struct {
	Title     string
	Slug      string // optional; derived from Title when empty
	Parent    string // optional parent slug
	Body      string
	Status    string // optional; defaults to pending
	Priority  int    // optional; defaults to 3
	BlockedBy []string
}

CreateTaskInput carries the fields for a new task.

type Decision

type Decision struct {
	ID        int64  `json:"id"`
	TaskSlug  string `json:"task_slug,omitempty"`
	SessionID int64  `json:"session_id,omitempty"`
	Ts        string `json:"ts"`
	Decision  string `json:"decision_md"`
	Rationale string `json:"rationale_md,omitempty"`
}

Decision is a choice made while executing a task — the "why we did it this way" record that turns a finished task into something you can learn from later. Stored as rows (queryable) but rendered as a "## Decisions" section when a task is shown.

type FindOpts

type FindOpts struct {
	Query        string // free text matched against titles, bodies, decisions, journal, link URLs
	Status       string // restrict matched tasks to this status
	Since        string // RFC3339 lower bound on decision/journal timestamps
	HasDecisions bool   // only tasks that carry at least one decision
}

FindOpts constrains a work-find search. Any subset may be set.

type FindResult

type FindResult struct {
	Tasks     []TaskView     `json:"tasks,omitempty"`
	Decisions []Decision     `json:"decisions,omitempty"`
	Journal   []JournalEntry `json:"journal,omitempty"`
}

FindResult is what a search turned up, kept in three buckets so "what was decided" and "what was done" are answerable, not just "what's open".

type JournalEntry

type JournalEntry struct {
	ID        int64  `json:"id"`
	TaskSlug  string `json:"task_slug,omitempty"`
	SessionID int64  `json:"session_id,omitempty"`
	Ts        string `json:"ts"`
	Kind      string `json:"kind"`
	Text      string `json:"text_md"`
}

JournalEntry is one line in the running log of what happened — the trail a fresh session reads to reconstruct where the last one left off.

type Link struct {
	ID        int64  `json:"id"`
	Kind      string `json:"kind"`
	URL       string `json:"url"`
	Label     string `json:"label,omitempty"`
	CreatedAt string `json:"created_at"`
}

Link ties a task to an external artifact — most importantly a GitHub issue or PR by its full URL, so "which task touched issue #412" is a lookup, not a grep through prose.

type ListOpts

type ListOpts struct {
	Status        string // exact status filter; empty for any
	Parent        string // parent slug; "" for any parent
	IncludeClosed bool   // when false, done/dropped tasks are omitted
}

ListOpts filters ListTasks.

type Section

type Section struct {
	Path  string `json:"path"`
	Level int    `json:"level"`
	Title string `json:"title"`
}

Section identifies one markdown heading within a task body. Path is the slash-joined chain of ancestor headings ("Design/Storage"), so nested sections are addressable without ambiguity.

func TOCFromBody

func TOCFromBody(body string) []Section

TOCFromBody lists the sections in a body.

type Session

type Session struct {
	ID        int64  `json:"id"`
	StartedAt string `json:"started_at"`
	EndedAt   string `json:"ended_at,omitempty"`
	Agent     string `json:"agent"`
	Summary   string `json:"summary_md,omitempty"`
}

Session is one agent working session: the unit worklog attributes decisions and journal entries to, so a later session can see who did what and when.

type Store

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

Store is a handle to one project's worklog database.

func Open

func Open(path string) (*Store, error)

Open opens (creating if needed) the database at path, applies the schema, and enables WAL so concurrent agent sessions sharing the tree do not clobber each other. Pass ":memory:" for an ephemeral database (tests).

func (*Store) ActiveBlockers

func (s *Store) ActiveBlockers(taskID int64) ([]Task, error)

ActiveBlockers returns the tasks still blocking the given task — those dependency targets that are not yet closed. A done or dropped dependency no longer blocks and is omitted.

func (*Store) AddDecision

func (s *Store) AddDecision(slug, decision, rationale string) (*Decision, error)

AddDecision records a decision against a task, attributed to the current session, and journals it.

func (*Store) AddDep

func (s *Store) AddDep(slug, blockerSlug string) error

AddDep records that task (slug) is blocked by another task (blockerSlug).

func (*Store) AddJournal

func (s *Store) AddJournal(slug, text string) (*JournalEntry, error)

AddJournal appends a freeform note, optionally against a task (slug "" records a project-level note).

func (s *Store) AddLink(slug, url, kind, label string) (*Link, error)

AddLink attaches a link to a task. When kind is empty it is detected from the URL. The addition is journaled.

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database.

func (*Store) CreateTask

func (s *Store) CreateTask(in CreateTaskInput) (*Task, error)

CreateTask inserts a task, optionally under a parent and with blocking edges, and records a "created" journal entry.

func (*Store) CurrentSessionID

func (s *Store) CurrentSessionID() int64

CurrentSessionID returns the session writes are attributed to, or 0 if none.

func (*Store) Decisions

func (s *Store) Decisions(taskID int64) ([]Decision, error)

Decisions returns the decisions recorded on a task, oldest first.

func (*Store) Detail

func (s *Store) Detail(slug string) (*TaskDetail, error)

Detail assembles the full record for one task.

func (*Store) EndCurrentSession

func (s *Store) EndCurrentSession() error

EndCurrentSession closes the in-process session (set by StartSession), if any. Safe to call more than once.

func (*Store) EndLatestOpenSession

func (s *Store) EndLatestOpenSession(summary string) (*Session, error)

EndLatestOpenSession closes the most recent session that has no end time, recording summary when non-empty. It is the entry point a Stop hook uses, so it need not know the session id. A no-op when nothing is open.

func (*Store) EndSession

func (s *Store) EndSession(id int64, summary string) (*Session, error)

EndSession records summary against the session and stamps its end time. When summary is empty the existing summary is left untouched.

func (*Store) Find

func (s *Store) Find(o FindOpts) (*FindResult, error)

Find searches across tasks, decisions, and the journal.

func (*Store) GetSection

func (s *Store) GetSection(slug, path string) (string, error)

GetSection returns one section of a task's body.

func (*Store) GetSession

func (s *Store) GetSession(id int64) (*Session, error)

GetSession returns one session by id.

func (*Store) GetTask

func (s *Store) GetTask(slug string) (*Task, error)

GetTask returns one task by slug.

func (s *Store) Links(taskID int64) ([]Link, error)

Links returns every link on a task, oldest first.

func (*Store) ListTasks

func (s *Store) ListTasks(o ListOpts) ([]TaskView, error)

ListTasks returns tasks in actionable order.

func (*Store) NextTask

func (s *Store) NextTask() (*TaskView, error)

NextTask returns the highest-priority actionable task — pending or in_progress, with no active blockers — or nil when nothing is actionable.

func (*Store) RecentJournal

func (s *Store) RecentJournal(limit int) ([]JournalEntry, error)

RecentJournal returns the most recent entries across the whole project, newest first, joined to their task slug when they have one.

func (*Store) RemoveDep

func (s *Store) RemoveDep(slug, blockerSlug string) error

RemoveDep drops a blocking edge; a missing edge is not an error.

func (*Store) SetSection

func (s *Store) SetSection(slug, path, content string) (*Task, error)

SetSection replaces one section of a task's body, leaving the rest untouched.

func (*Store) StartSession

func (s *Store) StartSession(agent string) (*Session, error)

StartSession opens a new session and marks it current, so subsequent writes (decisions, journal) are attributed to it. agent is a free label, typically the client name from the MCP handshake.

func (*Store) TOC

func (s *Store) TOC(slug string) ([]Section, error)

TOC lists a task's sections.

func (*Store) TaskJournal

func (s *Store) TaskJournal(taskID int64, limit int) ([]JournalEntry, error)

TaskJournal returns a task's own journal entries, newest first.

func (*Store) Tree

func (s *Store) Tree(rootSlug string) ([]TreeNode, error)

Tree returns the forest of top-level tasks (rootSlug == "") or the subtree rooted at a given slug. Every node carries closed tasks too, so the full history of a branch stays visible.

func (*Store) UpdateTask

func (s *Store) UpdateTask(in UpdateTaskInput) (*Task, error)

UpdateTask applies a partial update. A status change to a terminal value stamps closed_at (and clears it when reopened), and is journaled.

func (*Store) WarmContext

func (s *Store) WarmContext(journalLimit int) (string, error)

WarmContext renders the "where was I" briefing a new session opens with: the next actionable task, in-progress and blocked work, and the tail of the journal so the last session's trail is visible immediately.

type Task

type Task struct {
	ID        int64  `json:"id"`
	Slug      string `json:"slug"`
	ParentID  int64  `json:"parent_id,omitempty"`
	Title     string `json:"title"`
	Body      string `json:"body_md,omitempty"`
	Status    string `json:"status"`
	Priority  int    `json:"priority"`
	Position  int    `json:"position"`
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
	ClosedAt  string `json:"closed_at,omitempty"`
}

Task is one node in the work tree.

type TaskDetail

type TaskDetail struct {
	Task
	Blockers  []string       `json:"blocked_by,omitempty"`
	Children  []ChildRef     `json:"children,omitempty"`
	Links     []Link         `json:"links,omitempty"`
	Decisions []Decision     `json:"decisions,omitempty"`
	Journal   []JournalEntry `json:"journal,omitempty"`
}

TaskDetail is the full picture of one task: its body, what still blocks it, its children, and — the point of worklog — the links, decisions, and journal that record what was done and why.

func (*TaskDetail) Markdown

func (d *TaskDetail) Markdown() string

Markdown renders a task detail as human-readable markdown, overlaying the stored links and decisions as their own sections beneath the authored body.

type TaskView

type TaskView struct {
	Task
	Blockers   []string `json:"blocked_by,omitempty"`
	ChildCount int      `json:"child_count,omitempty"`
	Actionable bool     `json:"actionable"`
}

TaskView is a task plus the derived facts a reader needs to decide what to do next: its still-active blockers and whether it has children.

type TreeNode

type TreeNode struct {
	TaskView
	Children []TreeNode `json:"children,omitempty"`
}

TreeNode is a task with its recursively-nested children.

type UpdateTaskInput

type UpdateTaskInput struct {
	Slug     string
	Title    *string
	Status   *string
	Priority *int
	Position *int
	Parent   *string // "" detaches to root
	Body     *string
}

UpdateTaskInput carries an update; nil pointers leave a field unchanged.

Jump to

Keyboard shortcuts

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