state

package
v0.16.0 Latest Latest
Warning

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

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

Documentation

Overview

Package state manages the SQLite state database for The Forge.

The database lives at ~/.forge/state.db and tracks:

  • workers: active and historical Smith worker processes
  • prs: pull requests created by Forge across anvils
  • events: timestamped log of all significant actions

Index

Constants

View Source
const (
	DefaultMaxCIFixAttempts     = 5
	DefaultMaxReviewFixAttempts = 5
	DefaultMaxRebaseAttempts    = 3
)

Default lifecycle thresholds. These are used by NeedsAttentionBeads and can be overridden via config (settings.max_ci_fix_attempts, etc.).

View Source
const (
	ForgeSessionStatusDraft    = "draft"
	ForgeSessionStatusArchived = "archived"
)

Permitted forge session status values. Other beads may add more — the DB stores TEXT so this is an advisory list rather than a strict enum.

View Source
const (
	ForgeMessageRoleUser      = "user"
	ForgeMessageRoleAssistant = "assistant"
	ForgeMessageRoleSystem    = "system"
)

Permitted forge session message roles.

View Source
const (
	// ForgeStageDrafting is the default open-chat stage. The user and claude
	// converse freely; the user can ask claude to emit a plan at any point.
	ForgeStageDrafting = "drafting"
	// ForgeStageGrilling is the structured Q&A stage. Claude relentlessly
	// asks questions (with options + a recommendation) until the design tree
	// is exhausted. The user picks an option or writes a free-form answer.
	ForgeStageGrilling = "grilling"
	// ForgeStageReady marks a session whose plan and answers are settled
	// enough to be turned into beads in the next bead. The current bead
	// stops at producing the plan + answered grilling tree; the actual
	// bd-create flow lives in a follow-on bead.
	ForgeStageReady = "ready"
)

Beads-Forge stages. The session moves through these as the user iterates on the design with claude. The DB stores TEXT so future beads can extend the set without a schema migration.

View Source
const (
	// ForgeMessageKindText is a plain conversational turn.
	ForgeMessageKindText = "text"
	// ForgeMessageKindPlan is a markdown plan emitted by the assistant. The
	// content holds the plan body; metadata is unused.
	ForgeMessageKindPlan = "plan"
	// ForgeMessageKindQuestion is a structured grilling-stage question. The
	// metadata holds the JSON-encoded options + recommendation; the content
	// is the question text.
	ForgeMessageKindQuestion = "question"
	// ForgeMessageKindAnswer is the user's response to a question. Metadata
	// pins the parent question_id and the chosen option_id (when an option
	// was picked); content is the human-readable answer (option label or
	// free-form text).
	ForgeMessageKindAnswer = "answer"
	// ForgeMessageKindStatus is a system-emitted status note (e.g. "Stage
	// changed to grilling"). Rendered as italic in the chat view.
	ForgeMessageKindStatus = "status"
	// ForgeMessageKindBeadsCreated is the receipt for a successful
	// bead-emission turn. Content holds a chat-friendly recap; metadata
	// holds the JSON list of created beads (bead_id, anvil, title) so the
	// UI can render clickable links and replays survive page reloads.
	ForgeMessageKindBeadsCreated = "beads_created"
)

Forge message kinds. Like roles, this is a TEXT column so callers can add new kinds without a migration.

Variables

View Source
var ErrForgeSessionNotFound = errors.New("forge session not found")

ErrForgeSessionNotFound is returned when an UPDATE targets a session row that doesn't exist. Callers can map this to a 404 instead of treating a silent no-op as success.

Functions

func DefaultPath

func DefaultPath() (string, error)

DefaultPath returns ~/.forge/state.db.

Types

type AnvilPollStatus added in v0.5.0

type AnvilPollStatus struct {
	Anvil     string
	Timestamp time.Time
	OK        bool   // true if last poll succeeded, false on error
	Message   string // e.g. "5 ready" or error message
}

AnvilPollStatus holds the last poll outcome for an anvil.

type DB

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

DB wraps a SQLite connection for Forge state.

func Open

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

Open opens or creates the state database at the given path. If path is empty, DefaultPath() is used.

func (*DB) ActiveDispatchWorkers

func (db *DB) ActiveDispatchWorkers() ([]Worker, error)

ActiveDispatchWorkers returns active workers that are running primary dispatch pipeline phases (smith, temper, warden). Bellows (PR monitoring) and lifecycle workers (quench, burnish, rebase) are excluded so they don't consume dispatch capacity slots. Stalled workers are included so they continue to count against capacity and prevent the daemon from over-subscribing while stalled processes are still running.

func (*DB) ActiveDispatchWorkersByAnvil

func (db *DB) ActiveDispatchWorkersByAnvil(anvil string) ([]Worker, error)

ActiveDispatchWorkersByAnvil returns active dispatch workers for a given anvil, excluding bellows and lifecycle workers (quench, burnish, rebase). Stalled workers are included so they continue to count against per-anvil capacity.

func (*DB) ActiveWorkerByBead

func (db *DB) ActiveWorkerByBead(beadID string) (*Worker, error)

ActiveWorkerByBead returns the non-terminal worker for a given bead ID.

func (*DB) ActiveWorkerByBeadAndAnvil

func (db *DB) ActiveWorkerByBeadAndAnvil(beadID, anvil string) (*Worker, error)

ActiveWorkerByBeadAndAnvil returns the non-terminal worker for a given bead scoped to a specific anvil. Use this instead of ActiveWorkerByBead when iterating per-anvil to avoid false positives when two anvils share bead IDs.

func (*DB) ActiveWorkers

func (db *DB) ActiveWorkers() ([]Worker, error)

ActiveWorkers returns all workers with non-terminal status (including stalled).

func (*DB) AddBeadCost

func (db *DB) AddBeadCost(beadID, anvil string, input, output, cacheRead, cacheWrite int, cost float64) error

AddBeadCost adds token usage to a bead's cumulative cost.

func (*DB) AddCopilotRequest added in v0.2.0

func (db *DB) AddCopilotRequest(date string, multiplier float64) error

AddCopilotRequest records a Copilot premium request for today, weighted by the model's multiplier (e.g. opus 4.6 = 3x per invocation).

func (*DB) AddDailyCost

func (db *DB) AddDailyCost(date string, input, output, cacheRead, cacheWrite int, cost float64) error

AddDailyCost adds token usage to today's aggregate.

func (*DB) AddPendingOrphan added in v0.4.0

func (db *DB) AddPendingOrphan(beadID, anvil, title, branch string) error

AddPendingOrphan records a bead that needs user review before recovery. If the bead is already pending, it is a no-op.

func (*DB) AddProviderDailyCost added in v0.2.0

func (db *DB) AddProviderDailyCost(date, prov string, input, output, cacheRead, cacheWrite int, cost float64) error

AddProviderDailyCost adds token usage to a specific provider's daily aggregate.

func (*DB) AllWorkers

func (db *DB) AllWorkers(limit int) ([]Worker, error)

AllWorkers returns all workers ordered by most recent first.

func (*DB) AppendForgeSessionMessage added in v0.16.0

func (db *DB) AppendForgeSessionMessage(m ForgeSessionMessage) (ForgeSessionMessage, error)

AppendForgeSessionMessage inserts a new message and bumps the parent session's updated_at in a single transaction. Returns the persisted message with its assigned ID and timestamp.

func (*DB) AppendForgeSessionMessageRaw added in v0.16.0

