fixit¶
import "github.com/jbcom/radioactive-ralph/internal/fixit"
Package fixit implements the deliberate plan-creation pipeline for fixit-ralph’s advisor mode.
See docs/design/fixit-plan-pipeline.md for the architectural rationale. Each stage in this package corresponds to a stage in the design doc.
Index¶
Constants¶
MinConfidence is the floor below which a plan becomes provisional regardless of other rules passing.
const MinConfidence = 50
type AnalyzeOptions¶
AnalyzeOptions feeds Analyze.
type AnalyzeOptions struct {
Intent IntentSpec
RC RepoContext
Scores []VariantScore
// ClaudeBin overrides the default `claude` binary path. Tests use
// the cassette replayer or the fake-claude double here.
ClaudeBin string
// WorkingDir is the cwd for the spawned subprocess. Defaults to the
// repo root from RC.GitRoot.
WorkingDir string
// Model pins the tier for the planning subprocess. Empty defaults
// to "opus" — the advisor runs infrequently (once per plan), so
// the cost is bounded and the quality delta is meaningful.
Model string
// Effort pins the reasoning-effort level. Empty defaults to "high"
// so opus scales up on genuinely hard repos without burning tokens
// on simple ones.
Effort string
// Timeout caps the total Claude analysis time. Default 180s —
// opus with auto-effort can take longer than the old sonnet/medium
// defaults, so the cap is lifted from 90s accordingly.
Timeout time.Duration
}
type DocFile¶
DocFile records a markdown file’s frontmatter and basic metadata so Stage 4 can reason about doc freshness without re-reading every file.
type DocFile struct {
Path string
Frontmatter map[string]string
UpdatedISO string // from frontmatter or file mtime fallback
}
type EmitResult¶
EmitResult is what EmitToDAG returns.
type EmitResult struct {
PlanID string
Status PlanStatus
TaskIDs []string
Proposal PlanProposal
Validation ValidationResult
}
func EmitToDAG¶
func EmitToDAG(ctx context.Context, o EmitToDAGOpts) (EmitResult, error)
EmitToDAG writes the fixit output into plandag. Creates:
one plans row (status mapped from fixit PlanStatus)
one intents row capturing raw operator input
one analyses row capturing Claude’s Phase 2 output
N tasks rows (one per proposal.Tasks entry)
topologically-sorted dependency edges when Task.DependsOn is set
Returns the newly-generated plan UUID plus the slugified task IDs.
type EmitToDAGOpts¶
EmitToDAGOpts configures EmitToDAG.
type EmitToDAGOpts struct {
Store *plandag.Store
Topic string
Proposal PlanProposal
Validation ValidationResult
Status PlanStatus
Intent IntentSpec
RC RepoContext
RawOutput string // Phase 2 Claude output; stored in analyses.raw_json
}
type EmittedPlan¶
EmittedPlan describes what Stage 6 wrote.
type EmittedPlan struct {
Path string
Status PlanStatus
Proposal PlanProposal // zero-valued when Status=fallback
Validation ValidationResult
Intent IntentSpec
RepoContext RepoContext
}
func Emit¶
func Emit(plansDir, topic string, proposal PlanProposal, validation ValidationResult, status PlanStatus, intent IntentSpec, rc RepoContext) (EmittedPlan, error)
Emit runs Stage 6 — write the plan to disk and return what happened. The status comes from the caller (Current / Provisional / Fallback) based on Stage 4 + Stage 5 outcomes.
The emitted file ALWAYS has valid plan-format frontmatter so it satisfies the shape the plans-first discipline expects, even when status is fallback or provisional — other variants gate on the status field, not on presence of frontmatter.
func EmitFallback¶
func EmitFallback(plansDir, topic string, reason string, rawOutput string, intent IntentSpec, rc RepoContext) (EmittedPlan, error)
EmitFallback writes a diagnostic-only plan whose status is `fallback`. Used when Stage 4 fails twice or Stage 5 produces a failure the operator should see directly.
func RunPipeline¶
func RunPipeline(ctx context.Context, opts RunOptions) (EmittedPlan, error)
RunPipeline orchestrates Stages 1-6 and returns what was emitted. Errors here indicate an unrecoverable pipeline failure (e.g. can’t explore the repo); Stage 4/5 failures become fallback or provisional plans, not errors.
type GHIssue¶
GHIssue is the subset of `gh pr/issue list –json` fields fixit uses.
type GHIssue struct {
Number int
Title string
Draft bool
State string // OPEN, CLOSED, MERGED
MergeStatus string // CLEAN, DIRTY, BLOCKED, UNKNOWN
Author string
Labels []string
}
type GitCommit¶
GitCommit is a single entry from `git log –oneline` enriched with author + date.
type GitCommit struct {
SHA string
Subject string
Author string
DateISO string
}
type IntentOptions¶
IntentOptions feeds CaptureIntent.
type IntentOptions struct {
Topic string
Description string
Constraints []string
NonInteractive bool
RepoRoot string
Stdin io.Reader // defaults to os.Stdin
Stdout io.Writer // defaults to os.Stdout
}
type IntentSpec¶
IntentSpec captures Stage 1 output — what the operator is trying to accomplish and what’s off-limits.
type IntentSpec struct {
// Topic is the sanitized slug used in the output filename.
Topic string
// Description is free-form operator text describing the goal.
// Either passed via --description, scraped from a TOPIC.md at the
// repo root, or empty.
Description string
// Constraints are operator-declared hard limits. Examples: "no
// opus", "stay under $5", "main branch only", "weekend only".
// Stage 4 renders these as inviolable rules in the prompt; Stage
// 5 rejects proposals that violate them.
Constraints []string
// AnswersToQs is the populated record of any interactive
// questions the operator answered. Empty in non-interactive mode.
AnswersToQs map[string]string
}
func CaptureIntent¶
func CaptureIntent(opts IntentOptions) (IntentSpec, error)
CaptureIntent runs Stage 1. In non-interactive mode it returns immediately with whatever the caller supplied. In interactive mode it asks three short questions on stdout and reads answers from stdin. Either way it consults a TOPIC.md at the repo root for an operator-prepared description if –description wasn’t passed.
type InventorySnapshot¶
InventorySnapshot is a flattened view of the operator capability inventory (helper integrations, MCPs, agents). The full inventory.Inventory has more detail but the advisor only needs names and high-level availability.
type InventorySnapshot struct {
Skills []string // FullName form, e.g. "coderabbit:review"
MCPs []string
Agents []string
}
type PlanProposal¶
PlanProposal is Stage 4 output — the structured JSON the constrained Claude subprocess returns.
type PlanProposal struct {
Primary string `json:"primary"`
PrimaryRationale string `json:"primary_rationale"`
Alternate string `json:"alternate,omitempty"`
AlternateWhen string `json:"alternate_when,omitempty"`
Tasks []Task `json:"tasks"`
AcceptanceCriteria []string `json:"acceptance_criteria"`
Confidence int `json:"confidence"` // 0..100
}
func Analyze¶
func Analyze(ctx context.Context, opts AnalyzeOptions) (PlanProposal, error)
Analyze runs Stage 4. Returns a parsed PlanProposal on success, or (zero, error) on hard failure. Callers handle fallback emission; this function never returns a half-filled proposal.
One retry on JSON-parse failure: when Claude returns text that doesn’t unmarshal cleanly, we re-spawn with the parse error appended so the model can self-correct. Second failure bubbles up.
type PlanStatus¶
PlanStatus captures whether the emitted plan satisfies the plans- first discipline gate.
type PlanStatus string
const (
// StatusCurrent means the plan passed every validation rule and
// other variants will accept it as a valid durable plan.
StatusCurrent PlanStatus = "current"
// StatusProvisional means at least one validation rule failed but
// the proposal had enough merit to write. Other variants refuse to
// run on a provisional plan until the operator promotes it.
StatusProvisional PlanStatus = "provisional"
// StatusFallback means Stage 4 (Claude analysis) failed twice. The
// emitted file is a diagnostic, not a plan.
StatusFallback PlanStatus = "fallback"
)
type RefineIteration¶
RefineIteration records what happened in one pass.
type RefineIteration struct {
Iteration int
Proposal PlanProposal
Validation ValidationResult
Confidence int
Accepted bool
}
type RefineOptions¶
RefineOptions drives the Stage 4 + Stage 5 refinement loop.
type RefineOptions struct {
AnalyzeOpts AnalyzeOptions
MaxIterations int // default 3
MinConfidenceThreshold int // default 70
StopOnValidationPass bool // default true — stop as soon as validation passes
}
type RefineResult¶
RefineResult captures each iteration the loop produced, plus the final accepted proposal (or zero value if all iterations failed).
type RefineResult struct {
Iterations int // how many passes we actually ran
FinalProposal PlanProposal // the accepted or last-attempt proposal
FinalValidation ValidationResult
History []RefineIteration
AcceptedAt int // iteration number of the accepted proposal (1-indexed), 0 if never
}
func Refine¶
func Refine(ctx context.Context, rc RepoContext, intent IntentSpec, opts RefineOptions) (RefineResult, error)
Refine runs Stage 4 repeatedly, feeding validation failures back into each subsequent call as corrective context. Stops when any of:
Proposal validates AND confidence >= MinConfidenceThreshold
MaxIterations reached
Hard error (context cancel, claude spawn fail)
The LLM sees each prior attempt’s failures appended to the system prompt, so it can deliberately address them rather than randomly redrafting.
type RepoContext¶
RepoContext is Stage 2 output — everything the deterministic exploration discovered about the repo.
type RepoContext struct {
GitRoot string
CurrentBranch string
DefaultBranch string
OnDefaultBranch bool
Commits []GitCommit
DocsPresent []DocFile
DocsStale []string
DocsMissing []string
PlansDir string
PlansIndexExists bool
PlansIndexFM map[string]string
PlansFiles []string
GHAuthenticated bool
OpenPRs []GHIssue
OpenIssues []GHIssue
AIWelcomeIssues []GHIssue
Inventory InventorySnapshot
LangCounts map[string]int
GovernanceMissing []string
}
func Explore¶
func Explore(ctx context.Context, repoRoot string) (RepoContext, error)
Explore runs Stage 2 — deterministic repo exploration. Every field in the returned RepoContext comes from a shell-out (git, gh, file walk) — zero LLM calls.
type RunOptions¶
RunOptions drives the full six-stage pipeline.
type RunOptions struct {
RepoRoot string
Topic string
Description string
Constraints []string
NonInteractive bool
// ClaudeBin overrides the default `claude` binary for Stage 4.
// Tests pass the cassette replayer or fake-claude here.
ClaudeBin string
// SkipAnalysis bypasses Stage 4 — useful only for tests that
// exercise the deterministic stages without spawning a
// subprocess. When true, the pipeline returns after Stage 3 with
// a zero PlanProposal.
SkipAnalysis bool
// MaxRefinementIterations caps how many rounds of Claude
// refinement Stage 4 will do before giving up. Default 3.
// Configurable via CLI flag or config.toml [variants.fixit]
// max_refinement_iterations.
MaxRefinementIterations int
// MinConfidenceThreshold is the confidence floor a proposal must
// meet before we stop refining. Validate.MinConfidence is the
// lower absolute floor below which validation fails; this
// threshold is the refinement-loop bar. Default 70.
// Configurable via CLI flag or config.toml [variants.fixit]
// min_confidence_threshold.
MinConfidenceThreshold int
// PlanModel pins the Claude model tier for Stage 4 planning.
// Empty defaults to "opus" — planning benefits from the most
// capable tier and the advisor runs infrequently. Configurable
// via CLI (--plan-model) or config.toml [variants.fixit]
// plan_model.
PlanModel string
// PlanEffort pins the reasoning-effort level for Stage 4.
// Empty defaults to "high". Configurable via CLI (--plan-effort)
// or config.toml [variants.fixit] plan_effort.
PlanEffort string
}
type Task¶
Task is one item in a PlanProposal’s task list. The DAG-oriented fields (VariantHint, ContextBoundary, AcceptanceCriteria, DependsOn) are populated when Stage 4 emits enough structure to build a real DAG. When omitted, EmitToDAG falls back to an implicit linear chain.
type Task struct {
Title string `json:"title"`
Effort string `json:"effort"` // S | M | L
Impact string `json:"impact"` // S | M | L
VariantHint string `json:"variant_hint,omitempty"`
ContextBoundary bool `json:"context_boundary,omitempty"`
AcceptanceCriteria []string `json:"acceptance_criteria,omitempty"`
DependsOn []string `json:"depends_on,omitempty"`
}
type ValidationResult¶
ValidationResult is Stage 5 output — whether the proposal passed every rule and, if not, what failed.
type ValidationResult struct {
Passed bool
Failures []string
}
func Validate¶
func Validate(p PlanProposal, rc RepoContext, intent IntentSpec) ValidationResult
Validate runs Stage 5. Returns (passed, failures). A passing validation means the plan gets `status: current`; a failing validation still emits the plan but downgrades status to `provisional` so other variants’ plans-first gate refuses it.
type VariantScore¶
VariantScore is one entry in Stage 3’s deterministic ranking.
type VariantScore struct {
Variant string
Score int // 0..100
Reasons []string // human-readable bullets, fed into prompt
Disqualifying []string // hard exclusions; non-empty means score=0
}
func Score¶
func Score(rc RepoContext, intent IntentSpec) []VariantScore
Score runs Stage 3 — deterministic variant ranking. Every variant gets a 0..100 score with bullet-justified Reasons. Disqualifying notes set the score to 0 (e.g. world-breaker without –confirm-burn-everything from the CLI, since gated variants can’t be auto-handed-off).
Same input always produces same output. Rules live here so they can be unit-tested independently of Claude.
Generated by gomarkdoc