Help us shape the future of Go! Take the 2026 Go Developer Survey

root

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package root defines the root configuration for the CLI.

Index

Constants

View Source
const (
	StatusOK      = "ok"      // the command succeeded
	StatusBlocked = "blocked" // stopped at a human gate or an environment gap, not a failure
	StatusError   = "error"   // the command failed
)

Outcome status values (SPEC §8): the class of a command's result, the field an agent switches on under --jsonl.

View Source
const (
	ReasonAtOps      = "at_ops"     // arc reached the ops ship gate (step)
	ReasonUngrounded = "ungrounded" // critic routing gap, exit 12 (step)
	ReasonGate       = "gate"       // pending human approval, exit 4 (approve)
	ReasonProof      = "proof"      // proof verification failed, exit 8 (proof/close)
	ReasonRequalify  = "requalify"  // worker changed; requalify before running, exit 9 (§14)
	ReasonFailed     = "failed"     // arc failed evaluation past its rework budget (run, §4.1)
)

Outcome reason tokens: a stable machine string for a blocked/error outcome, so an agent branches on the token instead of matching prose. Free-form reasons (e.g. a confirmed finding's kind) may also appear; these are the shared ones.

Variables

This section is empty.

Functions

func CodeForError

func CodeForError(err error) int

CodeForError maps a returned error to the process exit code an error outcome reports when the call site set none: a validation error is a usage error (2), everything else is generic (1). Domain gates (4, 5–8, 12) set their own code. This specializes the stub climax generates, reading adh's error taxonomy. It reads the reason rather than adh.ErrorCode directly, so a validation error raised by a library selects the usage code too — the two must not disagree about one error, and only ReasonForError knows both classifications.

func LogLevel

func LogLevel(verbose, quiet bool) slog.Level

LogLevel resolves the diagnostic log level from the verbosity flags: --quiet shows only errors ("suppress non-error output", SPEC §2), --verbose unlocks debug, and the default shows warnings and errors — the incidental notices. quiet wins when both are set.

func NewLogger

func NewLogger(w io.Writer, jsonl bool, level slog.Level) *slog.Logger

NewLogger builds the stderr diagnostic logger at the given level: a JSON handler under --jsonl, so the log stream is structured like the stdout data plane, or a text handler for humans.

func ReasonForError

func ReasonForError(err error) string

ReasonForError is the machine reason token for a returned error: its domain error code (e.g. "not_found", "invalid", "conflict"), or "internal" for an untyped error. It lets an agent branch on the failure class under --jsonl.

Types

type Config

type Config struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
	Getenv func(string) string
	// Global flags, available on every command via the parent flag chain.
	ConfigPath string // --config: override the config file path
	Profile    string // --profile: named config profile (not yet wired; SPEC §3 tier 3)
	Repo       string // --repo: target repository root (not yet threaded to the store)
	Verbose    bool   // --verbose: increase log verbosity (no logging subsystem yet)
	Quiet      bool   // --quiet: suppress non-error stdout (wired in cmd.Run)
	NoColor    bool   // --no-color: disable ANSI color (no color output yet)
	JSONL      bool   // --jsonl: emit machine output as JSON Lines (SPEC §8)
	Yes        bool   // --yes: pre-answer non-gate prompts; never satisfies a safety gate (§5.2)
	DryRun     bool   // --dry-run: preview a mutation without persisting (approve/reject/close)
	// Log is the diagnostic stream on stderr, separate from the stdout data plane
	// (SPEC §8). New seeds a default; Run rebuilds it once the flags are parsed, so
	// --verbose/--quiet set the level and --jsonl selects the JSON handler.
	Log     *slog.Logger
	Flags   *ff.FlagSet
	Command *ff.Command
}

Config holds shared I/O writers, the injected environment accessor, the global flags (SPEC §2, bound once), and the root ff.Command. All subcommand configs embed *Config to inherit these.

func New

func New(getenv func(string) string, stdin io.Reader, stdout, stderr io.Writer) *Config

New returns a new root Config with the given I/O writers and environment accessor. getenv is injected (not os.Getenv) so config precedence is testable.

func (*Config) ConfigGetenv

func (c *Config) ConfigGetenv() func(string) string