func (db *DB) AppendForgeSessionMessageRaw(sessionID int64, role, kind, content, metadata string) (ForgeSessionMessage, error)

AppendForgeSessionMessageRaw is a thin wrapper that lets tests inject a message with explicit role + kind + metadata in one call. Production code should prefer AppendForgeSessionMessage with a fully-populated struct.

func (*DB) BeadTitle

func (db *DB) BeadTitle(beadID, anvil string) string

BeadTitle returns the display title for a bead, consulting queue_cache first then falling back to the most recent workers entry. Returns an empty string if no title is found.

func (*DB) ClarificationNeededBeadIDSet

func (db *DB) ClarificationNeededBeadIDSet() (map[string]struct{}, error)

ClarificationNeededBeadIDSet returns a set of "beadID\x00anvil" keys for all beads needing clarification. This allows callers to do a single query and then O(1) membership checks.

func (*DB) ClarificationNeededBeads

func (db *DB) ClarificationNeededBeads() ([]RetryRecord, error)

ClarificationNeededBeads returns all beads that need human clarification before work can start.

func (*DB) ClearNeedsAttention added in v0.16.0

func (db *DB) ClearNeedsAttention(beadID, anvil string) error

ClearNeedsAttention zeroes the needs-attention flags on a bead's retry row without scheduling a re-dispatch. Unlike ResetRetry it leaves clarification_needed, retry_count, and next_retry untouched, and unlike DismissRetry it preserves the row. It is idempotent: running it on an already-clean row (or a bead with no retry row at all) is a no-op success.

func (*DB) ClearRetry

func (db *DB) ClearRetry(beadID, anvil string) error

ClearRetry removes the retry record for a bead (typically after success).

func (*DB) Close

func (db *DB) Close() error

Close closes the database connection.

func (*DB) CompleteWorkersByBead

func (db *DB) CompleteWorkersByBead(beadID string) error

CompleteWorkersByBead marks all non-terminal workers for a bead as Done.

func (*DB) CompletedWorkers

func (db *DB) CompletedWorkers(limit int) ([]Worker, error)

CompletedWorkers returns workers in terminal states (done, failed, timeout), ordered by most recently completed first. Limit 0 means no limit.

func (*DB) Conn

func (db *DB) Conn() *sql.DB

Conn returns the underlying sql.DB for direct queries.

func (*DB) CountForgeSessionMessages added in v0.16.0

func (db *DB) CountForgeSessionMessages(sessionID int64) (int, error)

CountForgeSessionMessages returns the number of persisted messages for a session. Used by the API to populate the sidebar preview without sending the full message list.

func (*DB) CountWicketIssues added in v0.11.0

func (db *DB) CountWicketIssues(opts ListWicketIssuesOpts) (int, error)

CountWicketIssues returns the number of wicket_issues rows matching the given options using a DB-side COUNT(*) query (no full-row fetch).

func (*DB) CreateForgeSession added in v0.16.0

func (db *DB) CreateForgeSession(s ForgeSession) (ForgeSession, error)

CreateForgeSession inserts a new session row, returning the assigned ID and the canonical timestamps. CreatedBy may be empty when the caller is unable to attribute the session to a user.

func (*DB) CreateWebSession added in v0.16.0

func (db *DB) CreateWebSession(s WebSession) error

CreateWebSession inserts a new web session row.

func (*DB) DeleteForgeSession added in v0.16.0

func (db *DB) DeleteForgeSession(id int64) error

DeleteForgeSession removes a session and its messages. Foreign keys are declared with ON DELETE CASCADE in the schema, but SQLite only honours that when foreign_keys = ON, so we delete messages explicitly to be independent of the connection-level pragma.

func (*DB) DeletePendingRules added in v0.10.0

func (db *DB) DeletePendingRules(ids []int) error

DeletePendingRules removes pending warden rules by their IDs.

func (*DB) DeletePipelineBellowsWorker added in v0.8.0

func (db *DB) DeletePipelineBellowsWorker(beadID, anvil string) error

DeletePipelineBellowsWorker removes any worker row for bead+anvil that was repurposed from the pipeline (no "bellows-" prefix in ID). Called by the bellows poll loop before inserting the canonical bellows-{anvil}-{prNum} row so Hearth never shows two workers for the same PR.

func (*DB) DeleteWebSession added in v0.16.0

func (db *DB) DeleteWebSession(tokenHash string) error

DeleteWebSession removes a session by token hash.

func (*DB) DismissExhaustedPR

func (db *DB) DismissExhaustedPR(id int) error

DismissExhaustedPR marks an exhausted PR as closed so it no longer appears in the Needs Attention panel.

func (*DB) DismissRetry

func (db *DB) DismissRetry(beadID, anvil string) error

DismissRetry removes the retry record entirely, clearing the bead from the Needs Attention list without resetting for a retry.

func (*DB) EventsSince added in v0.16.0

func (db *DB) EventsSince(lastID, limit int) ([]Event, error)

EventsSince returns events with ID greater than lastID, ordered oldest-first. Used by the web SSE activity stream to deliver new events to connected clients; the oldest-first ordering and id > lastID predicate make it trivial to drive an EventSource Last-Event-ID resume.

func (*DB) ExhaustedPRs

func (db *DB) ExhaustedPRs(maxCI, maxRev, maxRebase int) ([]ExhaustedPR, error)

ExhaustedPRs returns non-terminal PRs where any fix/rebase counter has reached its threshold. The thresholds are passed as parameters so the caller can source them from config or constants. Non-positive threshold values are normalized to their intended defaults to avoid matching all PRs.

func (*DB) GetAllProviderQuotas

func (db *DB) GetAllProviderQuotas() (map[string]provider.Quota, error)

GetAllProviderQuotas returns all known provider quotas.

func (*DB) GetCopilotRequestsOn added in v0.2.0

func (db *DB) GetCopilotRequestsOn(date string) (float64, error)

GetCopilotRequestsOn returns the total weighted premium requests for the given date (YYYY-MM-DD). Returns 0 if no row exists yet.

func (*DB) GetDailyCost

func (db *DB) GetDailyCost(date string) (inputTokens, outputTokens, cacheRead, cacheWrite int, cost, limit float64, err error)

GetDailyCost returns cost data for a specific date.

func (*DB) GetForgeSession added in v0.16.0

func (db *DB) GetForgeSession(id int64) (*ForgeSession, error)

GetForgeSession returns the session with the given ID, or nil if no row exists. Errors other than ErrNoRows are returned to the caller.

func (*DB) GetPRByID

func (db *DB) GetPRByID(id int) (*PR, error)

GetPRByID returns a PR by its primary key id, or nil if not found.

func (*DB) GetPRByNumber

func (db *DB) GetPRByNumber(anvil string, number int) (*PR, error)

GetPRByNumber returns a PR by its anvil and number.

func (*DB) GetProviderDailyCosts added in v0.2.0

func (db *DB) GetProviderDailyCosts(date string) ([]ProviderDailyCost, error)

GetProviderDailyCosts returns per-provider cost data for a given date.

func (*DB) GetProviderQuota

func (db *DB) GetProviderQuota(pv string) (*provider.Quota, error)

GetProviderQuota returns the quota for a specific provider.

func (*DB) GetRetry

func (db *DB) GetRetry(beadID, anvil string) (*RetryRecord, error)

GetRetry returns the retry record for a bead, or nil if none exists.

func (*DB) GetTodayCopilotRequests added in v0.2.0

