smelter

package
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Jun 4, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package smelter batches pending warden rules into PRs. The Smelter periodically queries pending_warden_rules from the state DB, groups them by anvil, appends them to each anvil's .forge/warden-rules.yaml (deduping by rule ID), commits the change, and creates or updates a PR on the forge/warden-learn-batch/<anvil> branch.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConsolidateOptions added in v0.16.0

type ConsolidateOptions struct {
	// AnvilPath is the on-disk path to the anvil root. Both
	// .forge/warden-rules.yaml and .forge/warden-rules.archive.yaml are
	// resolved relative to it.
	AnvilPath string
	// AnvilName is used in log messages and event payloads.
	AnvilName string
	// Consolidator is the AI invocation hook used by Pass 1. When nil, Pass
	// 1 is skipped.
	Consolidator warden.ConsolidationRunner
	// DedupThreshold is the Jaccard similarity threshold (0.0–1.0) above
	// which two rules in the same category are considered duplicates. When
	// <= 0, Pass 1 is skipped.
	DedupThreshold float64
	// ArchiveAfterDays is the staleness threshold in days for Pass 2. When
	// <= 0, Pass 2 is skipped.
	ArchiveAfterDays int
	// Now optionally overrides the reference time used by the staleness
	// pass. Zero defaults to time.Now().UTC().
	Now time.Time
	// EventLogger is an optional callback for surfacing per-pass progress
	// to an external sink. The scheduled smelter wires this to the state
	// DB; CLI invocations may pass nil. Called with (eventName, message).
	EventLogger func(name, message string)
}

ConsolidateOptions configures a single off-cycle three-pass consolidation run against the warden rules file at AnvilPath. The same option set is used by the scheduled smelter loop and by the `forge warden consolidate` CLI command so both code paths share one implementation.

type ConsolidateResult added in v0.16.0

type ConsolidateResult struct {
	// Passes captures the per-pass outcomes in the standard PassResults
	// shape so the same commit-message and summary helpers work for both
	// scheduled and off-cycle runs.
	Passes PassResults
	// Pass1Archived lists the original rules superseded by Pass 1 merges.
	// Surfaced so callers that drive the smelter flushAnvil path (which
	// writes them to the archive store) can reuse the same result.
	Pass1Archived []warden.Rule
	// InitialCount is the rule count loaded from the active file before
	// any pass ran.
	InitialCount int
	// FinalActive is the rule count in the active file after all passes
	// completed and persistence (when any pass produced changes) ran.
	FinalActive int
	// ArchiveCount is the entry count in the archive file after the run.
	// Zero when no archive file exists.
	ArchiveCount int
	// FirstError is the first non-fatal error encountered during Pass 1
	// (typically an AI runner failure for a single cluster). The run
	// proceeds despite this; callers may choose to surface it in their
	// summary.
	FirstError error
}

ConsolidateResult bundles the structured output of a three-pass run. Callers (smelter loop / CLI) use it to render a summary and (in the CLI case) decide whether to print "no changes".

func ConsolidateAnvil added in v0.16.0

func ConsolidateAnvil(ctx context.Context, opts ConsolidateOptions) (ConsolidateResult, error)

ConsolidateAnvil loads the warden rules file under opts.AnvilPath, runs the three smelter passes (Pass 1 cluster consolidation, Pass 2 staleness archive, Pass 3 paths backfill) against the in-memory rules, and persists the results when any pass produced changes. Idempotent: rerunning against an already-consolidated file is a no-op (Passes.HasChanges() will be false in the result).

Pass 1 is skipped when opts.Consolidator is nil or opts.DedupThreshold <= 0. Pass 2 is skipped when opts.ArchiveAfterDays <= 0. Pass 3 has no threshold and runs over rules whose Source carries a copilot:PR#N token and whose Paths field is empty (idempotent on already-backfilled rules).

