Documentation
¶
Overview ¶
Package factory runs an implementer agent on a sidecar and loops it through review and validation until the checks pass or attempts run out.
Index ¶
- Constants
- Variables
- func CommitMessage(prompt, runID string, o Outcome) string
- func DefaultLogPath(runID string) (string, error)
- func Feedback(checks []Check) string
- func LoadChecks(dir string, optional bool, cfg *config.ProjectConfig, noValidate bool) ([]review.Prompt, []config.Command, error)
- func Members(impl *sidecar.PoolEntry, ids []string) []*sidecar.PoolEntry
- func Passed(checks []Check) bool
- func PoolName(runID string) string
- func PoolSize(reviewers int) int
- func ReviewerCount(requested int, prompts []review.Prompt) int
- func ValidationCommands(cmds []config.Command) []config.Command
- type Activity
- type Change
- type Check
- type Event
- type EventKind
- type Implementer
- type Kind
- type Loop
- type Outcome
- type Relay
- type Report
- type Result
- type ReviewerTree
- type RunOptions
- type Sidecars
- func (s *Sidecars) Check(ctx context.Context, round int) ([]Check, error)
- func (s *Sidecars) Collect(ctx context.Context) (Change, error)
- func (s *Sidecars) Implement(ctx context.Context, prompt string) (Turn, error)
- func (s *Sidecars) Prepare(ctx context.Context) error
- func (s *Sidecars) Pull(ctx context.Context) error
- type Status
- type Steps
- type Turn
- type Worktree
Constants ¶
const DefaultImplementTimeout = 30 * time.Minute
DefaultImplementTimeout bounds one implementer turn. A turn writes code and may run the tests several times, so it gets longer than a review.
const LogDefault = "default"
LogDefault is the RunOptions.Log that puts the run's log at DefaultLogPath.
Variables ¶
var ErrNothingToCheck = errors.New("nothing to check the implementer's work with")
ErrNothingToCheck means a run has neither review prompts nor validation commands, so its loop would pass whatever the implementer wrote.
Functions ¶
func CommitMessage ¶ added in v0.7.197
CommitMessage is the work's commit message: the prompt's first line as the subject, the prompt in full below it, and how the run ended.
func DefaultLogPath ¶ added in v0.7.197
DefaultLogPath is where a run's log goes when no path is given. The run ID is already the run's start time, so it alone names the file.
func Feedback ¶
Feedback renders the failed checks of a round as the implementer's next prompt, or "" when nothing failed. Errored checks are left out: they say nothing about the code, and asking the implementer to fix them would send it chasing infrastructure.
func LoadChecks ¶ added in v0.7.197
func LoadChecks(dir string, optional bool, cfg *config.ProjectConfig, noValidate bool) ([]review.Prompt, []config.Command, error)
LoadChecks loads what the implementer's work is checked with: the review prompts in dir and, unless noValidate, cfg's validation commands. With optional, a missing or empty dir means no reviews rather than an error, so validation commands alone may do.
func Members ¶ added in v0.7.197
Members describes every sidecar in a pool other than the implementer, the ones its tree is relayed to. ids are the pool's, once it is synced.
func Passed ¶
Passed reports whether every check passed. An errored check is not a pass: a round where a review could not run has not shown the code is clean.
func PoolName ¶ added in v0.7.197
PoolName names a run's sidecar pool. Each run has a pool of its own, rather than one per project reused as `chunk review` does: every member is synced from the run's worktree and gets a baseline commit on top of it, which a later run's sync does not expect. Two runs in one project must not share sidecars either.
func PoolSize ¶ added in v0.7.197
PoolSize is how many sidecars a run needs: the implementer, which runs the validation commands too, and one per reviewer.
func ReviewerCount ¶ added in v0.7.197
ReviewerCount is how many reviewer sidecars a run with prompts needs when asked for requested: one per prompt unless fewer are asked for, and none without prompts.
Types ¶
type Activity ¶
type Activity struct {
// Tool is the tool used, or "" for text the implementer wrote.
Tool string
// Detail is the tool's target (a file, a command) or the text.
Detail string
}
Activity is one thing the implementer did, for display.
type Change ¶
type Change struct {
// Fingerprint identifies the content of the change, so a round in which
// the implementer changed nothing can be detected.
Fingerprint string
// Stat is git's one-line summary, such as "3 files changed, 40 insertions(+)".
Stat string
}
Change is the implementer's work so far, relative to the baseline.
type Check ¶
type Check struct {
Name string
Kind Kind
Status Status
SidecarID string
Duration time.Duration
// Feedback is what the implementer is told when the check failed.
Feedback string
// Error is why the check could not run, for StatusErrored.
Error string
// Findings are a review's findings, kept as data for display.
Findings []review.Finding
// Prose is a review's prose alongside its findings, kept for display.
Prose string
// ExitCode and Output are a validation command's exit code and the tail of
// its output, kept for display and the run's log.
ExitCode int
Output string
}
Check is the outcome of one review or validation command in one round. A review is treated like any other command run on a sidecar: it passes or fails, and a failure carries feedback for the implementer.
func FromCommand ¶
func FromCommand(name, sidecarID string, exitCode int, output string, d time.Duration, runErr error) Check
FromCommand converts one validation command's run into a check. runErr is a failure to run the command at all, as opposed to it exiting non-zero.
func FromReview ¶
FromReview converts one review result, run with structured findings, into a check. A review fails only on findings worth changing (high or medium), the same bar the session loop uses, so the implementer is not sent round after round to polish style remarks. Lower findings are kept for display. A review that could not run errored.
type EventKind ¶
type EventKind int
EventKind identifies a step of the loop for display.
const ( EventImplementing EventKind = iota // a turn started; Round, Prompt EventImplemented // a turn ended; Round, Turn EventCollected // the work was collected; Round, Change EventChecking // checks started; Round EventChecked // checks ended; Round, Checks )
Event kinds, in the order a round emits them.
type Implementer ¶
type Implementer struct {
Exec review.Execer
Entry *sidecar.PoolEntry
Credential review.Credential
BaseURL string
Model string
Timeout time.Duration
OnActivity func(Activity)
// contains filtered or unexported fields
}
Implementer runs Claude Code on a sidecar to write code. Every turn resumes the same session, so feedback arrives with the context of the work so far.
type Kind ¶
type Kind string
Kind is what produced a check: a review prompt or a validation command.
type Loop ¶
type Loop struct {
// Attempts is the most rounds to check. Each round after the first starts
// with an implementer turn fixing the previous round's failures.
Attempts int
OnEvent func(Event)
}
Loop drives the implementer through rounds of review and validation.
type Outcome ¶
type Outcome struct {
Result Result
// Rounds is how many rounds were checked.
Rounds int
Change Change
// Checks are the last round's checks.
Checks []Check
}
Outcome is the end state of a loop.
type Relay ¶
type Relay struct {
// contains filtered or unexported fields
}
Relay carries the implementer's workspace to the reviewers through the run's worktree on this machine. Sidecars cannot reach each other, so the implementer's files are pulled into the worktree with rsync and pushed from there to each reviewer the same way the developer's own tree is synced.
A pull mirrors the implementer's files into the worktree with --delete, so the directory must be one chunk owns. It leaves every .git alone, nested ones included: the worktree keeps its own, and the implementer's git config and hooks never reach this machine, where git will run on the files.
type Report ¶ added in v0.7.197
type Report struct {
RunID string
// Worktree is the run's worktree. It is kept once the implementer has
// started, and removed, with its branch, if the run failed before that.
Worktree Worktree
// Started reports whether the implementer started.
Started bool
// Committed reports whether the work was committed on Worktree.Branch.
Committed bool
Outcome Outcome
// KeptSidecars are the sidecars left running with KeepSidecars.
KeptSidecars []string
// Log is the path of the run's log, if it kept one.
Log string
}
Report is what a run left behind.
func Run ¶ added in v0.7.197
func Run(ctx context.Context, opts RunOptions) (rep Report, err error)
Run makes the run's worktree and sidecars, runs the loop, and commits the implementer's work on the run's branch. A failure before the loop starts says which step failed; one in the loop is returned as Loop.Run returns it, with the report so far. The work is committed either way once the implementer has started, so it is not lost with the sidecars.
type Result ¶
type Result string
Result is why the loop stopped.
const ( // ResultPassed means every check passed. ResultPassed Result = "passed" // ResultExhausted means checks still failed when attempts ran out. ResultExhausted Result = "exhausted" // ResultStuck means the implementer changed nothing in response to // feedback, so another round would review the same code again. ResultStuck Result = "stuck" // ResultNoChange means the implementer's work is empty: it changed // nothing, or reverted everything it had changed. ResultNoChange Result = "no_change" )
Loop results.
type ReviewerTree ¶ added in v0.7.197
type ReviewerTree struct {
Round int
SidecarID string
// Fingerprint is the reviewer's change; Want is the implementer's.
Fingerprint string
Want string
// Err is why the reviewer's fingerprint could not be read.
Err error
}
ReviewerTree compares the change one reviewer is about to review with the implementer's, by fingerprint.
func (ReviewerTree) Matches ¶ added in v0.7.197
func (t ReviewerTree) Matches() bool
Matches reports whether the reviewer has the implementer's change.
type RunOptions ¶ added in v0.7.197
type RunOptions struct {
// Root is the developer's repository root. The run's worktree is made from
// it, and the pool's state is kept in it, where the dashboard finds it.
Root string
// Prompt is what the implementer is asked to do.
Prompt string
// Attempts is the most rounds to check.
Attempts int
// Reviewers is how many reviewer sidecars to run; see ReviewerCount.
Reviewers int
Prompts []review.Prompt
Commands []config.Command
Client *circleci.Client
OrgID string
Image string
// KeepSidecars leaves the sidecars running when the run ends.
KeepSidecars bool
Credential review.Credential
BaseURL string
Model string
ImplementTimeout time.Duration
ReviewTimeout time.Duration
// Exec runs commands on the sidecars; review.ClientExec when nil.
Exec review.Execer
// Log is where to keep a plain-text log of the run, with its full context
// whatever a display of it filters out: a path, LogDefault for
// DefaultLogPath, or "" for none.
Log string
// Verbose adds the review prompts, the output of commands that passed,
// and a check each round that every reviewer has the implementer's change
// to the log. It implies a log.
Verbose bool
// Status reports progress, warnings included. It must be set.
Status iostream.StatusFunc
// The rest report what the run is doing, and may be nil. OnStart is
// called once the run's worktree exists.
OnStart func(runID string, wt Worktree)
OnEvent func(Event)
OnActivity func(Activity)
OnReviewProgress func(review.ProgressEvent)
OnCheck func(Check)
}
RunOptions is one factory run. Everything that needs a person, such as picking an org or finding a credential, is settled before it.
type Sidecars ¶
type Sidecars struct {
Exec review.Execer
Implementer *Implementer
// Acquire and Release hand out reviewer sidecars: the pool's own, with the
// implementer's member already checked out.
Acquire func(context.Context) (*sidecar.PoolEntry, error)
Release func(*sidecar.PoolEntry)
Reviewers []*sidecar.PoolEntry
Relay *Relay
Prompts []review.Prompt
Review review.Options
Commands []config.Command
// OnCheck is called as each validation command finishes. Reviews report
// their progress through Review.ProgressFn.
OnCheck func(Check)
// OnReviewerTree, when set, is called each round for every reviewer, in
// Reviewers order, with whether the change it is about to review is the
// implementer's. It is a canary for the relay; a mismatch does not stop the
// round.
OnReviewerTree func(ReviewerTree)
// contains filtered or unexported fields
}
Sidecars runs the loop's steps on a pool of sidecars: the implementer writes code on the member it holds for the whole run, which also runs validation, and each review runs on another member the implementer's tree is relayed to.
func (*Sidecars) Check ¶
Check relays the implementer's tree to the reviewers, then runs the reviews there and validation on the implementer at the same time.
func (*Sidecars) Prepare ¶
Prepare commits the tree the pool synced to every member, the worktree's, as the baseline the implementer's work is measured against. Reviewers get one too: they are sent the implementer's files but never its .git, so `git diff HEAD` on a reviewer needs a commit of the same tree to diff against.
type Status ¶
type Status string
Status is how a check came out.
const ( // StatusPassed means the check ran and found nothing to fix. StatusPassed Status = "passed" // StatusFailed means the check ran and found something the implementer // must fix: review findings, or a validation command exiting non-zero. StatusFailed Status = "failed" // StatusErrored means the check could not run, such as a review that timed // out. It says nothing about the code, so it is not fed back. StatusErrored Status = "errored" )
Check statuses.
type Steps ¶
type Steps interface {
// Implement runs one implementer turn with prompt.
Implement(ctx context.Context, prompt string) (Turn, error)
// Collect describes the implementer's work so far.
Collect(ctx context.Context) (Change, error)
// Check reviews and validates the implementer's work so far.
Check(ctx context.Context, round int) ([]Check, error)
}
Steps are the operations one round of the loop is made of. Sidecars implements them against real sidecars; tests implement them in memory.
type Worktree ¶ added in v0.7.197
type Worktree struct {
Path string
Branch string
// Baseline is the commit the branch starts from: the developer's HEAD, or
// their uncommitted work committed on top of it.
Baseline string
// Head is the developer's HEAD when the run started. It differs from
// Baseline when their uncommitted work was committed as the baseline.
Head string
}
Worktree is a run's local checkout: a git worktree of the developer's repository on a branch of its own. The implementer's work is pulled into it each round, so the developer's own checkout is never touched and they can look at the work while the run goes.
func CreateWorktree ¶ added in v0.7.197
CreateWorktree checks out a new worktree of the repository at root into path, on the branch chunk/factory/<runID>. It starts from the developer's files as they are now: uncommitted work, untracked files included, is committed on the branch first, so it is the baseline and not part of the change under review.
func (Worktree) Commit ¶ added in v0.7.197
Commit commits everything in the worktree to its branch and returns the commit, or "" when nothing changed. The worktree is left in place for the developer to carry on in. Hooks and signing are skipped: the run cannot answer a prompt, and the work has already been checked.