func (db *DB) GetTodayCopilotRequests() (float64, error)

GetTodayCopilotRequests returns the total weighted premium requests used today. Returns 0 if no row exists yet.

func (*DB) GetTodayCost

func (db *DB) GetTodayCost() (float64, error)

GetTodayCost returns today's estimated cost total. Returns 0 if no row exists yet.

func (*DB) GetTodayCostOn

func (db *DB) GetTodayCostOn(date string) (float64, error)

GetTodayCostOn returns the estimated cost total for the given date (YYYY-MM-DD). Returns 0 if no row exists yet for that date.

func (*DB) GetWebSession added in v0.16.0

func (db *DB) GetWebSession(tokenHash string) (*WebSession, error)

GetWebSession looks up a session by its token hash, returning nil if not found or expired.

func (*DB) GetWicketIssue added in v0.11.0

func (db *DB) GetWicketIssue(repo string, number int) (*WicketIssue, error)

GetWicketIssue returns the wicket_issues row for the given repo and issue number, or nil if no such row exists.

func (*DB) GetWicketIssueByBeadID added in v0.11.0

func (db *DB) GetWicketIssueByBeadID(beadID string) (*WicketIssue, error)

GetWicketIssueByBeadID returns the wicket_issues row for the given bead ID, or nil if no such row exists. Used for lifecycle event bridging.

func (*DB) GetWicketSummary added in v0.11.0

func (db *DB) GetWicketSummary() ([]WicketAnvilSummary, error)

GetWicketSummary returns per-repo open and needs-human issue counts from the wicket_issues table. Only repos that have at least one open issue (state NOT IN ('closed', 'merged')) are returned, sorted by repo name.

func (*DB) GetWorker added in v0.13.0

func (db *DB) GetWorker(id string) (*Worker, error)

GetWorker returns the worker record for the given worker ID, regardless of status. Returns an error if no worker with that ID exists.

func (*DB) HasEventForDate added in v0.4.0

func (db *DB) HasEventForDate(eventType EventType, date string) (bool, error)

HasEventForDate reports whether any event of the given type was logged on the specified date (YYYY-MM-DD). It is used to deduplicate notifications that should fire at most once per calendar day (e.g. cost_limit_hit) even across daemon restarts, which would otherwise reset in-memory guards.

func (*DB) HasEventWithin added in v0.11.1

func (db *DB) HasEventWithin(eventType EventType, d time.Duration) (bool, error)

HasEventWithin reports whether any event of the given type was logged within the past duration d from now. Any DB error is returned to the caller.

func (*DB) HasOpenPRForBead

func (db *DB) HasOpenPRForBead(beadID, anvil string) (bool, error)

HasOpenPRForBead returns true if there is a non-terminal PR for the given bead in the given anvil, OR if there is a recently-merged PR still within the grace period (mergedPRGracePeriod). The grace window prevents orphan recovery from falsely reclaiming beads whose PR just merged but whose bd close has not yet completed.

func (*DB) HasWorkerRecord

func (db *DB) HasWorkerRecord(beadID, anvil string) (bool, error)

HasWorkerRecord returns true if Forge has ever had a worker for the given bead in the given anvil (any status). This is used by orphan recovery to distinguish beads that Forge previously claimed from beads that are in_progress because a human or external tool is working on them.

func (*DB) IncrementDispatchFailures

func (db *DB) IncrementDispatchFailures(beadID, anvil string, maxFailures int, reason string) (int, bool, error)

IncrementDispatchFailures atomically increments the dispatch_failures counter for a bead within a transaction. If the counter reaches maxFailures, sets needs_human=1 with a "circuit breaker:" prefixed error. Returns the new failure count and whether the circuit broke.

func (*DB) IncrementRecoveryFailures added in v0.14.0

func (db *DB) IncrementRecoveryFailures(beadID, anvil, reason string) (int, bool, error)

IncrementRecoveryFailures atomically increments the recovery_failures counter for a bead. If the counter reaches maxRecoveryFailures or the first failure was more than 30 minutes ago, sets needs_human=1. Returns the new failure count and whether needs_human was set.

func (*DB) InsertPR

func (db *DB) InsertPR(pr *PR) error

InsertPR adds a new PR record. ci_passing is intentionally omitted so the DB default (1 = passing) always applies for new PRs, avoiding silent insertion of a failing PR due to Go's zero-value false. has_pending_reviews is explicitly set to 1 (pending) so new PRs don't appear in Ready to Merge until bellows confirms no reviews are pending. This closes the race window where GitHub assigns reviewers (e.g. Copilot) asynchronously after PR creation. bellows_managed defaults to 0 for ext-* (externally created) PRs so the reconcile loop's auto-adoption release path is not triggered on every fresh insert; users opt in via the assign_bellows IPC action which sets both flags (Forge-l125). An explicit pr.BellowsManaged=true on the struct pre-pins an ext-* row at insert time and automatically sets bellows_manually_assigned=1 so that reconcile does not clear the flag on the next cycle.

func (*DB) InsertPendingRule added in v0.10.0

func (db *DB) InsertPendingRule(anvil, ruleYAML, sourcePR string) error

InsertPendingRule adds a new pending warden rule for the given anvil.

func (*DB) InsertWicketIssue added in v0.11.0

func (db *DB) InsertWicketIssue(issue WicketIssue) error

InsertWicketIssue inserts a new wicket_issues row.

func (*DB) InsertWorker

func (db *DB) InsertWorker(w *Worker) error

InsertWorker adds a new worker record.

func (*DB) InsertWorkerIfMissing added in v0.8.0

func (db *DB) InsertWorkerIfMissing(w *Worker) error

InsertWorkerIfMissing adds a worker record only when no row with the same id already exists. This avoids unnecessary WAL churn on repeated poll cycles (e.g. bellows upserts) where the row is stable between polls.

func (*DB) IsIssueTracked added in v0.11.0

func (db *DB) IsIssueTracked(repo string, number int) (bool, error)

IsIssueTracked returns true when a wicket_issues row exists for the given repo and issue number.

func (*DB) IsPRReadyToMerge

func (db *DB) IsPRReadyToMerge(id int) (bool, error)

IsPRReadyToMerge reports whether the given PR currently satisfies all ready-to-merge conditions: CI passing, not conflicting, no unresolved review threads, no pending review requests, and not in a needs_fix/closed/merged state. Approval is not required — repos without branch protection rules should still surface PRs as mergeable; GitHub enforces required reviews at merge time.

func (*DB) IsPendingOrphan added in v0.4.0

func (db *DB) IsPendingOrphan(beadID, anvil string) (bool, error)

IsPendingOrphan reports whether the given bead is already in the pending_orphans list.

func (*DB) LastEventTime added in v0.12.0

func (db *DB) LastEventTime(eventType EventType) (time.Time, bool, error)

LastEventTime returns the timestamp of the most recent event of the given type. The second return value is false when no such event exists. Any DB error is returned to the caller.

func (*DB) LastPollAllAnvils added in v0.12.0

func (db *DB) LastPollAllAnvils() ([]AnvilPollStatus, error)

LastPollAllAnvils returns the most recent poll or poll_error event for every distinct anvil that has ever been polled. It is used by Hearth when no config-derived anvil list is available (e.g. when forge.yaml is not found in the working directory or ~/.forge/).

func (*DB) LastPollPerAnvil added in v0.5.0

func (db *DB) LastPollPerAnvil(anvilNames []string) ([]AnvilPollStatus, error)

