intent

package
v0.0.0-...-86b21bc Latest Latest
Warning

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

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

Documentation

Overview

Package intent implements the operator-pinned session focus directive (gm-v5z2.3, work-planning.md §4 Layer 1.3).

An Intent is a small struct attached to a session that biases candidate selection toward a particular slice of work. Three orthogonal restrictors:

  • EpicID: only candidates descended from this epic match.
  • Label: only candidates carrying this bd label match.
  • BeadIDRegex: only candidates whose id matches this regex match.

Multiple restrictors AND together. At least one MUST be set for the intent to be valid; an empty Intent (no restrictors) is the "cleared" state and Match returns true for every candidate.

Selection (gm-v5z2.7) reads ctx.intent and demotes out-of-intent candidates by Intent.DemotionFactor (default 0.4). The rule is SOFT: a P0 bead outside intent can still beat a P3 bead inside intent if the score gap is wide enough.

Storage and the CLI surface (`gemba session focus`) live in internal/planner/intent/store.go and internal/cli/session_focus.go.

Index

Constants

View Source
const DefaultDemotionFactor = 0.4

DefaultDemotionFactor is the multiplier applied to a candidate score when the candidate falls outside the operator-pinned intent. 0.4 means a 0.8 in-intent score beats a 1.0 out-of-intent score. Operators can override per intent.

Variables

This section is empty.

Functions

func Match

func Match(intent Intent, c Candidate) bool

Match reports whether c is in-intent. Empty intent matches everything (the cleared state). Multiple restrictors AND together.

EpicID match: candidate.EpicID must equal intent.EpicID. Epic descendency (Intent says "gm-e3", candidate's epic is "gm-e3.5", gm-e3.5 is descendent of gm-e3) is the SELECTION layer's job — pre-resolve the epic to its descendant set before calling Match so this stays O(1) per candidate.

Label match: candidate.Labels must contain intent.Label.

BeadIDRegex match: regex must compile (validated upstream) and match the candidate's BeadID. A regex that fails to compile here (it shouldn't — Validate should have caught it) is treated as "no match" rather than panicking.

Types

type AuditAction

type AuditAction string

AuditAction names the kind of change recorded in session_intent_audit. Used both as the database column value and the CLI's --json output discriminator.

const (
	AuditSet   AuditAction = "set"
	AuditClear AuditAction = "clear"
)

type AuditEntry

type AuditEntry struct {
	ID        int64       `json:"id"`
	SessionID string      `json:"session_id"`
	Action    AuditAction `json:"action"`
	Prior     *Intent     `json:"prior,omitempty"`
	Next      *Intent     `json:"next,omitempty"`
	At        time.Time   `json:"at"`
	// Actor names the operator (or service) that triggered the
	// change. Free-text; the CLI stamps "cli:<user>" when called
	// via gemba session focus, an SPA mutation lands "spa:<user>".
	Actor string `json:"actor,omitempty"`
}

AuditEntry is one row in session_intent_audit. Captures the full before/after intent state alongside the wall-clock timestamp so a retrospective can attribute "the session was focused on gm-e3 when this bead landed" without re-reading the live row.

type Candidate

type Candidate struct {
	BeadID core.WorkItemID
	EpicID core.WorkItemID
	Labels []string
}

Candidate is the minimal projection of a WorkItem that Match needs. Keeping it narrow lets the selection layer feed in a pre-computed view of the candidate set without hauling the full core.WorkItem through every per-bead loop.

type Intent

type Intent struct {
	SessionID string `json:"session_id"`

	EpicID      string `json:"epic_id,omitempty"`
	Label       string `json:"label,omitempty"`
	BeadIDRegex string `json:"bead_id_regex,omitempty"`

	// Rationale is freeform "why this focus" text the operator
	// supplies. Surfaces in the audit log and the coach UI.
	Rationale string `json:"rationale,omitempty"`

	// DemotionFactor is the multiplier applied to out-of-intent
	// candidate scores. Zero or negative defaults to
	// DefaultDemotionFactor on read.
	DemotionFactor float64 `json:"demotion_factor,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Intent is the operator's session-pinned focus directive. Three orthogonal restrictors AND together; at least one MUST be set for a valid intent. An empty Intent (no restrictors) is the "cleared" state.

Stored as one row per session in the session_intents dolt table. Cleared on session end by the lifecycle hooks (gm-v5z2.7's integration); intents do NOT survive across sessions.

func (Intent) EffectiveDemotionFactor

func (i Intent) EffectiveDemotionFactor() float64

EffectiveDemotionFactor returns the demotion multiplier with the default applied. Reads should always go through this rather than touching DemotionFactor directly so a zero value (the default JSON unmarshal landing) doesn't silently zero out scores.

func (Intent) IsZero

func (i Intent) IsZero() bool

IsZero reports whether the intent carries no restrictors. Treat it as "no focus directive set" — Match returns true for every candidate, and the selection layer skips its demotion pass.

func (Intent) Validate

func (i Intent) Validate() error

Validate enforces the "at least one restrictor" rule and that BeadIDRegex parses. Returns nil on the empty (cleared) intent — callers that want to forbid empties check IsZero themselves.

type SetInput

type SetInput struct {
	SessionID      string
	EpicID         string
	Label          string
	BeadIDRegex    string
	Rationale      string
	DemotionFactor float64
	Actor          string
	Now            func() time.Time
}

SetInput is the input to Set. SessionID is required; the restrictor fields validate via Intent.Validate before any SQL fires. Now is injected for deterministic test runs.

type Store

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

Store persists session intents + the audit log. Wraps a *sql.DB pool the caller owns; nil db disables the writer (callers branch on `if store != nil` rather than feature-flag).

func NewStore

func NewStore(db *sql.DB) *Store

NewStore wraps the given *sql.DB. Returns nil when db is nil so the wiring stays branch-free at the call site.

func (*Store) Audit

func (s *Store) Audit(ctx context.Context, sessionID string, limit int) ([]AuditEntry, error)

Audit returns the audit history for sessionID, newest first. limit ≤ 0 returns the full history; the caller's responsibility to set a reasonable bound for hot paths.

func (*Store) Clear

func (s *Store) Clear(ctx context.Context, sessionID string, actor string, nowFn func() time.Time) error

Clear deletes the intent row for SessionID and appends a "clear" audit row. Idempotent: clearing an already-cleared session returns nil + still writes the audit row.

func (*Store) EnsureSchema

func (s *Store) EnsureSchema(ctx context.Context) error

EnsureSchema applies the embedded planner.SchemaSQL DDL — idempotent CREATE TABLE IF NOT EXISTS for the intent tables alongside the rest of the planner schema. Safe to call multiple times; the rest of the planner has its own EnsureSchema and they share the embedded SQL file so duplicate calls are fine.

func (*Store) Get

func (s *Store) Get(ctx context.Context, sessionID string) (*Intent, error)

Get reads the live intent for sessionID. Returns (nil, nil) when no row exists — matches the rest of the planner's reader "absent ≠ error" convention.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Intent, error)

List returns every live intent. Used by the CLI's `gemba session focus list` and by the selection layer's per-tick snapshot read.

func (*Store) Set

func (s *Store) Set(ctx context.Context, in SetInput) (*Intent, error)

Set writes (or replaces) the intent for SessionID and appends a "set" audit row. Returns the resulting Intent so the caller can echo the canonical form (e.g. with the default demotion applied).

Jump to

Keyboard shortcuts

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