ConfigGetenv returns an environment accessor that bridges the config flags into the injected environment: --config overrides ADH_CONFIG (the chosen file) and --profile overrides ADH_PROFILE (the config.Load profile layer, SPEC §3 tier 3); other keys pass through to the injected Getenv. The approval phrase is still never sourced from the environment (§5.2) — ADH_APPROVAL_PHRASE stays ignored.

func (*Config) EmitBlocked

func (c *Config) EmitBlocked(code int, reason, message string) error

EmitBlocked writes a non-fatal "blocked" Outcome.

func (*Config) EmitError

func (c *Config) EmitError(code int, reason, message string) error

EmitError writes an error Outcome.

func (*Config) EmitJSONL

func (c *Config) EmitJSONL(v any) error

EmitJSONL writes v as a single JSON line to Stdout.

func (*Config) EmitOK

func (c *Config) EmitOK(data any) error

EmitOK writes a success Outcome carrying data.

type DryRunUnsupportedError

type DryRunUnsupportedError string

DryRunUnsupportedError is the error a command returns when --dry-run is set but the command does not honor it — only approve, reject, and close do — so a global --dry-run never silently mutates state on a command that would otherwise ignore the flag. Its string value is the command name, so the message reads "<cmd>: --dry-run not supported ...". Like ExitError, it is a defined type, so callers return it by conversion (root.DryRunUnsupportedError("run")) rather than a cross-package call; the dispatcher envelopes it like any other command error.

func (DryRunUnsupportedError) Error

func (c DryRunUnsupportedError) Error() string

Error renders the refusal in the standard "<cmd>: <reason>" form (SPEC §8).

func (DryRunUnsupportedError) Unwrap

func (c DryRunUnsupportedError) Unwrap() error

Unwrap gives the refusal a machine-readable code without giving up the defined type, which a test matches with errors.As.

Asking for --dry-run where it is not honored is a malformed invocation, not an adh fault, and with no coded cause in the chain it was reported as reason "internal".

type ExitError

type ExitError int

ExitError is returned by commands that want a specific non-zero exit code without printing an additional error message. run() in main.go checks for ExitError with errors.As and calls os.Exit(int(e)) directly, bypassing the default "error: ..." printer.

func (ExitError) Error

func (e ExitError) Error() string

Error reports the exit status. ExitError satisfies the error interface so a command can request a specific exit code without printing an "error: ..." line.

type MissingFlagError

type MissingFlagError struct {
	Scope string   // "harness gate" — the command and verb as the caller typed them
	Flags []string // "--candidate", with the dashes, as the caller would type them
}

MissingFlagError is returned by a verb invoked without a flag it requires.

Separate from MissingOperandError because the two are answerable in different ways: an operand is positional and names a thing that has to exist, so a caller — or a test fixture — can go and get one from a kind alone. A required flag names a knob whose value only the caller's intent supplies, and there is no vocabulary of flag values that would mean anything.

It exists so the ratchet over refusals needs no exemptions. Three verbs refused for a missing flag in prose, and every mechanical check of "does this refusal say what it wants" then had to carve them out by name — a list that would go stale the way two others in this repository already did. A type they can carry is cheaper than a list of commands allowed not to.

func (MissingFlagError) Error

func (e MissingFlagError) Error() string

Error renders the refusal in the standard "<cmd>: <reason>" form (SPEC §8).

func (MissingFlagError) Unwrap

func (e MissingFlagError) Unwrap() error

Unwrap gives the refusal a machine-readable code; see MissingVerbError.Unwrap.

type MissingOperandError

type MissingOperandError struct {
	Scope string // "arc show" — the command and verb as the caller typed them
	Kind  OperandKind
}

MissingOperandError is returned by a verb invoked without the positional operand it needs.

**The refusal carries the requirement as data, and that is the whole point.** Twenty-two verbs each wrote this refusal as prose, so the only way to learn that `arc show` wants an arc id was to read the message — and the --jsonl contract test, which enumerates every verb, could not supply an operand it had no way to ask about. Every payload reachable only with one therefore went unasserted, and two commands were printing prose under --jsonl inside that blind spot.

It is the same correction MissingVerbError made one level up, for the same reason: the test used to parse the verb list out of the usage line and silently lost coverage twice when the prose drifted. Prose is not a contract.