LastPollPerAnvil returns the most recent poll or poll_error event for each of the given anvil names. Anvils with no poll history are omitted.

func (*DB) LastWicketScanAt added in v0.11.0

func (db *DB) LastWicketScanAt() (*time.Time, error)

LastWicketScanAt returns the timestamp of the most recent wicket_scan_done event, or nil when no such event exists yet.

func (*DB) LastWorkerBranchForBead added in v0.4.0

func (db *DB) LastWorkerBranchForBead(beadID, anvil string) (string, error)

LastWorkerBranchForBead returns the branch from the most recent worker record for a given bead in a given anvil. Returns an empty string (not an error) if no worker record exists yet.

func (*DB) LastWorkerLogPath

func (db *DB) LastWorkerLogPath(beadID string) (string, error)

LastWorkerLogPath returns the log path from the most recent worker for a bead.

func (*DB) LatestWardenRejectMessage added in v0.8.0

func (db *DB) LatestWardenRejectMessage(beadID string) string

LatestWardenRejectMessage returns the message from the most recent warden_reject event for the given bead, or "" if none exists.

func (*DB) ListForgeSessionMessages added in v0.16.0

func (db *DB) ListForgeSessionMessages(sessionID int64) ([]ForgeSessionMessage, error)

ListForgeSessionMessages returns the messages for a session in insertion order (oldest first), suitable for direct rendering in the chat view.

func (*DB) ListForgeSessions added in v0.16.0

func (db *DB) ListForgeSessions(createdBy string, limit int) ([]ForgeSession, error)

ListForgeSessions returns sessions ordered by most-recent activity first. When createdBy is non-empty, only sessions started by that user are returned; this keeps the sidebar scoped to the signed-in user. Limit is clamped to [1, 200].

func (*DB) ListForgeSessionsMissingAnvil added in v0.16.0

func (db *DB) ListForgeSessionsMissingAnvil() ([]ForgeSession, error)

ListForgeSessionsMissingAnvil returns sessions whose anvil column is empty, ordered by id ASC for stable iteration. Used by the `forge backfill-anvils` admin command to repair legacy rows created before the anvil field became required.

func (*DB) ListForgeSessionsWithCounts added in v0.16.0

func (db *DB) ListForgeSessionsWithCounts(createdBy string, limit int) ([]ForgeSessionWithCount, error)

ListForgeSessionsWithCounts returns sessions with their message counts using a single LEFT JOIN query, eliminating the N+1 pattern of a per-row COUNT. When createdBy is non-empty, rows owned by that user plus unattributed rows (created_by="") are returned, consistent with forgeSessionVisibleTo.

func (*DB) ListPendingOrphans added in v0.4.0

func (db *DB) ListPendingOrphans() ([]PendingOrphan, error)

ListPendingOrphans returns all beads awaiting user decision, ordered by creation time.

func (*DB) ListWicketIssues added in v0.11.0

func (db *DB) ListWicketIssues(opts ListWicketIssuesOpts) ([]WicketIssue, error)

ListWicketIssues returns wicket_issues rows matching the given options, ordered by created_at descending.

func (*DB) LogEvent

func (db *DB) LogEvent(typ EventType, message, beadID, anvil string) error

LogEvent records an event in the database.

func (*DB) LogEventAt added in v0.11.1

func (db *DB) LogEventAt(typ EventType, message, beadID, anvil string, at time.Time) error

LogEventAt records an event with an explicit timestamp. It is intended for use in tests where deterministic timestamps are required.

func (*DB) MarkNeedsHuman added in v0.7.0

func (db *DB) MarkNeedsHuman(beadID, anvil, reason string) error

MarkNeedsHuman immediately sets needs_human=1 for a bead, creating the retries row if it doesn't exist. Use this when a pipeline outcome definitively requires human attention (e.g. no-diff hard reject) without waiting for the circuit breaker to trip after multiple failures.

func (*DB) MarkWorkerStalled

func (db *DB) MarkWorkerStalled(id string) error

MarkWorkerStalled sets a worker's status to stalled and records the time.

func (*DB) MergedPRs added in v0.8.0

func (db *DB) MergedPRs() ([]PR, error)

MergedPRs returns all PRs with status=merged that have a non-empty, non-external bead_id (i.e. beads that Forge can attempt to close).

func (*DB) NeedsAttentionBeads

func (db *DB) NeedsAttentionBeads(maxCI, maxRev, maxRebase int) ([]NeedsAttentionBead, error)

NeedsAttentionBeads returns all beads with needs_human=1, clarification_needed=1, or status=stalled, enriched with title from queue_cache or workers tables. It also includes PRs that have exhausted their CI-fix, review-fix, or rebase attempt limits. The maxCI/maxRev/maxRebase thresholds determine which PRs are considered exhausted.

func (*DB) NeedsHumanBeadIDSet added in v0.3.0

func (db *DB) NeedsHumanBeadIDSet() (map[string]struct{}, error)

NeedsHumanBeadIDSet returns a set of "beadID\x00anvil" keys for all beads currently marked needs_human=1 in the retries table. This intentionally includes any reason that set needs_human (dispatch circuit breaker trips, crucible child failures, exhausted retries, clarification needed that escalated to human review, etc.), and excludes all rows where needs_human=0. Callers can use this set for O(1) membership checks in the dispatch loop to determine which beads are not eligible for automatic dispatch.

func (*DB) NeedsHumanBeads

func (db *DB) NeedsHumanBeads() ([]RetryRecord, error)

NeedsHumanBeads returns all beads that have exhausted retries.

func (*DB) OpenPRs

func (db *DB) OpenPRs() ([]PR, error)

OpenPRs returns all PRs with non-terminal status.

func (*DB) OpenPRsWithDetail added in v0.5.0

func (db *DB) OpenPRsWithDetail() ([]OpenPRDetail, error)

OpenPRsWithDetail returns all non-terminal PRs with title resolution and full status fields.

func (*DB) OrphanedMonitoringBellowsWorkers added in v0.16.0

func (db *DB) OrphanedMonitoringBellowsWorkers() ([]OrphanedWorkerInfo, error)

OrphanedMonitoringBellowsWorkers returns bellows monitoring workers whose underlying PR is missing or already terminal (merged/closed), using a single LEFT JOIN query rather than per-row GetPRByNumber lookups.

func (*DB) PRByNumber

func (db *DB) PRByNumber(number int) (*PR, error)

PRByNumber returns the PR record for a given GitHub PR number, or nil if not found.

func (*DB) Path

func (db *DB) Path() string

Path returns the database file path.

func (*DB) PendingRetries

func (db *DB) PendingRetries() ([]RetryRecord, error)

PendingRetries returns retries that are ready to be attempted (next_retry <= now).

func (*DB) PurgeExpiredWebSessions added in v0.16.0

func (db *DB) PurgeExpiredWebSessions() (int64, error)

PurgeExpiredWebSessions removes all sessions whose expires_at is in the past. Returns the number of rows removed.

func (*DB) QueryPendingRulesByAnvil added in v0.10.0

func (db *DB) QueryPendingRulesByAnvil() (map[string][]PendingRule, error)

QueryPendingRulesByAnvil returns all pending warden rules grouped by anvil.

func (*DB) QueueCache

func (db *DB) QueueCache() ([]QueueItem, error)

QueueCache returns all cached queue items, sorted by section (ready, unlabeled, in_progress), then priority, bead ID, and anvil.