Behaviour notes:

  • The active rules file is always written when any pass produced changes.
  • The archive file is written only when Pass 1 (duplicates) or Pass 2 (stale rules) produced entries to archive. A Pass 3-only run updates the active rules file but leaves the archive file untouched.
  • When the archive is written, it lands before the active rules file so a partial failure can never leave the active file without a matching archive record.
  • The pending warden rules queue in state.db is NOT consulted — ConsolidateAnvil operates only on what is already on disk. Pulling pending rules into the active file remains the smelter loop's responsibility.
  • When opts.Consolidator returns errors for individual clusters they are aggregated and the first one is reported in the result; the remaining clusters still merge.

type Option added in v0.16.0

type Option func(*Smelter)

Option configures a Smelter at construction time.

func WithArchiveAfterDays added in v0.16.0

func WithArchiveAfterDays(fn func() int) Option

WithArchiveAfterDays supplies a function the Smelter calls at flush time to resolve the staleness threshold (in days) used by Pass 2. A function is used so config hot-reload can take effect without restarting. A nil function or one returning <= 0 disables the staleness pass.

func WithConsolidator added in v0.16.0

func WithConsolidator(runner warden.ConsolidationRunner) Option

WithConsolidator wires the AI invocation hook used by Pass 1 consolidation. When the runner returns a JSON object with the merged rule fields, the Smelter replaces the cluster members in the active rules file and moves the originals to the archive store. Pass nil to disable consolidation.

func WithDedupThreshold added in v0.16.0

func WithDedupThreshold(fn func() float64) Option

WithDedupThreshold supplies a function the Smelter calls at flush time to resolve the current dedup similarity threshold. A function (rather than a value) is used so config hot-reload can take effect without restarting the smelter. A nil function or one returning <= 0 disables consolidation.

type PassResults added in v0.16.0

type PassResults struct {
	// Added lists the IDs of rules newly inserted from the pending queue
	// (Pass 1, before consolidation). IDs are taken from the rule YAML.
	Added []string

	// Consolidated holds the per-cluster merge outcomes from Pass 1.
	Consolidated []warden.MergeResult

	// Archived holds the stale rules moved to the archive store by Pass 2
	// (ArchiveReason="stale"). Pass 1 duplicates archived with
	// ArchiveReason="duplicate" are *not* included here — they are surfaced
	// through Consolidated.
	Archived []warden.ArchivedRule

	// Backfilled lists the IDs of rules whose Paths field was populated by
	// Pass 3 from the changed files of the rule's source PR(s).
	Backfilled []string
}

PassResults aggregates the structured outputs of the smelter's three flush passes for a single anvil. Each field is independent: a pass that did not run, or that produced no changes, contributes an empty slice. The result is fed to buildCommitMessage so a single commit message can summarize all passes in one PR per anvil per run.

func (PassResults) HasChanges added in v0.16.0

func (p PassResults) HasChanges() bool

HasChanges reports whether at least one pass produced an outcome. When false, callers should skip committing — the rules file is byte-identical to main and a commit would be empty.

type Smelter

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

Smelter batches pending warden rules into PRs on a schedule.

func New

func New(db *state.DB, interval time.Duration, anvilPaths map[string]string, opts ...Option) *Smelter

New creates a Smelter. interval controls how often Flush is called; pass 0 to disable scheduled runs (Flush can still be called directly).

func (*Smelter) Flush

func (s *Smelter) Flush(ctx context.Context) error

Flush queries all pending warden rules, groups them by anvil, and for each anvil: creates a worktree, appends rules to warden-rules.yaml (deduping by rule ID), commits, force-pushes to the batch branch, and creates/updates a PR. Flushed rules are deleted from the DB on success.

func (*Smelter) Run

func (s *Smelter) Run(ctx context.Context) error

Run starts the periodic flush loop. Blocks until ctx is canceled. If interval <= 0 at startup, scheduled flushes are paused until UpdateInterval is called with a positive value.

func (*Smelter) UpdateAnvilPaths

func (s *Smelter) UpdateAnvilPaths(paths map[string]string)

UpdateAnvilPaths replaces the set of anvils. Safe to call while Run is active.

func (*Smelter) UpdateInterval

func (s *Smelter) UpdateInterval(d time.Duration)

UpdateInterval signals the Run loop to reset its ticker with d. If d <= 0 the ticker is paused until a positive interval is sent. Safe to call concurrently while Run is active.

Jump to

Keyboard shortcuts

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