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
- func Match(intent Intent, c Candidate) bool
- type AuditAction
- type AuditEntry
- type Candidate
- type Intent
- type SetInput
- type Store
- func (s *Store) Audit(ctx context.Context, sessionID string, limit int) ([]AuditEntry, error)
- func (s *Store) Clear(ctx context.Context, sessionID string, actor string, nowFn func() time.Time) error
- func (s *Store) EnsureSchema(ctx context.Context) error
- func (s *Store) Get(ctx context.Context, sessionID string) (*Intent, error)
- func (s *Store) List(ctx context.Context) ([]Intent, error)
- func (s *Store) Set(ctx context.Context, in SetInput) (*Intent, error)
Constants ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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.