func (*DB) QueueCount added in v0.2.0

func (db *DB) QueueCount() (int, error)

QueueCount returns the number of items currently in the queue cache. It is a lightweight alternative to QueueCache() for callers that only need the count, avoiding a full row scan and allocation.

func (*DB) ReadyToMergePRs

func (db *DB) ReadyToMergePRs() ([]ReadyToMergePR, error)

ReadyToMergePRs returns PRs where: CI passing, not conflicting, no unresolved review threads, no pending review requests, and not in a terminal/fix state. Title is resolved with a best-effort lookup from queue_cache or workers.

func (*DB) RecentDailyCosts

func (db *DB) RecentDailyCosts(n int) ([]struct {
	Date          string
	InputTokens   int
	OutputTokens  int
	EstimatedCost float64
}, error)

RecentDailyCosts returns daily cost records, most recent first.

func (*DB) RecentEvents

func (db *DB) RecentEvents(n int) ([]Event, error)

RecentEvents returns the most recent n events.

func (*DB) RecentEventsExcluding added in v0.5.0

func (db *DB) RecentEventsExcluding(n int, excludeTypes []EventType) ([]Event, error)

RecentEventsExcluding returns the n most recent events, excluding the given types.

func (*DB) RemovePendingOrphan added in v0.4.0

func (db *DB) RemovePendingOrphan(beadID, anvil string) error

RemovePendingOrphan removes a bead from the pending_orphans list.

func (*DB) ReplaceQueueCacheForAnvils

func (db *DB) ReplaceQueueCacheForAnvils(anvils []string, items []QueueItem) error

ReplaceQueueCacheForAnvils atomically replaces the cached queue rows for the specified anvils only, leaving rows for other anvils untouched. This allows failed anvil polls to retain their last-known cached data.

func (*DB) ResetDispatchFailures

func (db *DB) ResetDispatchFailures(beadID, anvil string) error

ResetDispatchFailures clears the dispatch_failures counter and any circuit-breaker-induced needs_human flag for a bead, allowing it to be dispatched again. It only resets rows that were actually tripped by the dispatch circuit breaker (dispatch_failures > 0 with a "circuit breaker:" last_error), so unrelated needs_human states are preserved.

func (*DB) ResetPRFixCounts

func (db *DB) ResetPRFixCounts(id int) error

ResetPRFixCounts resets all fix/rebase counters on a PR and sets its status back to open so Bellows re-detects and dispatches new fix cycles.

func (*DB) ResetRecoveryFailures added in v0.14.0

func (db *DB) ResetRecoveryFailures(beadID, anvil string) error

ResetRecoveryFailures clears the recovery failure counter and timestamp for a bead after a successful recovery. If the row was marked needs_human by a tripped recovery circuit breaker, it also clears that recovery-specific state while preserving unrelated needs_human reasons.

func (*DB) ResetRetry

func (db *DB) ResetRetry(beadID, anvil string) error

ResetRetry clears the needs_human and clarification_needed flags and resets the retry count to zero, allowing the bead to be dispatched again.

func (*DB) SetClarificationNeeded

func (db *DB) SetClarificationNeeded(beadID, anvil string, needed bool, reason string) error

SetClarificationNeeded marks or clears the clarification_needed flag for a bead. When needed=true and no retry record exists, one is created with the flag set. When needed=false, only existing records are updated (no row is created).

func (*DB) SetDailyCostLimit

func (db *DB) SetDailyCostLimit(date string, limit float64) error

SetDailyCostLimit sets the cost limit for a specific date.

func (*DB) StalledWorkers

func (db *DB) StalledWorkers(staleThreshold time.Duration) ([]Worker, error)

StalledWorkers returns active non-stalled workers whose log files have not been modified within the given staleThreshold. Workers without a log path (pending workers) are still considered stalled if their start time exceeds the threshold. Already-stalled workers are excluded to avoid repeated filesystem stat calls on log files that won't change their status. All phases listed in backgroundPhases are excluded from the global check because they only produce log output when external state changes (e.g. PR events) and can be legitimately silent for long stretches. However, lifecycle workers (quench/burnish/rebase) that were registered with a per-worker StaleTimeout are additionally checked using that shorter threshold — these phases appear in backgroundPhases and are therefore absent from the first result set, so there is no risk of duplicates.

func (*DB) TotalCostSince

func (db *DB) TotalCostSince(sinceDate string) (float64, error)

TotalCostSince returns aggregate cost since a given date.

func (*DB) TouchForgeSession added in v0.16.0

func (db *DB) TouchForgeSession(id int64) error

TouchForgeSession bumps updated_at to now without changing other fields. Called after appending a message so the sidebar reorders correctly.

func (*DB) TouchWebSession added in v0.16.0

func (db *DB) TouchWebSession(tokenHash string, ttl time.Duration) (time.Time, error)

TouchWebSession updates the last_seen timestamp and slides the expiry forward by ttl. Returns the new expiry time.

func (*DB) UpdateForgeSession added in v0.16.0

func (db *DB) UpdateForgeSession(id int64, title, status *string) error

UpdateForgeSession applies a partial update to the session row. Only the fields whose pointer is non-nil are written, so callers can rename a session without touching its status (or vice versa). The updated_at column is always advanced.

func (*DB) UpdateForgeSessionAnvil added in v0.16.0

func (db *DB) UpdateForgeSessionAnvil(id int64, anvil string) error

UpdateForgeSessionAnvil sets the anvil column on a session and advances updated_at. Returns ErrForgeSessionNotFound when no row matches the id so the caller can distinguish a missing row from a no-op write.

func (*DB) UpdateForgeSessionStageAndPlan added in v0.16.0

func (db *DB) UpdateForgeSessionStageAndPlan(id int64, stage, plan *string) (*ForgeSession, error)

UpdateForgeSessionStageAndPlan sets the stage and/or plan fields on a session and advances updated_at. nil pointers leave the corresponding column untouched. Returns the updated row for callers that want to echo it back to the client without a separate SELECT.

Returns ErrForgeSessionNotFound when the UPDATE matched no rows so callers can distinguish "row missing" from "DB unavailable" — without this the handler would silently 200 OK on a vanished session id.

func (*DB) UpdatePRBellowsAssignment added in v0.16.0

func (db *DB) UpdatePRBellowsAssignment(id int, managed, manuallyAssigned bool) error

UpdatePRBellowsAssignment sets both the bellows_managed and bellows_manually_assigned flags for a PR atomically. The manual flag records that a user explicitly assigned (or released) bellows via IPC, so the reconcile loop can distinguish user-initiated assignments from legacy auto-adoption and avoid clobbering them.

func (*DB) UpdatePRBellowsManaged added in v0.7.0

func (db *DB) UpdatePRBellowsManaged(id int, managed bool) error

UpdatePRBellowsManaged sets the bellows_managed flag for a PR. When true, bellows will run lifecycle workers (quench, burnish, rebase) for this PR.

func (*DB) UpdatePRLifecycle

func (db *DB) UpdatePRLifecycle(id int, ciFixCount, reviewFixCount, rebaseCount int, ciPassing bool) error

UpdatePRLifecycle updates the lifecycle state of a PR.

func (*DB) UpdatePRMergeability

func (db *DB) UpdatePRMergeability(id int, ciPassing, isConflicting, hasUnresolvedThreads, hasPendingReviews, hasApproval bool) error

