Go 92 lines
// 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
}
}