Documentation
¶
Index ¶
- Variables
- func CheckBlockedOnInput(output string) bool
- func CheckNoWorkNeeded(output string) bool
- func ExtractBinaryFromTarball(tarballPath, destDir string) (string, error)
- func LogDir(issueNumber int) string
- func PerformReleaseUpgrade(client GitHubClient, version, token string, extraEnv []string, ...)
- func ReadSessionID(repo string, issueNumber int, stageName string) string
- func SemverGreater(a, b string) bool
- func SessionDir(issueNumber int) string
- type ClaudeInvoker
- type Config
- type Engine
- type GitHubClient
- type InvocationObserver
- type InvokeOptions
- type PushUnblockObserver
- type RealClaudeInvoker
- type SpawnBlock
- type StageChangeObserver
- type TokenUsage
- type WebhookHealthState
- type WorktreeManager
- func (wm *WorktreeManager) BaseDir() string
- func (wm *WorktreeManager) CleanupWorktree(issueNumber int, deleteBranch bool) error
- func (wm *WorktreeManager) DefaultBaseBranch() (string, error)
- func (wm *WorktreeManager) EnsureWorktree(issueNumber int, baseBranch string, skipUpdate bool) (string, error)
- func (wm *WorktreeManager) Prune()
- func (wm *WorktreeManager) PushBranch(issueNumber int) error
- func (wm *WorktreeManager) WorktreeDir(issueNumber int) string
Constants ¶
This section is empty.
Variables ¶
var ErrSkipItem = errors.New("skip item")
ErrSkipItem is returned by ensureRepoReady when a repo cannot be cloned and the item should be skipped for this poll cycle (but not surfaced as an error).
Functions ¶
func CheckBlockedOnInput ¶
CheckBlockedOnInput reports whether output contains the FABRIK_BLOCKED_ON_INPUT marker.
func CheckNoWorkNeeded ¶
CheckNoWorkNeeded reports whether output contains the FABRIK_NO_WORK_NEEDED marker. This marker signals that the emitting stage determined no code or documentation changes are required. It must co-occur with FABRIK_STAGE_COMPLETE; when both are present the engine skips all remaining non-cleanup stages (adding dummy completion labels and "skipped" comments) and moves the issue directly to Done without a PR.
func ExtractBinaryFromTarball ¶
ExtractBinaryFromTarball extracts the "fabrik" binary from a .tar.gz archive at tarballPath and writes it to a temp file in destDir. Returns the path to the temp file. The caller is responsible for renaming or removing it.
func PerformReleaseUpgrade ¶
func PerformReleaseUpgrade(client GitHubClient, version, token string, extraEnv []string, logf func(string, ...any))
PerformReleaseUpgrade fetches the latest release from GitHub, compares it to the running version, and — if a newer version is available — downloads the platform-matching tarball, atomically replaces the running binary, and re-execs with os.Args. extraEnv is appended to the environment for the re-exec (e.g. []string{"FABRIK_AUTO_UPGRADED=1"}); pass nil for no extras.
All failures are non-fatal: logf is called with a warning and the function returns so the caller can fall through to its normal operation.
func ReadSessionID ¶
ReadSessionID reads the session ID for a given repo, issue, and stage name. repo should be "owner/repo" for multi-repo projects, or "" for single-repo. Returns the session ID string, or empty string if the file does not exist, is unreadable, or is empty.
func SemverGreater ¶
SemverGreater reports whether version a is greater than version b. Both versions may have a leading "v" which is stripped before comparison. Each version is split on "." and each segment is compared as an integer. Returns false (not an upgrade) on any parse error.
func SessionDir ¶
SessionDir returns the directory where Claude sessions are cached for an issue. The path is <cwd>/.fabrik/sessions/issue-N/ for single-repo projects. Use sessionDirForItem for multi-repo-aware paths.
Types ¶
type ClaudeInvoker ¶
type ClaudeInvoker interface {
Invoke(ctx context.Context, stage *stages.Stage, issue gh.ProjectItem, newComments []gh.Comment, resume bool, workDir string, opts InvokeOptions) (output string, completed bool, usage TokenUsage, err error)
InvokeForComments(ctx context.Context, stage *stages.Stage, issue gh.ProjectItem, comments []gh.Comment, workDir string, opts InvokeOptions) (output string, completed bool, usage TokenUsage, err error)
}
ClaudeInvoker defines the interface for invoking Claude Code.
type Config ¶
type Config struct {
Owner string
Repo string
ProjectNum int
OwnerType string
User string
Token string
Version string
Yolo bool
AutoUpgrade bool
GitSSH bool
PollSeconds int
MaxConcurrent int
MaxRetries int
ReviewWaitTimeout time.Duration // How long to wait for PR reviewers before auto-advancing anyway (default 15m)
ReconcileInterval time.Duration // Reconcile ticker cadence (0 = use lightReconcileInterval default of 3m)
MaxReviewCycles int // Max review re-invocation cycles per issue before pausing (default 5)
CIWaitTimeout time.Duration // How long to wait for CI in the merge guard before pausing (default 30m)
MaxCiFixCycles int // Max CI-fix re-invocation cycles per issue before pausing (default 5)
MaxRebaseCycles int // Max rebase re-invocation cycles per issue before pausing (default 3)
ClaudeWaitDelay time.Duration // How long to wait after Claude exits before giving up on pipe drain and recovering output (default 30s)
DebugOutput bool
SymlinkEnv bool
PluginDir string
Stages []*stages.Stage
Webhooks bool
WebhookPort int
WebhookEvents []string
ProjectStatusPollSeconds int // Layer 2 status-only sweep cadence in seconds; default 15 s (gate runs every poll cycle; field retained for config compatibility)
// ReadyCh is closed once Run() has registered signal handlers. Tests use
// this to avoid sending SIGINT before signal.Notify is installed.
ReadyCh chan struct{}
}
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
func NewWithDeps ¶
func NewWithDeps(cfg Config, client GitHubClient, claude ClaudeInvoker, worktrees *WorktreeManager) *Engine
NewWithDeps creates an Engine with explicit dependencies (for testing). worktrees is a convenience parameter: if non-nil, it is registered as the WM for cfg.Owner+"/"+cfg.Repo (or "_test/_test" when cfg is empty).
func (*Engine) SetCleanupHook ¶
func (e *Engine) SetCleanupHook(fn func())
SetCleanupHook registers fn to be called exactly once before any force-quit path (SIGHUP re-exec, second SIGTERM, second SIGHUP). Wraps fn in sync.Once so concurrent force-quit goroutines can't invoke it simultaneously. Must be called before Run(). No-op when fn is nil.
type GitHubClient ¶
type GitHubClient interface {
FetchProjectBoard(owner, repo string, projectNum int, ownerType string) (*gh.ProjectBoard, error)
ProbeProjectBoard(owner, repo string, projectNum int, ownerType string) ([]gh.BoardProbeItem, string, error)
FetchItemDetails(item *gh.ProjectItem) error
FetchStatusField(projectID string) (*gh.StatusField, error)
FetchLabels(owner, repo string, issueNumber int) ([]string, error)
AddLabelToIssue(owner, repo string, issueNumber int, labelName string) error
RemoveLabelFromIssue(owner, repo string, issueNumber int, labelName string) error
AddComment(owner, repo string, issueNumber int, body string) (int, error)
AddCommentReaction(owner, repo string, commentDatabaseID int, content string) error
AddPRReviewCommentReaction(owner, repo string, commentDatabaseID int, content string) error
ResolveReviewThread(threadID string) error
UpdateComment(owner, repo string, commentDatabaseID int, body string) error
UpdateIssueBody(owner, repo string, issueNumber int, body string) error
UpdateProjectItemStatus(projectID, itemID, statusFieldID, statusOptionID string) error
ArchiveProjectItem(projectID, itemID string) error
GetIssueBody(owner, repo string, issueNumber int) (string, error)
FindPRForIssue(owner, repo string, issueNumber int) (int, error)
FetchLinkedPR(owner, repo string, issueNumber int) (*gh.PRDetails, error)
FetchPRMergeable(owner, repo string, prNumber int) (*bool, error)
FetchPRMergeableState(owner, repo string, prNumber int) (string, error)
FetchCheckRuns(owner, repo, sha string) ([]gh.CheckRun, error)
FetchPRClosingIssues(owner, repo string, prNumber int) ([]int, error)
FetchPRsForSHA(owner, repo, sha string) ([]int, error)
FetchProjectItem(owner, repo string, issueNumber int) (*gh.ProjectItem, error)
GetPRBase(owner, repo string, prNumber int) (string, error)
UpdatePRBase(owner, repo string, prNumber int, newBase string) error
CreateDraftPR(owner, repo, title, head, base, body string, issueNumber int) (int, error)
MarkPRReady(owner, repo string, prNumber int) error
MergePR(owner, repo string, prNumber int) error
CloseIssue(owner, repo string, issueNumber int) error
CreateIssue(owner, repo, title, body string) (number int, nodeID string, err error)
AddProjectV2ItemById(projectID, contentNodeID string) (string, error)
AddBlockedByIssue(issueNodeID, blockerNodeID string) error
DeleteReviewRequest(owner, repo string, prNumber int, reviewers []string) error
AddReviewRequest(owner, repo string, prNumber int, reviewers []string) error
FetchLatestRelease(owner, repo string) (*gh.LatestRelease, error)
FetchLabelAppliedAt(owner, repo string, issueNumber int, labelName string) (time.Time, error)
SeedLabels(owner, repo string, stageNames []string, lockedUser string) error
RateLimitStats() (rest, graphql gh.RateLimitStats)
DeleteForwardingHooks(owner, repo string) error
FetchProjectItemStatus(itemID string) (string, error)
FetchProjectItemStatusBatch(projectID string) (map[string]string, error)
FetchProjectUpdatedAt(projectID string) (time.Time, error)
LookupIssueProjectItem(projectID, repo string, issueNumber int) (itemID string, status string, err error)
}
GitHubClient defines the GitHub operations needed by the engine.
type InvocationObserver ¶
InvocationObserver is registered on engine.store and fires a tui.JobCompletedEvent whenever InvocationChanged is observed. It replaces the ad-hoc emitStructural(JobCompletedEvent{...}) calls in poll.go, ci.go, merge_gate.go, and reviews.go. All three fields (LastInvocationCompleted, LastInvocationBlocked, LastTokenUsage) are set atomically by InvocationRecorded, so the observer reads a consistent view from the Snapshot.
type InvokeOptions ¶
type InvokeOptions struct {
ModelOverride string // from "model:<name>" label, overrides stage.Model
EffortOverride string // from "effort:<level>" label, overrides stage.EffortLevel
BaseBranch string // actual default base branch for the managed repo (e.g. "develop", not always "main")
MaxTurnsOverride int // when > 0, overrides stage.MaxTurns for this invocation; 0 means use stage.MaxTurns
// OnPIDReady, if non-nil, is called once after cmd.Start() with the Claude subprocess PID.
// Used by the heartbeat/liveness system to record the PID in the store.
OnPIDReady func(pid int)
}
InvokeOptions bundles per-issue override parameters for Claude invocations. Using a struct rather than individual parameters means future overrides (e.g. DisableAdaptiveThinking) are zero-churn field additions.
type PushUnblockObserver ¶
type PushUnblockObserver struct {
Store *itemstate.Store
Remove func(owner, repo string, number int)
// Logf is an optional diagnostic hook. When non-nil, it is called at key
// branch points so push-unblock decisions are traceable in fabrik.log.
Logf func(format string, args ...any)
}
PushUnblockObserver fires on two distinct events and removes fabrik:blocked when all blockers are resolved:
StateChanged (blocker closes): scans Store.All() for items that carry fabrik:blocked and list the closing issue in their BlockedBy slice, then checks whether every remaining blocker is closed.
BlockedByChanged (dependent's BlockedBy first populated via deep-fetch): inspects only the changed item's own snapshot; if it carries fabrik:blocked and all listed blockers are already closed in the store, removes the label.
This dual-trigger ensures the dependent unblocks within seconds regardless of which event arrives first — the blocker's close or the dependent's first deep-fetch. Neither StateChanged nor BlockedByChanged is in wakeChFlags or cycleSetFlags, so this observer does not trigger a poll wake — label removal is a direct side effect only.
type RealClaudeInvoker ¶
type RealClaudeInvoker struct {
DebugOutput bool
}
RealClaudeInvoker wraps the existing InvokeClaude function.
func (*RealClaudeInvoker) Invoke ¶
func (r *RealClaudeInvoker) Invoke(ctx context.Context, stage *stages.Stage, issue gh.ProjectItem, newComments []gh.Comment, resume bool, workDir string, opts InvokeOptions) (string, bool, TokenUsage, error)
func (*RealClaudeInvoker) InvokeForComments ¶
func (r *RealClaudeInvoker) InvokeForComments(ctx context.Context, stage *stages.Stage, issue gh.ProjectItem, comments []gh.Comment, workDir string, opts InvokeOptions) (string, bool, TokenUsage, error)
type SpawnBlock ¶
SpawnBlock represents one child issue declared in a Plan's FABRIK_SPAWN_CHILD_BEGIN/END block.
func ParseSpawnBlocks ¶
func ParseSpawnBlocks(body string) []SpawnBlock
ParseSpawnBlocks scans body for all FABRIK_SPAWN_CHILD_BEGIN/END pairs and returns the parsed spawn blocks in order. Malformed or incomplete pairs are silently skipped. The BEGIN marker must be followed by the target repo on the same line: "FABRIK_SPAWN_CHILD_BEGIN owner/repo". The first non-empty line after BEGIN is the TITLE: line; the body starts after the blank line following the title.
type StageChangeObserver ¶
StageChangeObserver is registered on cacheImpl.store and fires a tui.StageChangedEvent whenever StatusChanged is observed. This allows the TUI to reactively update the displayed stage for an active item without waiting for the next poll cycle.
type TokenUsage ¶
type TokenUsage = itemstate.TokenUsage
TokenUsage is a type alias for itemstate.TokenUsage. Using an alias ensures engine.TokenUsage and itemstate.TokenUsage are the same type with zero-cost assignment between the two packages (Phase 3-E).
func InvokeClaude ¶
func InvokeClaude(ctx context.Context, stage *stages.Stage, issue gh.ProjectItem, newComments []gh.Comment, resume bool, workDir string, opts InvokeOptions) (string, bool, TokenUsage, error)
InvokeClaude runs Claude Code with the given stage configuration and issue context. workDir is the directory Claude should run in (typically a git worktree). opts.ModelOverride, if non-empty, replaces the stage's configured model. opts.EffortOverride, if non-empty, replaces the stage's configured effort level. It returns Claude's output, whether Claude indicated completion, and token usage.
func InvokeClaudeForComments ¶
func InvokeClaudeForComments(ctx context.Context, stage *stages.Stage, issue gh.ProjectItem, comments []gh.Comment, workDir string, opts InvokeOptions) (string, bool, TokenUsage, error)
InvokeClaudeForComments runs Claude Code with a comment-review prompt. It uses the stage's CommentPrompt if defined, otherwise a default. opts.ModelOverride, if non-empty, replaces the stage's configured model. opts.EffortOverride, if non-empty, replaces the stage's configured effort level.
type WebhookHealthState ¶
type WebhookHealthState string
WebhookHealthState indicates the health of the gh webhook forward stream.
const ( WebhookStreamStartingUp WebhookHealthState = "starting_up" WebhookStreamHealthy WebhookHealthState = "healthy" WebhookStreamUnhealthy WebhookHealthState = "unhealthy" )
type WorktreeManager ¶
type WorktreeManager struct {
// contains filtered or unexported fields
}
WorktreeManager handles git worktrees for issue isolation.
func NewWorktreeManager ¶
func NewWorktreeManager(repoDir string) *WorktreeManager
func NewWorktreeManagerForRepo ¶
func NewWorktreeManagerForRepo(baseDir, worktreeRoot, rName string) *WorktreeManager
NewWorktreeManagerForRepo creates a WorktreeManager that namespaces all worktree paths under worktreeRoot/<repoName>/. Used by production code for each discovered repo (baseDir is the bare-clone directory; worktreeRoot is .fabrik/worktrees).
func NewWorktreeManagerWithRoot ¶
func NewWorktreeManagerWithRoot(repoDir, worktreeRoot string) *WorktreeManager
func (*WorktreeManager) BaseDir ¶
func (wm *WorktreeManager) BaseDir() string
BaseDir returns the main repository directory.
func (*WorktreeManager) CleanupWorktree ¶
func (wm *WorktreeManager) CleanupWorktree(issueNumber int, deleteBranch bool) error
CleanupWorktree removes the worktree and optionally the branch for an issue. Serialized with mu because git worktree remove writes to .git/worktrees/ metadata and .git/config, which are not safe to update concurrently with EnsureWorktree or PushBranch.
func (*WorktreeManager) DefaultBaseBranch ¶
func (wm *WorktreeManager) DefaultBaseBranch() (string, error)
DefaultBaseBranch returns the default branch of the repo. It uses a three-step fallback ladder to determine the actual remote default branch without relying on hard-coded names like "main" or "master":
- git symbolic-ref refs/remotes/origin/HEAD — kept fresh by ensureBareClone running `git remote set-head origin --auto` on every entry, so this reflects the remote's current default branch.
- git ls-remote --symref origin HEAD — authoritative network round-trip, used when refs/remotes/origin/HEAD is missing (e.g. set-head failed at clone time due to a transient network error).
- git symbolic-ref HEAD — the bare clone's own HEAD, frozen at clone time. Last-resort fallback when both local origin/HEAD and the network lookup are unavailable; may be STALE if the remote default branch changed after the initial clone.
Step 2 is ordered before step 3 intentionally: the bare clone's local HEAD is never refreshed after clone time, so trusting it over the authoritative remote lookup caused PRs to target stale defaults (e.g. targeting `main` after a repo's default branch was changed to `develop`).
Returns an error if the default branch cannot be determined.
func (*WorktreeManager) EnsureWorktree ¶
func (wm *WorktreeManager) EnsureWorktree(issueNumber int, baseBranch string, skipUpdate bool) (string, error)
EnsureWorktree creates or returns the path to a worktree for the given issue. Each issue gets its own branch (fabrik/issue-N) and worktree directory. When skipUpdate is true (e.g. on retry attempts), the worktree is returned as-is without rebasing onto main. This avoids introducing unrelated changes mid-session.
func (*WorktreeManager) Prune ¶
func (wm *WorktreeManager) Prune()
Prune removes stale worktree registrations from git. Should be called once per poll cycle, before workers spawn — never during concurrent work.
func (*WorktreeManager) PushBranch ¶
func (wm *WorktreeManager) PushBranch(issueNumber int) error
PushBranch pushes the issue's worktree branch to origin. Uses -u to set upstream tracking on the first push. Serialized with mu because git push -u writes upstream tracking to .git/config, which is not safe to update concurrently across workers. Errors are non-fatal (e.g., no commits yet) — the caller decides how to handle them.
func (*WorktreeManager) WorktreeDir ¶
func (wm *WorktreeManager) WorktreeDir(issueNumber int) string
WorktreeDir returns the path to the worktree for an issue (whether it exists or not).