UpdatePRMergeability persists the mergeability state for a PR. Called by Bellows on each poll to keep the ready-to-merge view current. hasApproval is stored so the last known approval state survives daemon restarts, allowing Bellows to correctly seed lastSnap from DB and detect the ready-to-merge transition even for PRs that were already ready when the daemon restarted.

func (*DB) UpdatePRStatus

func (db *DB) UpdatePRStatus(id int, status PRStatus) error

UpdatePRStatus updates a PR's status and last_checked time by its internal database ID.

func (*DB) UpdatePRStatusIfNeedsFix

func (db *DB) UpdatePRStatusIfNeedsFix(id int, status PRStatus) error

UpdatePRStatusIfNeedsFix conditionally updates a PR's status only when the current status is needs_fix. This prevents overwriting a terminal status (e.g. merged or closed) if the PR transitions while a fix worker is running.

func (*DB) UpdatePRTitle added in v0.7.0

func (db *DB) UpdatePRTitle(id int, title string) error

UpdatePRTitle sets the title for a PR record.

func (*DB) UpdateWicketIssue added in v0.11.0

func (db *DB) UpdateWicketIssue(issue WicketIssue) error

UpdateWicketIssue updates the mutable fields of an existing wicket_issues row, identified by repo + issue_number.

func (*DB) UpdateWorkerLogPath

func (db *DB) UpdateWorkerLogPath(id string, logPath string) error

UpdateWorkerLogPath updates the log path of a worker.

func (*DB) UpdateWorkerPID

func (db *DB) UpdateWorkerPID(id string, pid int) error

UpdateWorkerPID updates the PID of a running worker.

func (*DB) UpdateWorkerPhase

func (db *DB) UpdateWorkerPhase(id string, phase string) error

UpdateWorkerPhase updates the active pipeline phase for a worker.

func (*DB) UpdateWorkerStatus

func (db *DB) UpdateWorkerStatus(id string, status WorkerStatus) error

UpdateWorkerStatus updates a worker's status and optionally sets completed_at.

func (*DB) UpdateWorkerTitle added in v0.12.0

func (db *DB) UpdateWorkerTitle(id string, title string) error

UpdateWorkerTitle updates the display title of a worker.

func (*DB) UpsertProviderQuota

func (db *DB) UpsertProviderQuota(pv string, q *provider.Quota) error

UpsertProviderQuota creates or updates a provider's quota record.

func (*DB) UpsertRetry

func (db *DB) UpsertRetry(r *RetryRecord) error

UpsertRetry creates or updates a retry record.

func (*DB) WorkersByAnvil

func (db *DB) WorkersByAnvil(anvil string) ([]Worker, error)

WorkersByAnvil returns all workers for a given anvil.

func (*DB) WorkersByBead added in v0.16.0

func (db *DB) WorkersByBead(beadID, anvil string, limit int) ([]Worker, error)

WorkersByBead returns workers for a specific bead, optionally filtered by anvil, ordered most recent first. Limit 0 means no limit.

type Event

type Event struct {
	ID        int
	Timestamp time.Time
	Type      EventType
	Message   string
	BeadID    string
	Anvil     string
}

Event represents a logged event.

type EventType

type EventType string

EventType categorizes events in the log.

const (
	EventDaemonStarted EventType = "daemon_started"
	EventDaemonStopped EventType = "daemon_stopped"
	EventConfigReload  EventType = "config_reload"
	EventOrphanCleanup EventType = "orphan_cleanup"
	// EventPoll is retained as a constant for historical rows already in the
	// events table (and so referring code does not need to be churned), but it
	// is no longer written by the daemon: successful polls are tracked only in
	// the in-memory map on Daemon (see Daemon.lastPollMap). Failed polls
	// continue to be persisted as EventPollError so they remain visible.
	EventPoll                 EventType = "poll"
	EventPollError            EventType = "poll_error"
	EventBeadClaimed          EventType = "bead_claimed"
	EventSmithStarted         EventType = "smith_started"
	EventSmithDone            EventType = "smith_done"
	EventSmithStats           EventType = "smith_stats"
	EventSmithFailed          EventType = "smith_failed"
	EventSmithRecheck         EventType = "smith_recheck"
	EventWardenStarted        EventType = "warden_started"
	EventWardenPass           EventType = "warden_pass"
	EventWardenReject         EventType = "warden_reject"
	EventWardenHardReject     EventType = "warden_hard_reject"
	EventTemperStarted        EventType = "temper_started"
	EventTemperPassed         EventType = "temper_passed"
	EventTemperFailed         EventType = "temper_failed"
	EventBellowsStarted       EventType = "bellows_started"
	EventCIFailed             EventType = "ci_failed"
	EventQuenchStarted        EventType = "ci_fix_started"
	EventQuenchSuccess        EventType = "ci_fix_success"
	EventQuenchFailed         EventType = "ci_fix_failed"
	EventReviewChanges        EventType = "review_changes"
	EventBurnishStarted       EventType = "review_fix_started"
	EventBurnishSuccess       EventType = "review_fix_success"
	EventBurnishFailed        EventType = "review_fix_failed"
	EventReviewThreadResolved EventType = "review_thread_resolved"
	EventBurnishSmithError    EventType = "review_fix_smith_error"
	EventBurnishTemperFailed  EventType = "review_fix_temper_failed"
	EventPRCreated            EventType = "pr_created"
	EventPRMerged             EventType = "pr_merged"
	EventPRClosed             EventType = "pr_closed"
	EventPRConflicting        EventType = "pr_conflicting"
	EventPRNeedsFix           EventType = "pr_needs_fix"
	EventRebaseStarted        EventType = "rebase_started"
	EventRebaseSuccess        EventType = "rebase_success"
	EventRebaseFailed         EventType = "rebase_failed"
	EventLifecycleExhausted   EventType = "lifecycle_exhausted"
	EventClarificationNeeded  EventType = "clarification_needed"
	EventClarificationCleared EventType = "clarification_cleared"
	EventRetryReset           EventType = "retry_reset"
	EventRetryCleared         EventType = "retry_cleared"
	EventBeadDismissed        EventType = "bead_dismissed"
	EventSchematicStarted     EventType = "schematic_started"
	EventSchematicDone        EventType = "schematic_done"
	EventSchematicSkipped     EventType = "schematic_skipped"
	EventDispatchFailed       EventType = "dispatch_failed"
	EventDispatchCircuitBreak EventType = "dispatch_circuit_break"
	// EventDispatchBlockedStrandedBranch fires when the pre-dispatch remote
	// check finds origin/forge/<bead-id> with commits not reachable from the
	// base ref and no PR — a prior worker pushed but never opened a PR.
	EventDispatchBlockedStrandedBranch EventType = "dispatch_blocked_stranded_branch"
	EventRateLimited                   EventType = "rate_limited"
	EventCostLimitHit                  EventType = "cost_limit_hit"
	EventSchematicSubBead              EventType = "schematic_sub_bead"
	EventWorkerStalled                 EventType = "worker_stalled"
	EventBeadTagged                    EventType = "bead_tagged"
	EventBeadClosed                    EventType = "bead_closed"
	EventPRReadyToMerge                EventType = "pr_ready_to_merge"
	EventPRMergeRequested              EventType = "pr_merge_requested"
	EventPRMergeFailed                 EventType = "pr_merge_failed"
	EventPRAutoMerged                  EventType = "pr_auto_merged"
	EventError                         EventType = "error"
	EventBeadRecovered                 EventType = "bead_recovered"
	EventBeadStopped                   EventType = "bead_stopped"
	EventDepcheckStarted               EventType = "depcheck_started"
	EventDepcheckPassed                EventType = "depcheck_passed"
	EventDepcheckFound                 EventType = "depcheck_found"
	EventDepcheckFailed                EventType = "depcheck_failed"
	EventDepcheckBeadCreated           EventType = "depcheck_bead_created"
	EventDepcheckDedup                 EventType = "depcheck_dedup"
	EventVulnScanStarted               EventType = "vuln_scan_started"
	EventVulnScanDone                  EventType = "vuln_scan_done"
	EventVulnScanFailed                EventType = "vuln_scan_failed"
	EventVulnScanCycleDone             EventType = "vuln_scan_cycle_done"
	EventVulnBeadCreated               EventType = "vuln_bead_created"
	EventWardenRerun                   EventType = "warden_rerun"
	EventApproveAsIs                   EventType = "approve_as_is"
	EventForceSmith                    EventType = "force_smith"
	EventAutoLearnError                EventType = "auto_learn_error"
	EventAutoLearnSkipped              EventType = "auto_learn_skipped"
	EventAutoLearnRules                EventType = "auto_learn_rules"
	EventWardenRuleLearned             EventType = "warden_rule_learned"
	EventBeadAutoClosed                EventType = "bead_auto_closed"
	EventNoChangesNeeded               EventType = "no_changes_needed"
	EventPRCreationFailed              EventType = "pr_creation_failed"
	EventPRAlreadyExists               EventType = "pr_already_exists"

	// Crucible events — parent bead orchestration with children on feature branches.
	EventCrucibleStarted         EventType = "crucible_started"
	EventCrucibleChildDispatched EventType = "crucible_child_dispatched"
	EventCrucibleChildPRCreated  EventType = "crucible_child_pr_created"
	EventCrucibleChildMerged     EventType = "crucible_child_merged"
	EventCrucibleChildFailed     EventType = "crucible_child_failed"
	EventCrucibleFinalPR         EventType = "crucible_final_pr"
	EventCrucibleComplete        EventType = "crucible_complete"
	EventCruciblePaused          EventType = "crucible_paused"

	// Questgiver/Adventurer events — E2E quest execution and bead creation.
	EventQuestgiverScanDone EventType = "questgiver_scan_done"
	EventAdventurerStarted  EventType = "adventurer_started"
	EventAdventurerPassed   EventType = "adventurer_passed"
	EventAdventurerFailed   EventType = "adventurer_failed"
	EventTestBeadCreated    EventType = "test_bead_created"

	// Smelter events — batch warden rule flushing.
	EventSmelterStarted   EventType = "smelter_started"
	EventSmelterFlushed   EventType = "smelter_flushed"
	EventSmelterFailed    EventType = "smelter_failed"
	EventSmelterCycleDone EventType = "smelter_cycle_done"

	// Wicket events — GitHub issue triage monitor.
	EventWicketScanDone        EventType = "wicket_scan_done"
	EventWicketIssueTriage     EventType = "wicket_issue_triage"
	EventWicketBeadCreated     EventType = "wicket_bead_created"
	EventWicketClarification   EventType = "wicket_clarification"
	EventWicketRejected        EventType = "wicket_rejected"
	EventWicketFlaggedHuman    EventType = "wicket_flagged_human"
	EventWicketError           EventType = "wicket_error"
	EventWicketDispatchConfirm EventType = "wicket_dispatch_confirm"
	EventWicketPRLinked        EventType = "wicket_pr_linked"
	EventWicketIssueClosed     EventType = "wicket_issue_closed"
	EventWicketIssueStaleClose EventType = "wicket_issue_stale_close"

	// Pipeline hook events.
	EventHookFailed EventType = "hook_failed"

	// Recovery circuit breaker — orphan bead recovery exhausted retries.
	EventRecoveryCircuitBreak EventType = "recovery_circuit_break"
)