A missing operand is a *malformed invocation*, distinct from a well-formed one refused for its state (ECONFLICT) and from a missing flag, which is not positional and has MissingFlagError instead. Only the positional case belongs here.

func (MissingOperandError) Error

func (e MissingOperandError) Error() string

Error renders the refusal in the standard "<cmd>: <reason>" form (SPEC §8).

func (MissingOperandError) Unwrap

func (e MissingOperandError) Unwrap() error

Unwrap gives the refusal a machine-readable code; see MissingVerbError.Unwrap.

type MissingVerbError

type MissingVerbError struct {
	Scope string
	Verbs []string
}

MissingVerbError is returned by a command invoked with no verb at all.

A struct rather than a constructor, matching DryRunUnsupportedError: a composite literal is not a cross-package call, so callers return it directly instead of wrapping an error they did not need to wrap. It also means the verb list travels as data, which is what lets one Error method own the wording for every command.

Thirteen commands each wrote this message and its unknown-verb twin by hand, naming their own verb list in both -- and again in the usage line, a third copy the --jsonl contract test parses to decide what to enumerate. The three agree today; nothing made them, and a drift in the usage copy would quietly narrow the contract's coverage rather than fail.

func (MissingVerbError) Error

func (e MissingVerbError) Error() string

Error renders the refusal in the standard "<cmd>: <reason>" form (SPEC §8).

func (MissingVerbError) Unwrap

func (e MissingVerbError) Unwrap() error

Unwrap gives the refusal a machine-readable code.

An invocation with no verb is malformed, not an adh fault. Without a coded cause in the chain, ReasonForError reports "internal" -- the token meaning adh itself broke -- and a caller with a retry-on-internal policy retries what can never succeed. EINVALID also selects the usage exit code in CodeForError, so the code fixes both at once.

type OperandKind

type OperandKind string

OperandKind is the closed vocabulary of positional operands a command can require.

A named type rather than a bare string, for the reason SPEC-ADDITIONS §13.2 gives for reusing adh.FindingKind instead of inventing a second taxonomy: a closed set with one owner can be switched over exhaustively, so a member added later is named by the compiler and the linter rather than forgotten by whoever needed it.

const (
	OperandArc      OperandKind = "an arc id"
	OperandUnit     OperandKind = "a context unit id"
	OperandLabel    OperandKind = "a label or path to route by"
	OperandPath     OperandKind = "an artifact path"
	OperandManifest OperandKind = "a proof manifest path"
	OperandDir      OperandKind = "a directory of transcripts"
	OperandTitle    OperandKind = "a title"
	OperandLevel    OperandKind = "a level (L0-L4)"
	OperandTool     OperandKind = "a tool id (see `tool list`)"
	OperandSpec     OperandKind = "an NFR spec id"
	OperandClass    OperandKind = "a lesson class"
	OperandStaging  OperandKind = "a staging id"
	OperandBranch   OperandKind = "a branch name"
)

The operand kinds a command can require, and the whole vocabulary of them.

Each value is the noun phrase the refusal reads with, so the wording lives once beside the identifier rather than in each command. A kind names *what must exist* for the operand to be satisfiable — which is what a caller has to go and get, and what a test fixture has to mint — so two commands wanting an arc id share one kind while an artifact path and a transcript directory do not.

type Outcome

type Outcome struct {
	Status  string `json:"status"`            // ok | blocked | error
	Code    int    `json:"code"`              // process exit code (0 for ok)
	Reason  string `json:"reason,omitempty"`  // stable machine token (app-defined)
	Message string `json:"message,omitempty"` // human-readable detail
	Data    any    `json:"data,omitempty"`    // payload on success
}

Outcome is a generic machine-readable result envelope, written as one JSON object per line (JSONL) on stdout.

type UnknownVerbError

type UnknownVerbError struct {
	Scope string
	Got   string
	Verbs []string
}

UnknownVerbError is returned for a verb the command does not dispatch.

func (UnknownVerbError) Error

func (e UnknownVerbError) Error() string

Error renders the refusal in the standard "<cmd>: <reason>" form (SPEC §8).

func (UnknownVerbError) Unwrap

func (e UnknownVerbError) Unwrap() error

Unwrap gives the refusal a machine-readable code; see MissingVerbError.Unwrap.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL