Documentation
¶
Overview ¶
Package learning holds the learning-workflow services of the console (`seamark tui`): evidence refresh, distillation, and trigger extraction. Each service is a read-only plan and a confirmed run.
A plan binds the inputs that the user reviewed, for example the index file, the configuration files, and the agent command. A run checks that binding again before it starts work. It reports a conflict instead of a different operation.
The package holds console policy, not an engine. The CLI reaches the same engines without this policy: index.RefreshSources through index.RefreshFixes and index.RefreshReviews, distill.Run, and distill.ExtractTriggers. Golden tests pin the CLI output, so the CLI takes this policy only through a separate decision. The policy differs from the CLI in these points:
- A configuration error blocks a plan, because a paid run must not use a default that the user did not review. The CLI is lenient in two cases. index.RefreshReviews warns about a malformed config.yaml and uses the default review window. `seamark lessons --distill` warns about a malformed lessons.yaml and runs without the pins, so it can propose a pinned pattern again. The CLI trigger extraction refuses both files, as the console does.
- A distillation region must be inside the workspace, because a mistyped region plans an empty run. The CLI keeps its lenient path conversion.
- A plan opens the index read-only, because a preview must not migrate the database. The CLI opener migrates an older schema.
- An agent runs in its own process group, because a stop must end every process that the agent starts. The CLI keeps the terminal process group, so a terminal interrupt reaches the agent.
- A cleanup of stale pending proposals is a separate confirmed action, because it deletes rows that the user did not select. A CLI distillation run does this cleanup itself.
- Trigger extraction asks only selected rows that the pin file and the ledger agree about, so the Pins view and the preview agree. The CLI asks a wider set of rows.
The services stay out of index and distill for two reasons. Only the console uses them. They also need integration and pinfile, and the engines do not import these packages.
Index ¶
- Variables
- func CleanupPending(ws workspace.Workspace, p *DistillPreview) (int, error)
- func RunDistill(ctx context.Context, ws workspace.Workspace, reg *integration.Registry, ...) (*distill.Execution, error)
- func RunExtract(ctx context.Context, ws workspace.Workspace, reg *integration.Registry, ...) (*distill.ExtractExecution, error)
- type Blocker
- type BlockerKind
- type DistillConfigError
- type DistillPreview
- type DistillRequest
- type Effect
- type EffectKind
- type ExtractConfigError
- type ExtractPreview
- type ExtractRequest
- type Outcome
- type Plan
- type Snapshot
- type Window
Constants ¶
This section is empty.
Variables ¶
var ( // ErrDistillNotReady reports an index that is absent, not indexed, // or incompatible. The console links to the explicit build or repair // action; a distillation never builds the index as a side effect. ErrDistillNotReady = errors.New("the index is not ready for distillation") // ErrDistillConfig marks a configuration reader error. The console // is stricter than the CLI here: it must not spend against a default // that the user did not review. DistillConfigError names the file. ErrDistillConfig = errors.New("the distillation configuration is invalid") // ErrDistillBlocked reports a run of a preview with blockers. A // blocked preview discloses; it never authorizes an agent call. ErrDistillBlocked = errors.New("the distillation preview is blocked") // ErrNothingToDistill reports a run of a preview without an eligible // group. No agent starts. A needed cleanup has its own action. ErrNothingToDistill = errors.New("the preview has no eligible evidence group") // ErrNotCleanupOnly reports a cleanup request for a preview that has // eligible groups or no cleanup. A run performs such a cleanup itself. ErrNotCleanupOnly = errors.New("the preview is not a cleanup-only plan") )
var ( // ErrExtractNotReady reports an index that is absent, not indexed, // or incompatible. The console links to the explicit build or // repair action; an extraction never builds the index. ErrExtractNotReady = errors.New("the index is not ready for trigger extraction") // ErrExtractConfig marks a configuration reader error. A paid run // must not use a default that the user did not review. // ExtractConfigError names the file. ErrExtractConfig = errors.New("the trigger extraction configuration is invalid") // ErrExtractBlocked reports a run of a preview with blockers. A // blocked preview discloses; it never authorizes an agent call. ErrExtractBlocked = errors.New("the trigger extraction preview is blocked") // ErrNothingToExtract reports a run of a preview without an eligible // proposal. No agent starts. ErrNothingToExtract = errors.New("the preview has no eligible proposal") )
var ErrConflict = errors.New("the evidence preview is out of date")
ErrConflict reports that an evidence plan no longer matches the workspace. The user reviewed a different state, so the console asks for a new preview instead of running the old one.
var ErrRefreshBlocked = errors.New("the evidence refresh is blocked")
ErrRefreshBlocked reports a run of a plan that has blockers. A blocked plan is a disclosure only; it never authorizes work.
Functions ¶
func CleanupPending ¶
func CleanupPending(ws workspace.Workspace, p *DistillPreview) (int, error)
CleanupPending deletes the stale pending proposals of a cleanup-only preview: no eligible group and a non-empty cleanup. It deletes only the planned IDs that are still pending, and only while the store snapshot and the workspace identity are unchanged. The configuration files do not decide the cleanup set, so a change to them does not block it. It returns the number of deleted rows.
func RunDistill ¶
func RunDistill(ctx context.Context, ws workspace.Workspace, reg *integration.Registry, p *DistillPreview, inv agent.Invoker, progress func(distill.Event), ) (*distill.Execution, error)
RunDistill executes a confirmed preview. Before any work it checks the workspace identity, both bound files, the resolved invocation, and the store snapshot; a changed input returns an error that wraps distill.ErrStale and starts nothing. It then calls distill.Execute with the store boundary checks on, and repeats the file and identity checks before each group.
A nil inv builds the invoker from the preview's invocation; tests pass a fake. That invoker starts the agent in its own process group, so a stop also ends a wrapper script and its model call. The console is in raw mode, so no terminal interrupt reaches the agent. The execution is nil when the run is refused before it starts; otherwise it is the typed result, also after a stop.
func RunExtract ¶
func RunExtract(ctx context.Context, ws workspace.Workspace, reg *integration.Registry, p *ExtractPreview, inv agent.Invoker, onEvent func(distill.ExtractEvent), ) (*distill.ExtractExecution, error)
RunExtract executes a confirmed preview. Before any work it checks the workspace identity, both bound files, the resolved invocation, the working directory, every planned row with its living evidence, and the pin links of the planned rows. A changed input returns an error that wraps distill.ErrExtractStale (and so distill.ErrStale) and starts nothing. It then calls distill.ExecuteExtract, which repeats the file, identity, and link checks before each batch through its Check option.
A nil inv builds the invoker from the preview's invocation; tests pass a fake. That invoker starts the agent in its own process group, so a stop also ends a wrapper script and its model call. The execution is nil when the run is refused before it starts; otherwise it is the typed result, also after a stop.
Types ¶
type Blocker ¶
type Blocker struct {
Kind BlockerKind
// Reason is one safe line: secrets redacted, controls removed.
Reason string
// Path is the file that the user repairs: the database for
// BlockIndex, config.yaml for BlockConfig.
Path string
}
Blocker is one reason why a plan cannot run.
type BlockerKind ¶
type BlockerKind int
BlockerKind names why a plan cannot run.
const ( // BlockIndex means the index database is not ready. The user builds // or repairs it as a separate, explicit action. BlockIndex BlockerKind = iota + 1 // BlockConfig means config.yaml cannot be read or parsed. BlockConfig )
type DistillConfigError ¶
type DistillConfigError struct {
// Path is repository-relative: .seamark/config.yaml or
// .seamark/lessons.yaml.
Path string
// Err is the reader error, with its original text.
Err error
}
DistillConfigError is a configuration reader error that blocks a distillation preview. Path lets the console link to the file or to the matching Settings group.
func (*DistillConfigError) Error ¶
func (e *DistillConfigError) Error() string
Error names the file and the reader error.
func (*DistillConfigError) Unwrap ¶
func (e *DistillConfigError) Unwrap() []error
Unwrap matches both ErrDistillConfig and the reader error, for example reviews.ErrLessonsConfig.
type DistillPreview ¶
type DistillPreview struct {
// Request is the scope as the user chose it, so "prepare remaining
// work" can plan again with the same choices.
Request DistillRequest
// Plan is the frozen group and evidence selection.
Plan *distill.Plan
// Region is the normalized repository-relative region; "" means
// the whole repository.
Region string
// Invocation is the resolved agent command. Dir is empty for the
// default Claude preset and for a custom agent.argv; only Codex sets
// it to RepoRoot. An empty Dir runs the process in the console's
// working directory, which can differ from RepoRoot (seamark -C).
// A surface that discloses the directory must show os.Getwd() then.
Invocation agent.CommandSpec
// Config and Lessons observe config.yaml and lessons.yaml. A run
// refuses the preview when either file changed.
Config integration.FileGuard
Lessons integration.FileGuard
// RepoRoot is the root that the index records; DBPath the database.
RepoRoot string
DBPath string
// PlannedAt is when the plan was made.
PlannedAt time.Time
// Blockers are safe display lines that stop a run, for example a
// missing agent executable. The preview still discloses the plan.
Blockers []string
}
DistillPreview is the read-only preview of one distillation. It is what the user confirms, and RunDistill executes only this preview.
func PlanDistill ¶
func PlanDistill(ws workspace.Workspace, reg *integration.Registry, req DistillRequest) (*DistillPreview, error)
PlanDistill previews one distillation. It reads the index through a read-only store and never runs the agent, prunes, or marks. It checks only that the agent executable is on PATH. A malformed config.yaml or lessons.yaml returns a *DistillConfigError; the CLI would warn and fall back to defaults instead.
func (*DistillPreview) CleanupOnly ¶
func (p *DistillPreview) CleanupOnly() bool
CleanupOnly reports a preview whose only work is the stale-pending cleanup. CleanupPending performs it after its own confirmation.
func (*DistillPreview) NoOp ¶
func (p *DistillPreview) NoOp() bool
NoOp reports a preview with neither eligible groups nor a cleanup. The console shows "Nothing new to distill" and starts nothing.
type DistillRequest ¶
type DistillRequest struct {
// RegionInput is the region as typed. NormalizeRegion reads it
// relative to the workspace root; "" means the whole repository.
RegionInput string
// Limit caps new groups. 0 means distill.DefaultGroupLimit, not
// "all": all groups need the explicit AllGroups choice, because a
// run without a bound must be a deliberate choice.
Limit int
// AllGroups selects every eligible group. Limit must then be 0.
AllGroups bool
}
DistillRequest is the scope the user chose for one distillation.
type Effect ¶
type Effect struct {
// Source is index.SourceFixes, index.SourceReviews, or "" for data
// that belongs to no source.
Source string
Kind EffectKind
// Text is one plain line that names the data and the effect.
Text string
}
Effect is one stored-data disclosure of a plan.
type EffectKind ¶
type EffectKind int
EffectKind tells how a refresh relates to one part of the stored data.
const ( // EffectReplace means the refresh replaces the data. EffectReplace EffectKind = iota + 1 // EffectKeep means the refresh never changes the data. EffectKeep // EffectLater means a later, separate action can change the data // because of the refresh. The refresh itself does not do it. EffectLater )
type ExtractConfigError ¶
type ExtractConfigError struct {
// Path is repository-relative: .seamark/config.yaml or
// .seamark/lessons.yaml.
Path string
// Err is the reader error, with its original text.
Err error
}
ExtractConfigError is a configuration reader error that blocks an extraction preview. Path lets the console link to the file or to the matching Settings group.
func (*ExtractConfigError) Error ¶
func (e *ExtractConfigError) Error() string
Error names the file and the reader error.
func (*ExtractConfigError) Unwrap ¶
func (e *ExtractConfigError) Unwrap() []error
Unwrap matches both ErrExtractConfig and the reader error, for example reviews.ErrLessonsConfig.
type ExtractPreview ¶
type ExtractPreview struct {
// Request is the selection as the user made it, so "prepare remaining"
// can plan again with the same choices.
Request ExtractRequest
// Plan holds the frozen batches, their exact prompts, and the
// selected proposals that are not asked.
Plan *distill.ExtractPlan
// Invocation is the resolved agent command.
Invocation agent.CommandSpec
// WorkDir is where the agent process runs: Invocation.Dir, or the
// console's working directory when Dir is empty. It can differ from
// RepoRoot (seamark -C), so a surface discloses it.
WorkDir string
// Config and Lessons observe config.yaml and lessons.yaml. A run
// refuses the preview when either file changed.
Config integration.FileGuard
Lessons integration.FileGuard
// RepoRoot is the root that the index records; DBPath the database.
RepoRoot string
DBPath string
// LiveNotes holds the live pin note of each planned applied row whose
// linked pin has another note than the recorded proposal. The prompt
// sends the recorded note, so a surface shows both. The values are
// file text: a surface sanitizes them. Nil means no note differs.
LiveNotes map[int64]string
// Blockers are safe display lines that stop a run, for example a
// missing agent executable. The preview still discloses the plan.
Blockers []string
// WorkDirErr is the error that kept PlanExtract from reading the
// console's working directory, or nil. Blockers then also holds its
// display line. A surface uses IsWorkDirBlocker to tell that line
// from an agent blocker, because a setting cannot fix it.
WorkDirErr error
// PlannedAt is when the plan was made.
PlannedAt time.Time
}
ExtractPreview is the read-only preview of one extraction. It is what the user confirms, and RunExtract sends only this preview.
func PlanExtract ¶
func PlanExtract(ws workspace.Workspace, reg *integration.Registry, req ExtractRequest) (*ExtractPreview, error)
PlanExtract previews one extraction of the selected proposals. It reads the index through a read-only store, never runs the agent, and never stamps a row. It checks only that the agent executable is on PATH. A malformed config.yaml or lessons.yaml returns an *ExtractConfigError that names the file, and no preview: a paid run must not use a pin file that the user cannot see. It does not read distill.write, because extraction never writes a pin.
func (*ExtractPreview) Ineligible ¶
func (p *ExtractPreview) Ineligible() []distill.ExtractSkip
Ineligible returns every selected item that the run does not ask: the request's skipped Pins entries first, in request order, then the plan's ineligible proposals, in ascending ID order. It returns a copy.
func (*ExtractPreview) IsWorkDirBlocker ¶
func (p *ExtractPreview) IsWorkDirBlocker(line string) bool
IsWorkDirBlocker reports whether line is the blocker of WorkDirErr. The agent runs in the console's working directory, so only a restart from a readable directory removes this blocker. The check compares the line that PlanExtract built, so a surface never parses the text.
func (*ExtractPreview) NoOp ¶
func (p *ExtractPreview) NoOp() bool
NoOp reports a preview without an eligible proposal. The console shows the ineligible selection and offers no start.
type ExtractRequest ¶
type ExtractRequest struct {
// IDs are the explicitly selected proposals.
IDs []int64
// Skipped are selected Pins entries without exactly one linked
// proposal, with the reason. The preview lists them as ineligible.
// An ID here that IDs also holds is not asked, because the selecting
// surface already found it ineligible.
Skipped []distill.ExtractSkip
}
ExtractRequest is the selection the user made for one extraction. There is no "all": an empty selection plans no agent call.
type Outcome ¶
type Outcome struct {
// Fixes and Reviews report each source. Their Cause field is the
// typed cause, for example index.CauseAuth for a gh login problem.
Fixes index.SourceOutcome
Reviews index.SourceOutcome
// Comments and Kept are the fetched and kept review comment counts.
Comments int
Kept int
// After is the evidence state that the run left. A blocked run reads
// it without git or gh, so its repository stays unresolved. A run
// without a plan has no workspace, so After.CountsErr is set.
After Snapshot
// Conflict is non-nil when the plan went stale: before the start,
// with both sources not attempted, or before a store open, with that
// source failed. Each refused source carries index.CauseRefused. It
// wraps ErrConflict.
Conflict error
// FinishedAt is the time the run returned.
FinishedAt time.Time
}
Outcome reports one evidence run.
func RunRefresh ¶
func RunRefresh(ctx context.Context, p *Plan, fetch reviews.FetchFunc, progress func(index.RefreshEvent), ) (Outcome, error)
RunRefresh runs a confirmed plan. It checks the bound observation first; any change returns a conflict and starts nothing. Before each store open it checks the database again, because the open would create a missing database or migrate an older one. A nil fetch uses gh. A stop through ctx ends the GitHub fetch, and the stored review corpus then stays; the local fix phase always finishes its write.
The two sources succeed or fail on their own. The fix write commits before the fetch starts, so a failed fetch never undoes saved fixes. A fix mine error does not stop the fetch either. A database error in the fix phase is the exception: the engine then stops before any network use, because the review write needs the same database.
type Plan ¶
type Plan struct {
Mode index.SourceMode
Workspace workspace.Workspace
Probe workspace.IndexProbe
// Root is the repository root that the CLI also mines: the root that
// the index records.
Root string
Repository reviews.Repository
RepoState reviews.RepositoryState
// GHPath is where gh is on PATH; "" when it is missing. It is not
// proof of a login.
GHPath string
Window Window
// FixWindowDays is the fix-commit window in days. It is not
// configurable.
FixWindowDays int
// Head is the HEAD commit whose history the fix mine reads. It is ""
// when no commit exists, and for a blocked plan, which reads no git.
Head string
// Network is true when the refresh runs gh, which contacts GitHub.
// Only the combined mode with a GitHub origin sets it. A local
// refresh mines git history only, so it makes no network call.
Network bool
// Current is the stored evidence before the refresh.
Current Snapshot
// Affects discloses what the refresh does to stored data.
Affects []Effect
// Blockers lists why the plan cannot run; empty means ready.
Blockers []Blocker
PlannedAt time.Time
// contains filtered or unexported fields
}
Plan is the read-only preview of one evidence refresh. It performs no network call and no write. RunRefresh runs it only while the bound observation still holds.
func PlanRefresh ¶
PlanRefresh previews an evidence refresh in mode. It writes nothing and runs no gh. An index that is not ready blocks the plan before any git command runs. A malformed config.yaml blocks the plan, so a run never spends network work against a default the user did not review. A missing GitHub origin or gh is no blocker: the local source still runs, and the plan discloses that reviews are unavailable.
type Snapshot ¶
type Snapshot struct {
// Probe is the index database state.
Probe workspace.IndexProbe
// Findings counts the stored findings per source.
Findings map[string]int
// FixFindings sums the fix-mined sources; ReviewFindings is the
// review source; Lessons counts the stored review lessons.
FixFindings int
ReviewFindings int
Lessons int
// DistilledGroups counts the evidence groups that a distillation
// recorded, with or without a proposal. A group can end without a
// proposal, so an empty ledger alone does not prove that none ran.
DistilledGroups int
// FixesMinedAt and ReviewsMinedAt are the last successful mine times
// in Unix seconds; 0 means never or unknown.
FixesMinedAt int64
ReviewsMinedAt int64
// CountsErr is the store read error, or nil.
CountsErr error
// Repository is the GitHub target that the review fetch uses.
Repository reviews.Repository
RepoState reviews.RepositoryState
// GHPath is where gh is on PATH; "" when it is missing. A path is not
// proof of a login, and only a fetch tests access.
GHPath string
// Window is the review window from config.yaml.
Window Window
// ObservedAt is the time of the observation.
ObservedAt time.Time
}
Snapshot is the read-only evidence state of a workspace. Unknown values stay unknown: a stamp of 0 means never mined or unknown, and a non-nil CountsErr means that the counts are not zero but unknown.
type Window ¶
type Window struct {
// Stored is the raw reviews.window_days value; nil when absent.
Stored *int
// Effective is the window in days; -1 means unlimited. It is 0 when
// ConfigErr is set, because no window is known then.
Effective int
// Floor is the count of newest comments that always survive the
// window, so the window is not a strict cutoff.
Floor int
// ConfigErr is the config.yaml read or parse error. A plan blocks on
// it, unlike the CLI, which falls back to the default window.
ConfigErr error
}
Window describes the review window that a refresh applies.