type ExhaustedPR

type ExhaustedPR struct {
	ID             int
	Number         int
	Anvil          string
	BeadID         string
	CIFixCount     int
	ReviewFixCount int
	RebaseCount    int
	Reason         string
}

ExhaustedPR represents a PR that has exhausted its CI-fix, review-fix, or rebase attempt limits and needs human attention.

type ForgeSession added in v0.16.0

type ForgeSession struct {
	ID        int64
	Title     string
	Status    string
	Anvil     string
	CreatedBy string
	CreatedAt time.Time
	UpdatedAt time.Time
	// Stage is the current Beads-Forge stage (drafting | grilling | ready).
	// The AI integration bead added these values; sessions created before the
	// migration get the default "drafting" via the column default.
	Stage string
	// Plan is the latest implementation plan emitted for the session, in
	// markdown. Empty in drafting until the user requests a plan; updated by
	// the AI worker each time a fresh plan is generated.
	Plan string
}

ForgeSession is one conversation row that backs the Beads-Forge page in Hearth 2.0. Each session pairs a list of messages (forge_session_messages) with metadata about who started it and what stage it is in.

Status values are an open-ended TEXT to avoid an enum migration when later beads add stages (plan_ready, beads_created, etc.). The foundation bead uses only "draft" and "archived".

type ForgeSessionMessage added in v0.16.0

type ForgeSessionMessage struct {
	ID        int64
	SessionID int64
	Role      string
	Content   string
	CreatedAt time.Time
	// Kind is one of: text (default chat turn), plan (claude-emitted plan
	// markdown), question (structured question with options), answer (user's
	// answer to a question), status (system status message — e.g. stage
	// transitions). The state layer treats the value as opaque text.
	Kind string
	// Metadata is an optional JSON payload tied to Kind. For "question" it
	// holds the options + recommendation; for "answer" it pins the
	// question_id and option_id. Empty string means no metadata.
	Metadata string
}

ForgeSessionMessage is one entry in a forge session conversation. Roles follow the chat-style convention: "user", "assistant", "system".

