thread

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: May 30, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package thread provides inline Q&A threads for spec review.

A thread is a lightweight, section-anchored conversation: a question, a list of replies, and an open/resolved flag. Threads are persisted as a sidecar YAML file next to the spec so they ride the existing git-backed specs-repo sync without touching the spec markdown or its frontmatter.

The engine performs no terminal I/O and shells out to nothing. Callers (the CLI, the TUI, and the MCP handler) drive it through the Store interface and render the results themselves.

Index

Constants

View Source
const (
	StatusOpen     = "open"
	StatusResolved = "resolved"
)

Status values for a thread.

Variables

This section is empty.

Functions

This section is empty.

Types

type Reply

type Reply struct {
	Author string    `yaml:"author"`
	At     time.Time `yaml:"at"`
	Body   string    `yaml:"body"`
}

Reply is a single message appended to a thread.

type SidecarStore

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

SidecarStore persists threads as <specsDir>/<SPEC-ID>.threads.yaml.

The file is the only new tracked artifact: it sits beside the spec and syncs through the existing specs-repo git flow. Serialization is deterministic so independent edits diff cleanly and merge associatively.

func NewSidecarStore

func NewSidecarStore(dir string) *SidecarStore

NewSidecarStore returns a store rooted at the given specs directory.

func (*SidecarStore) Create

func (s *SidecarStore) Create(specID, section, author, question string) (Thread, error)

Create appends a new open thread.

func (*SidecarStore) Get

func (s *SidecarStore) Get(specID, threadID string) (Thread, error)

Get returns a single thread by ID.

func (*SidecarStore) List

func (s *SidecarStore) List(specID string) ([]Thread, error)

List loads and returns all threads for a spec. A missing sidecar is not an error — it simply means the spec has no threads yet.

func (*SidecarStore) Reply

func (s *SidecarStore) Reply(specID, threadID, author, body string) (Thread, error)

Reply appends a reply to an existing thread.

func (*SidecarStore) Resolve

func (s *SidecarStore) Resolve(specID, threadID, by string) (Thread, error)

Resolve marks a thread resolved.

func (*SidecarStore) SidecarPath

func (s *SidecarStore) SidecarPath(specID string) string

SidecarPath returns the sidecar file path for a spec ID.

type Store

type Store interface {
	// List returns all threads for a spec, in deterministic order.
	List(specID string) ([]Thread, error)
	// Create appends a new open thread anchored to a section and returns it.
	Create(specID, section, author, question string) (Thread, error)
	// Reply appends a reply to an existing thread.
	Reply(specID, threadID, author, body string) (Thread, error)
	// Resolve marks a thread resolved. Resolving an already-resolved thread
	// is a no-op that returns the thread unchanged.
	Resolve(specID, threadID, by string) (Thread, error)
	// Get returns a single thread by ID.
	Get(specID, threadID string) (Thread, error)
}

Store is the engine boundary for thread persistence. Callers depend on this interface, never on the concrete backend, so a future backend (e.g. a server or local cache) needs no caller changes.

type Thread

type Thread struct {
	// ID is a short, stable, content-independent identifier (e.g. "T-7f3a").
	// It never changes, so replies and resolves never collide on renumbering.
	ID string `yaml:"id"`

	// Section is the markdown section slug the thread is anchored to.
	// This is the only anchor in v1 — a thread is never orphaned by line shifts.
	Section string `yaml:"section"`

	// Status is open or resolved.
	Status string `yaml:"status"`

	// Author is the handle/name of whoever asked the question.
	Author string `yaml:"author"`

	// Created is when the question was asked (UTC).
	Created time.Time `yaml:"created"`

	// Question is the opening message.
	Question string `yaml:"question"`

	// Replies are appended in chronological order.
	Replies []Reply `yaml:"replies,omitempty"`

	// ResolvedBy and ResolvedAt are set when the thread is resolved.
	ResolvedBy string     `yaml:"resolved_by,omitempty"`
	ResolvedAt *time.Time `yaml:"resolved_at,omitempty"`
}

Thread is a single section-anchored conversation.

func Merge

func Merge(a, b []Thread) []Thread

Merge reconciles two thread sets into one. It is used to resolve the rare case where two reviewers edited the same sidecar offline.

Strategy:

  • Threads are unioned by ID. A thread present in only one side is kept.
  • For a thread present in both sides, replies are unioned (same author + timestamp + body counts as the same reply); the resolved state wins if either side resolved it. This makes merges associative and never drops a reply.

The result is returned in deterministic order so a merged file diffs cleanly.

func (Thread) IsOpen

func (t Thread) IsOpen() bool

IsOpen reports whether the thread is still awaiting resolution.

Jump to

Keyboard shortcuts

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