// Package workflow is eeco's workflow registry, runner, and scaffolder. // // A workflow inspects a repository and either passes cleanly, reports a // finding, blocks (a required tool is missing), or defers an AI pass. // The exit-code contract is fixed and shared with the CLI: // // 0 clean // 1 finding / failure // 2 blocked (a required tool is missing) // 3 AI pass deferred (no --ai) // // Builtin workflows are implemented natively in Go and registered in the // default registry. User workflows are scaffolded into the gitignored // workspace as a directory with a runnable entry plus a one-line README // and executed by the script runner, which honours the same contract. // // Every workflow is read-only with respect to the tracked tree: it // writes only inside the workspace and uses the queue for any decision. package workflow import ( "io" "github.com/ajhahnde/eeco/internal/ai" "github.com/ajhahnde/eeco/internal/config" ) // Exit codes shared by every workflow and surfaced as the process exit // code by `eeco run`. const ( CodeClean = 0 CodeFinding = 1 CodeBlocked = 2 CodeAIDeferred = 3 ) // Finding is one located issue. Line is 1-based; 0 means the finding is // file-scoped rather than tied to a specific line. type Finding struct { Path string Line int Msg string } // Result is the outcome of a single workflow run. Code is one of the // Code* constants. Summary is a one-line headline for the report; // Findings carries the detail lines. type Result struct { Code int Summary string Findings []Finding } // Env is the execution context handed to a workflow. The repository // root is cfg.RepoRoot; a workflow treats it as its working directory // and must never write outside cfg.Workspace. type Env struct { Config *config.Config // AI reports whether the operator opted this run into a gated, // budget-capped AI pass (`--ai`). It mirrors Gate.Consent and is kept // for read-only builtins that only need the boolean. AI bool // Gate is the shared, single-invocation AI gate (consent + budget + // prompt-parking). A workflow that wants an AI pass calls Gate.Run // and falls back to its non-AI path when the Outcome is Skipped. Nil // is tolerated: a workflow then behaves as if no pass was consented. Gate *ai.Gate // Out is an optional sink for progress lines. Nil is fine. Out io.Writer } // Workflow is a named, runnable check. Run must be side-effect-free on // the tracked tree and must return a Code from the contract. type Workflow interface { Name() string // Summary is the one-line description shown in listings. Summary() string Run(env Env) (Result, error) } // normalizeCode clamps an arbitrary integer to the contract. Anything // outside {0,1,2,3} is treated as a failure (1) so a misbehaving // workflow can never masquerade as clean. func normalizeCode(c int) int { switch c { case CodeClean, CodeFinding, CodeBlocked, CodeAIDeferred: return c default: return CodeFinding } }