Kind extends the message with a structured payload type used to render non-text turns in the chat view (plan emissions, structured questions from the grilling stage, and the user's answers). Metadata holds a JSON-encoded payload whose shape depends on Kind.

type ForgeSessionWithCount added in v0.16.0

type ForgeSessionWithCount struct {
	ForgeSession
	MessageCount int
}

ForgeSessionWithCount pairs a session with its pre-computed message count. Returned by ListForgeSessionsWithCounts to avoid a separate COUNT per row.

type ListWicketIssuesOpts added in v0.11.0

type ListWicketIssuesOpts struct {
	// Repo filters to a specific "owner/repo" string. Empty = all repos.
	Repo string
	// State filters to a specific lifecycle state. Empty = all states.
	State string
	// Limit caps the number of rows returned. 0 = no limit.
	Limit int
}

ListWicketIssuesOpts filters for ListWicketIssues.

type NeedsAttentionBead

type NeedsAttentionBead struct {
	BeadID              string
	Anvil               string
	Title               string
	Description         string
	Reason              string
	NeedsHuman          bool
	ClarificationNeeded bool
	FailureCount        int
	// PRID is non-zero when this item originates from an exhausted PR rather
	// than the retries table. The caller uses this to route retry/dismiss
	// actions to the correct DB operation.
	PRID     int
	PRNumber int
}

NeedsAttentionBead represents a bead requiring human attention, combining retry metadata with a best-effort title and description lookup from queue_cache or workers.

type OpenPRDetail added in v0.5.0

type OpenPRDetail struct {
	ID                   int
	Number               int
	Anvil                string
	BeadID               string
	Branch               string
	Status               PRStatus
	Title                string
	CIPassing            bool
	IsConflicting        bool
	HasUnresolvedThreads bool
	HasPendingReviews    bool
	HasApproval          bool
	CIFixCount           int
	ReviewFixCount       int
	RebaseCount          int
	IsExternal           bool // true for PRs discovered via GitHub reconciliation (ext-* bead ID)
	BellowsManaged       bool // true when bellows runs lifecycle workers for this PR
}

OpenPRDetail represents an open PR with full status detail for the TUI PR panel.

type OrphanedWorkerInfo added in v0.16.0

type OrphanedWorkerInfo struct {
	WorkerID string
	PRStatus string
}

OrphanedWorkerInfo identifies a bellows monitoring worker whose underlying PR is missing or in a terminal state. PRStatus is empty when no PR row exists.

type PR

type PR struct {
	ID                      int
	Number                  int
	Anvil                   string
	BeadID                  string
	Branch                  string
	BaseBranch              string // Target branch for the PR (empty = repo default base branch)
	Status                  PRStatus
	CreatedAt               time.Time
	LastChecked             *time.Time
	CIFixCount              int
	ReviewFixCount          int
	RebaseCount             int
	CIPassing               bool
	IsConflicting           bool
	HasUnresolvedThreads    bool
	HasPendingReviews       bool
	HasApproval             bool
	Title                   string
	BellowsManaged          bool // true = bellows runs lifecycle workers (quench, burnish, rebase)
	BellowsManuallyAssigned bool // true = a user explicitly assigned bellows via IPC; reconcile must not clobber
}

PR represents a pull request entry.

func (*PR) IsExternal added in v0.7.0

func (p *PR) IsExternal() bool

IsExternal returns true if this PR was discovered via GitHub reconciliation rather than created by a Forge pipeline.

type PRStatus

type PRStatus string

PRStatus represents the lifecycle of a pull request.

const (
	PROpen     PRStatus = "open"
	PRApproved PRStatus = "approved"
	PRMerged   PRStatus = "merged"
	PRClosed   PRStatus = "closed"
	PRNeedsFix PRStatus = "needs_fix"
)

type PendingOrphan added in v0.4.0

type PendingOrphan struct {
	BeadID    string
	Anvil     string
	Title     string
	Branch    string
	CreatedAt time.Time
}

PendingOrphan represents a bead awaiting user decision during orphan recovery.

type PendingRule added in v0.10.0

type PendingRule struct {
	ID        int
	Anvil     string
	RuleYAML  string
	SourcePR  string
	CreatedAt time.Time
}

PendingRule represents a warden rule awaiting human approval.

type ProviderDailyCost added in v0.2.0

type ProviderDailyCost struct {
	Provider      string
	InputTokens   int
	OutputTokens  int
	CacheRead     int
	CacheWrite    int
	EstimatedCost float64
}

ProviderDailyCost holds per-provider cost data for a single day.

type QueueItem

type QueueItem struct {
	BeadID      string
	Anvil       string
	Title       string
	Description string
	Priority    int
	Status      string
	Labels      string       // JSON-encoded []string
	Section     QueueSection // ready / unlabeled / in_progress
	Assignee    string
}

QueueItem represents a cached bead from the daemon's poll.

type QueueSection

type QueueSection string

QueueSection categorises a bead's position in the dispatch pipeline.

const (
	QueueSectionReady      QueueSection = "ready"       // will be auto-dispatched
	QueueSectionUnlabeled  QueueSection = "unlabeled"   // available but not tagged for dispatch
	QueueSectionInProgress QueueSection = "in_progress" // currently being worked on
)

type ReadyToMergePR

type ReadyToMergePR struct {
	ID     int
	Number int
	Anvil  string
	BeadID string
	Branch string
	Title  string
}

ReadyToMergePR represents a PR that meets all conditions for merging.

type RetryRecord

type RetryRecord struct {
	BeadID               string
	Anvil                string
	RetryCount           int
	NextRetry            *time.Time
	NeedsHuman           bool
	ClarificationNeeded  bool
	DispatchFailures     int
	RecoveryFailures     int
	FirstRecoveryFailure *time.Time
	LastError            string
	UpdatedAt            time.Time
}

RetryRecord tracks retry state for a bead.

type WebSession added in v0.16.0

type WebSession struct {
	TokenHash string
	Username  string
	CreatedAt time.Time
	ExpiresAt time.Time
	LastSeen  time.Time
}

WebSession represents an authenticated web UI session.

type WicketAnvilSummary added in v0.11.0

type WicketAnvilSummary struct {
	// Repo is the "owner/repo" string used as the grouping key.
	Repo            string
	OpenCount       int
	NeedsHumanCount int
}

WicketAnvilSummary holds per-repo issue counts for the Hearth TUI Wicket panel.

type WicketIssue added in v0.11.0

type WicketIssue struct {
	ID          int
	Repo        string
	IssueNumber int
	Title       string
	Body        string
	Author      string
	// State is the lifecycle state of this record: "pending", "bead_created",
	// "ask_clarify", "needs_human", "rejected", "dispatched", "stale", "merged", "closed".
	State        string
	TriageAction string
	TriageReason string
	// BeadID is the bead identifier created for this issue; empty when no
	// bead has been created yet.
	BeadID string
	// PRNumber is the pull request number linked to this issue (0 when no PR yet).
	PRNumber int
	// PRUrl is the URL of the linked pull request.
	PRUrl string
	// CommentCount is the number of comments seen on the last check (used to detect new replies).
	CommentCount int
	// AuthorRepliedAt records the last time the issue author posted a comment.
	// Used for stale detection to avoid false positives when non-author comments
	// (e.g. bot comments, bystander replies) bump UpdatedAt.
	AuthorRepliedAt *time.Time
	CreatedAt       time.Time
	UpdatedAt       time.Time
	ProcessedAt     *time.Time
}

WicketIssue is the database representation of a GitHub issue that has been seen (and optionally triaged) by the Wicket monitor.

type Worker

type Worker struct {
	ID          string
	BeadID      string
	Anvil       string
	Branch      string
	PID         int
	Status      WorkerStatus
	Phase       string // active component: smith|temper|warden|bellows|idle
	Title       string // bead title for display in hearth
	PRNumber    int    // PR number for bellows-triggered workers (quench/burnish/rebase)
	StartedAt   time.Time
	CompletedAt *time.Time
	LogPath     string
	// StaleTimeout is an optional per-worker override for stale-detection. When > 0,
	// the worker is checked independently of the global stale_interval using this
	// custom threshold. Lifecycle workers (quench/burnish/rebase) use this to get
	// stale detection even though they are excluded from the global background-phase
	// check. Stored as seconds in the DB.
	StaleTimeout time.Duration
}

Worker represents a Smith worker entry.

type WorkerStatus

type WorkerStatus string

WorkerStatus represents the lifecycle state of a Smith worker.

const (
	WorkerPending    WorkerStatus = "pending"
	WorkerRunning    WorkerStatus = "running"
	WorkerReviewing  WorkerStatus = "reviewing"
	WorkerMonitoring WorkerStatus = "monitoring"
	WorkerDone       WorkerStatus = "done"
	WorkerFailed     WorkerStatus = "failed"
	WorkerTimeout    WorkerStatus = "timeout"
	WorkerStalled    WorkerStatus = "stalled"
)

Jump to

Keyboard shortcuts

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