evo

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

Evident Output

Go presentation library for CLI state, progress, evidence, changes, plans, and conclusions.

Application code owns execution. Package evo owns presentation only.

Quick start

import (
    "os"
    evo "github.com/zachbornheimer/evident-output"
)

func main() {
    evo.Init(evo.Config{Title: "bpp-csharp"}) // first statement — arms first paint before any I/O
    os.Exit(evo.Main(run))
}

func run() error {
    // Start as casually as fmt — then promote to structure when useful.
    evo.Println("Reading configuration")
    evo.Printf("Found %d packages\n", 18)

    evo.Task("working tree").Done()
    evo.Task("branches").Block(
        "local-only branch",
        evo.Detail("commit or stash before continuing"),
    )

    evo.Task("cleanup").Delete(2, "stale local branch") // singular object, ledger renders "2 stale local branches"; [changed]/[planned] picked from Config.DryRun; no Done needed — a recorded effect auto-resolves
    for pkg := range evo.Task("install").Each(packages) {
        install(pkg)
    }
    return nil // Block is a presentation outcome, not a Go error
}
go get github.com/zachbornheimer/evident-output@v0.2.16

Requires Go 1.25+. License: Apache-2.0.

Design philosophy and polish-phase basis: docs/roadmap/implementation-basis.md, docs/philosophy/.

Construction: evo.Init(Config{…}) is the sole constructor — the package-level default instance (front door) by default; Config.Isolated: true returns an independent hosted instance instead — TTY, NO_COLOR, stdout/stderr defaults included. Advanced: Config.Options: []Option{Title(...), …} for exact writer/terminal/clock wiring — an explicit To(w) under Options bypasses TTY/color inference entirely (raw ANSI on w unless you also call NoColor()); leaving both To() and Terminal(...) unset instead defaults to os.Stdout with the ordinary TTY/NO_COLOR inference applied. Config honesty: VisibilityDelay: evo.Delay(0) is immediate (nil = default 80ms). Debug: evo.DebugConfig{Level: evo.LevelDebug} selects the journal threshold — evo.LogLevel, a distinct type from stdlib slog.Level (LevelUnset → Info). Lifecycle: os.Exit(evo.Main(run)) (default instance, run func() error) or os.Exit(out.Run(run)) (hosted, Config.Isolated: true, run func(*Output) error) seals Finish + Close + exit code; a non-nil run error is recorded as Fail only when nothing already failed. Messages: one human instrument — Print / Printf / Println + Verbose(). Infrastructure logs: slog.New(out.SlogHandler()) (level from Config.Debug.Level only), written to Config.Stderr (default os.Stderr) — a piped run like prog > log.txt won't capture them; redirect with 2> (or 2>&1) instead. Semantic state: Task. Mutations: Task.Add/Delete/Create/Update/Remove/Write/Push/Record/RecordName pick [planned] vs [changed] from Config.DryRun — one spelling, never a call-site tense flip. Loops and taxonomy: Task.Each(items []string) / EachN(len(items)) (any other slice type) own absolute progress; Task.Skipped(reason, name) / Task.Kept(reason, name) own the counted, summed skip/keep partition. Confirm: evo.Confirm(question, …) owns the whole ask-decide-resolve gate — Done / ⊘ declined / ⊘ blocked by policy, never a Go error. question is literal text, not a printf format — Confirm is the one entity-text spelling that takes no variadic fmt args (every other one — Task/Done/Warn/Phase/Skip/Group/Reason — is printf-variadic), so build the string yourself (fmt.Sprintf) before calling. A decline resolves [blocked] → exit 1 (see the conclusion table below) — pass AssumeYes (or check a separate flag before calling Confirm at all) if declining should exit 0 instead. The default policy hint names a --yes flag; pass evo.PolicyFlag("--apply") when your program's real flag is spelled differently. Capture: Task.Evidence (work or tool-backed gate); silent by default; pending fragments in DetailTail; Config.Redactor before retention. cmd.Stdout = task.PhaseWriter() turns a talkative child's last line into the live Phase; out.Suspend(fn) hands the tty to a child that paints its own UI. Platform: evo.ID + narrow Scope (Task/Tasks only — not a sandbox); ResultWriter() under FormatData.

Pick the entity

Shape Use when
Task Everything — a check/gate resolved directly (Done/Warn/Block/Fail/Skip, no Phase/Progress) renders as a fact row; work with phases, progress, or mutation verbs shows a spinner while running
Tasks Collection of independent tasks (state is derived)

Multi-gate: resolve every Task, then if out.AnyBlockedSoFar() { return nil } before mutation; Main maps ExitCode.

Advanced (tooling call sites): Plan / Changes are the instance-API primitives Task's mutation verbs (Delete/Create/Update/…) are built on — reach for them directly only when a tool needs the would/did split without a Task.

Severity dialect

Outcome Meaning
Warn Soft concern or optional tool missing; command may continue
Block Policy / precondition failed; stop before mutation (evaluation succeeded)
Fail Evaluation failed or required tool/IO failed

Block ≠ Go error. After Block, return nil from run and let Main exit 1.

Conclusion band → exit code

The trailing [state] band and the process exit code always agree — never read one without checking the other. · partial and · warned are modifiers on the state, not a state of their own: an abandoned loop or a forgotten terminal verb on an otherwise clean finish adds · partial, and a resolved Warn that didn't otherwise change the headline adds · warned, to whatever state the run already concluded, without changing its exit code.

Band Exit code Meaning
[changed] 0 A mutation verb (Delete/Create/…) recorded outside DryRun
[planned] 0 A mutation verb recorded under Config.DryRun (would, not did)
[ready] 0 Every task resolved Done; no mutation verb recorded
[unchanged] 0 Every task resolved Done.Unchanged; nothing needed to change
[blocked] 1 At least one Block, and nothing Failed
[failed] 2 At least one Fail, or a caller-supplied misuse
[cancelled] 130 Cancel or an interrupt ended the run early
any of the above + · partial unchanged The run also left an unresolved task — same exit code as the state above
any of the above + · warned unchanged At least one Warn resolved without otherwise changing the headline

Child processes / tool-backed gates

Evidence belongs to the entity (a Task, whether it ran or was resolved as a fact-check gate), not the whole session — and not context. For an *exec.Cmd, prefer Task.Run (below); reach for Evidence directly only when the caller already owns stdout/stderr plumbing:

upgrade := out.Task("brew packages")
proof := upgrade.Evidence() // always retains a bounded ring; debug only controls display

if err := run.Run(ctx, "brew", []string{"upgrade", "--formula"}, proof); err != nil {
    return upgrade.Failf("brew upgrade failed: %w", err)
}
upgrade.Done()

Tool-backed condition (a Task resolved directly, no Phase/Progress):

docker := out.Task("docker daemon")
proof := docker.Evidence()
if err := runDockerInfo(proof); err != nil {
    docker.Failf("could not inspect the daemon: %w", err)
} else {
    docker.Done()
}
  • Ownership: Task.Evidence associates evidence with that entity.
  • Silent by default: ring always retains; no Diagnostics/Debug mirror unless MirrorToDiagnostics() / MirrorToDebug().
  • Redaction: Config.Redactor (or evo.Redact) applies before ring retention.
  • Detail: DetailTail() is a ProblemOption; separate Stdout()/Stderr() buffers. Failf's trailing %w also renders a summary/evidence split for the wrapped error itself.
  • Defaults: last 200 lines / 256KiB via KeepLastLines / MaxEvidenceBytes.
  • Session out.Capture: advanced only — prefer entity-owned capture.

Platform adapters (contracts, not sugar)

Keep the core vocabulary small. Scale via Config, schema keys, and stream contracts:

Need Contract
Stable machine identity out.Task("label", evo.ID("gate.tree")) — keys appear in Snapshot/JSON
Plugin / subsystem namespace out.Scope("registry").Task("pull", evo.ID("image")) → key registry.image (IDs only; not isolation)
Domain payload purity Format: FormatData + json.NewEncoder(out.ResultWriter()) (stdout); human on stderr
Secret scrubbing Config.Redactor or evo.Redact(r) — Debug fields + Capture ring
Host-owned rendering FormatExternal + out.Snapshot() (no inline stream)

Avoid inventing parallel APIs (RunAll, framework-specific facades in core). Prefer one Config field or EntityOption over a new top-level type.

Status

Release: v0.2.16 — pin maintenance class closed: PublishedRelease + auto-walk drift gate + sync-release-pins (no ad-hoc skill/README pin edits). Portable install forbids @latest and personal clone paths. Architecture spec: v0.5 (design candidate). Implemented surface: ordinary ladder through Plan/Changes/Capture/slog/ResultWriter; interactive VT; hardened MCP; polish-phase docs under docs/. External/manual items remain waived (Windows ConPTY / tmux / SSH RC, a11y contrast / screen-reader, host RC matrices and a11y manual reviews).

Ready now External / manual only
Task, Tasks, Changes, Plan, Print Windows ConPTY RC (PORT-003)
Conclusion + exit codes + Cancel cleanup tmux RC (PORT-004)
Plain, JSON (§25.1), JSONL (§25.2) SSH RC (PORT-005)
Interactive live region (testkit.Screen) Light/dark contrast review (A11Y-006)
SlogHandler, DebugWriter, Suspend, Snapshots(), MaxEntities, MaxEvents, AlsoWrite Screen-reader review (A11Y-007)
Appendix H.1–H.22 + agent harness + multi-file GoPackage review
ANSI driver + width/CJK + OSC strip + s390x cross-compile
CLI: review / preview / explain (real JSON)
MCP: lifecycle, protocol negotiate, unknown-field reject, panic contain, token budget, remote-path reject, catalog checksum
Framework adapter examples (urfave/Kong shapes, no core deps)

Vocabulary

Type Meaning
Task One operation — a named condition resolved directly (Done/Warn/Block/Fail/Skip) or work with phase/progress
Tasks Collection of independent tasks (state is derived)
Problem Structured evidence for warn / block / fail
Changes / Plan Effects that happened vs would happen
Conclusion Headline + Changed / Partial / Cancelled + exit code
Main Finish + Close + process exit code for CLI entrypoints

Do not put schedulers, RunAll, retries, or shell execution in this library. Review rule API-026 flags those helpers only on evo receivers (AST), not strings.Map.

Develop

mise run setup    # go mod download
mise run test     # unit + roast
mise run test-race
mise run conformance
mise run traceability   # all §31 IDs present
mise run ci             # lint + test + scan + conformance + traceability

Trunk is configured daemonless (--monitor=false). Prefer mise over raw tools.

Conformance (roast)

conformance/ is the executable specification (Raku/roast model):

  • TRACEABILITY.md — all 272 §31 IDs dispositioned (267 pass, 5 waived with reason + owner for external/manual only; 0 untested)
  • schema/scenario.v1.json — declarative scenario dialect
  • scenarios/*.json + Go Appendix H tests (appendix_h_test.go)

Architecture source (current design candidate): docs/architecture/EVIDENT_OUTPUT_ARCHITECTURE_SPEC_v0.5.md. Prior implemented baseline: docs/architecture/EVIDENT_OUTPUT_ARCHITECTURE_SPEC_v0.3.md.

Completeness vs §31 (v0.3 matrix): docs/architecture/COMPLETENESS_MATRIX.md (267 pass / 5 waived).

Examples (adoption ladder)
examples/print/              Print, Printf, Println
examples/verbose/            visibility gating (--verbose)
examples/repo-status/        Tasks, Problems, actions
examples/install-pipeline/   Tasks + Capture
examples/migrate/            Plan versus Changes
examples/doctor/             severity dialect + WriteJSON
examples/data-command/       machine stdout / human stderr (ResultWriter)
examples/scope-plugin/       Scope + ID for plugin namespaces
examples/live-progress/      ordinary multi-progress
examples/debug-history/      slog durable debug
examples/debug-pane/         rolling slog viewport
examples/terminal-driver/    advanced custom TerminalDriver
mise run examples          # non-interactive batch
go run ./examples/print/
go run ./examples/verbose/ --verbose
CLI
go run ./cmd/evident-output review path/to/file.go   # JSON findings (exit 1 if recheck_required)
go run ./cmd/evident-output preview --item=status --state=ok
go run ./cmd/evident-output explain API-006
go run ./cmd/evident-output version
MCP (stdio)

The companion server evident-output-mcp is a local stdio MCP process (no hosted URL). Stdin/stdout are JSON-RPC only; logs go to stderr. Transport supports NDJSON (MCP spec) and Content-Length framing (some client SDKs).

Advertised tool names use underscores (Grok rejects dotted tool names and then registers tool_count: 0). Dotted aliases still work on tools/call.

Tool name (tools/list) Grok use_tool id Purpose
evident_output_list_guides evident-output__evident_output_list_guides Guidance catalog
evident_output_get_guidance evident-output__evident_output_get_guidance Sections by id
evident_output_review evident-output__evident_output_review Go / transcript / JSON review
evident_output_preview evident-output__evident_output_preview Plain profile previews
evident_output_explain evident-output__evident_output_explain Rule id (rule_id)

explain arguments: { "rule_id": "DOM-011" } (not id).

Install the binary (pinned)
mkdir -p "$HOME/.local/bin"

# Module install (network + sumdb) — pin a release tag, never @latest:
GOBIN="$HOME/.local/bin" go install \
  github.com/zachbornheimer/evident-output/cmd/evident-output-mcp@v0.2.16

# Or from a local clone of this repo:
#   git clone https://github.com/zachbornheimer/evident-output.git
#   cd evident-output && go build -o "$HOME/.local/bin/evident-output-mcp" ./cmd/evident-output-mcp

"$HOME/.local/bin/evident-output-mcp" --version

Host configs must use an absolute command (or ${HOME}/… where the host expands it). Bare evident-output-mcp fails when the agent process PATH omits ~/.local/bin.

Verify without restarting an existing TUI session
# Process-level handshake
grok mcp doctor evident-output --json
# expect: healthy=true, "5 tools discovered", protocol 2025-06-18

# Fresh agent process (same attach path as the TUI); use any trusted cwd:
grok -p 'Call use_tool on evident-output__evident_output_list_guides with {}. Reply CONNECTED and the text field, or FAILED.' \
  --output-format plain \
  --max-turns 5 \
  --always-approve
# expect: CONNECTED / "5 guides"

If doctor is green but headless says FAILED, check session events.jsonl for mcp_server_connected with "tool_count":0 (tool names/schemas rejected) vs mcp_server_failed (spawn/handshake).

NDJSON smoke (no Grok required):

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual","version":"0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  | "$HOME/.local/bin/evident-output-mcp" 2>/dev/null
Host config snippets (print-only)
"$HOME/.local/bin/evident-output-mcp" config --client grok
# also: claude-code|codex|gemini|opencode  — uses ${HOME}/.local/bin/…

Integrations: integrations/ · skill: skills/cli-output/SKILL.md

Grok (xAI TUI / Build)
# $HOME/.grok/config.toml  (${HOME} expanded by Grok)
[mcp_servers.evident-output]
command = "${HOME}/.local/bin/evident-output-mcp"
enabled = true
startup_timeout_sec = 30
grok mcp add evident-output -- "$HOME/.local/bin/evident-output-mcp"
grok mcp list
grok mcp doctor evident-output --json

Project scope only starts when the folder is trusted. Prefer user scope for always-on tools.

See integrations/grok/README.md.

Claude Code / Cursor / Codex
"$HOME/.local/bin/evident-output-mcp" config --client claude-code
"$HOME/.local/bin/evident-output-mcp" config --client codex

Review kinds for evident_output_review: go (default), transcript, json / structured.

Examples

Small real programs (flags, help, exit codes) — not snippets. Copy a whole folder as a starting shape.

Example Pattern
repo-status Parallel Tasks (done / blocked / warn), conclusion exit code
install-pipeline Tasks collection with Progress/Bytes/Fail (final report)
migrate Plan dry-run vs Changes apply (--apply)
doctor Mixed doctor items; --json snapshot on stdout
data-command Data command: JSON stdout, human report stderr
live-progress Live multi-progress: bars + indeterminate phases (ANSI on stderr)
debug-history DebugHistory: durable HH:MM:SS.mmm [DEBUG] … above live/items
debug-pane DebugPane: rolling slog pane; --fail keeps diagnostics tail
mise run examples                          # all, back-to-back with headers
go run ./examples/repo-status/ --name my-app
go run ./examples/install-pipeline/
go run ./examples/migrate/                 # dry-run plan
go run ./examples/migrate/ --apply
go run ./examples/doctor/ --json | jq .conclusion
go run ./examples/data-command/ 2>/dev/null | jq .
go run ./examples/live-progress/              # in-place ANSI live region (real TTY)
go run ./examples/live-progress/ --frames     # numbered frames you can scroll
go run ./examples/debug-history/              # history-mode debug interleave
go run ./examples/debug-pane/                 # pane removed on success
go run ./examples/debug-pane/ --fail          # failure preserves diagnostics tail

# mise run examples: uses live ANSI when stderr is a TTY; --frames otherwise.
# EVO_EXAMPLES_FRAMES=1 mise run examples   # force scrubable frames in the batch
Machine output
snap := out.Snapshot()
plain, _ := evo.RenderPlain(snap, evo.PlainOptions{Width: 80})
jsonBytes, _ := evo.EncodeJSON(snap)
jsonl, _ := evo.EncodeJSONL(out.Events())

Schemas: schema/output.v1.json, schema/event.v1.json.

Production ANSI driver
import "github.com/zachbornheimer/evident-output/terminal"

drv := terminal.NewANSI(os.Stderr, terminal.WithInteractive(true), terminal.WithSize(80, 24))
out := evo.Init(evo.Config{Title: "deploy", Options: []evo.Option{evo.Terminal(drv)}})

No To() needed: the driver owns rendering, and evident-output detects its Sink() and routes the residual/plain projection there too — the conclusion band renders exactly once, never a second time on a different stream.

Interactive (testkit / virtual terminal)
screen := testkit.NewScreen(testkit.Interactive(), testkit.Width(80), testkit.NoColor())
clock := testkit.NewClock()
out := evo.Init(evo.Config{Options: []evo.Option{
    evo.Terminal(screen),
    evo.Clock(clock),
    evo.VisibilityDelay(150 * time.Millisecond),
    evo.MaxFrameRate(20),
}})
// Phase/Progress draw a live region; instant Done before the threshold does not flash.
// DebugHistory (default): out.Debug → durable above live (timestamp + [DEBUG]).
// DebugPane(...): rolling slog viewport in the live region; optional failure tail.

Contributing

See CONTRIBUTING.md (DCO sign-off). Red test → green → refactor. Small conventional commits.

Documentation

Overview

Package evo is Evident Output: a presentation library for CLI state, progress, evidence, changes, plans, messages, actions, and conclusions.

Application code owns execution. Evo owns presentation.

func main() {
    evo.Init(evo.Config{Title: "repo"}) // first statement — arms first paint before any I/O
    os.Exit(evo.Main(run))
}

func run() error {
    evo.Println("Reading configuration")
    evo.Task("working tree").Done()
    t := evo.Task("fetch")
    output := t.Evidence()
    // run.Run(ctx, "git", args, output); t.Fail(..., output.DetailTail()) on error
    return nil // Block is a presentation outcome, not a Go error
}

Adoption ladder (guess-driven defaults — the naive spelling is the correct one):

  1. evo.Init(Config) once in main, before any I/O; os.Exit(evo.Main(run)) — dry-run wording, empty-case, and exit codes are all owned; run returns only error.
  2. Print / Printf / Println / Verbose — start as casually as fmt.
  3. evo.Task(name) for everything — a check/gate resolved directly (Done/Warn/Block/Fail/Skip, no Phase/Progress call) renders as a fact row; work with Phase/Progress or a mutation verb (Add/Delete/Create/Update/Remove/Write/Push/Record/RecordName) shows a spinner while running — the verb picks [planned] vs [changed] from Config.DryRun; no call site ever flips its own tense. name is a printf format whenever args follow it (evo.Task("build %s", ref)); no args leaves name untouched.
  4. evo.Task(name).Each(items) for loop progress (absolute, never double-counted). Each takes []string (the item name becomes the live Phase); for any other slice type, drive the same absolute progress with EachN(len(items)) — no []string copy needed just to get a progress bar. .PhaseWriter() as cmd.Stdout so a talkative child's last line becomes the live Phase; Task.Run(cmd) wires an *exec.Cmd through that same capture/phase plumbing in one call and hands back the subprocess error verbatim for the caller to resolve. An item that fails inside the loop body resolves on the loop's own task handle (task.Fail(...); break) — never a second evo.Task declared per item — leaving Progress sealed at the count already reached (release-gate round 6 finding 7).
  5. evo.Task(name).Skipped(evo.Reason("..."), name) / .Kept(evo.Reason("..."), name) — taxonomy counted and summed, never a bare "skipped N". evo.Reason(name) is a get-or-create lookup on the default instance: the same string at every call site merges into one bucket, so an inline evo.Reason("protected") is always legal — lifting it to a package var is optional, never required for correctness. Individual names render under Config.Verbosity: VerbosityVerbose (see doc there); at the default VerbosityNormal the human line stays the aggregated "! skipped N (...)" count. The names are never dropped — they always live on TaskSnapshot.Skipped/Kept (Output.Snapshot / TaskHandle.Snapshot); the wire JSON document does not carry them.
  6. evo.Confirm(question, ...) — owns the whole ask-decide-resolve gate (prompt, quiesce, Done/Blocked resolution, exit code). question is verbatim text, not a printf format like Task/Group/Reason/Phase/Skip's text — use fmt.Sprintf to build a dynamic question first. Confirm is the one entity-text spelling that stays non-printf (release-gate round 6 finding 4).
  7. evo.Group(name) for named children with derived, auto-lifecycle state.
  8. task.Fail(summary) / task.Block(summary) are statements — no return value, so a bare call is errcheck-clean. `return task.Failf("schema mismatch: %w", err)` (task declared as evo.Task("validate manifest")) builds and returns one error in a single line: a trailing ": %w"/", %w" splits the formatted text into the rendered summary and an evidence line for the wrapped error; Blockf is the same for Block. The summary states WHAT went wrong, not the task's own name again — the rendered row already carries the task label, so a summary of "validate manifest: %w" would just repeat it back. Warn, and success/skip verbs, stay void too — this is never fluent chaining. Done/Warn/Task/Group/Reason/Phase/Skip are printf-variadic themselves (fmt.Sprintf semantics when args follow); there is no separate Donef/Warnf/Taskf/Reasonf/Phasef/ Skipf (C6). Output.Failf stays void rather than mirroring TaskHandle.Failf's *Failure return (release-gate round 4 finding 5): every call site uses it as a bare statement, a returned error would fail errcheck at each of them with no lint-config exception on this repo, and there is no per-call Next chain for an output-level failure to attach to the way TaskHandle.Failf's *Failure attaches to its task (Output.Next already covers the output-level case). Documented asymmetry, not an oversight.
  9. Config{Debug: evo.DebugConfig{Level: evo.LevelDebug}} selects the journal threshold for Debug/Capture mirrors and the slog bridge. evo.LogLevel is its own type, distinct from stdlib slog.Level — SlogHandler translates between the two internally, but Config.Debug.Level itself never takes a slog.Level value. LevelUnset (the zero value) resolves to LevelInfo; LevelTrace/LevelDebug are the two levels that surface Debug journal lines. Package-level evo.SlogHandler() journals to the default instance, the same default-instance sugar evo.Task/evo.Verbose already offer.

Ordinary surface: evo.Init/evo.Main, Print*, evo.Task/evo.Group (+ ID), Task.Evidence, Task.Each / Task.PhaseWriter / Task.Run, Task.Fail / Task.Failf / Task.Block / Task.Blockf, evo.Confirm, evo.Reason, Changes/Plan (tooling call sites, see below), slog via SlogHandler (level from Config.Debug.Level).

Advanced surface, for testing and tooling call sites that need a hosted instance instead of the package-level default: Config.Isolated returns an independent *Output that never touches package state; Output.Run(run func(*Output) error) seals it (the hosted counterpart of Main's run func() error, called on the *Output itself instead of the default instance); Config.Options is the raw-Option escape hatch for exact writer/ terminal/clock wiring. Plan/Changes for the would/did split without a Task, session Evidence, terminal drivers, and testkit.

Index

Constants

View Source
const (
	ExitOK        = 0
	ExitBlocked   = 1
	ExitFailed    = 2
	ExitCancelled = 130
)

Default exit codes from architecture §26.

View Source
const DefaultVisibleNames = 3

DefaultVisibleNames is how many names TruncateNames keeps before summarizing.

View Source
const EventSchemaVersion = "0.2"

EventSchemaVersion is the durable event schema version. Tracks the 0.2 contract series (pre-1.0 wire format may still evolve).

View Source
const JSONSchemaVersion = "0.3"

JSONSchemaVersion is the final JSON document schema version. Tracks the 0.3 contract series (pre-1.0 wire format may still evolve). Bumped from 0.2: "items" no longer exists as a separate wire kind — the item/task fold means every entity (including a fact-check resolved without ever running) is a "tasks" row (CHANGELOG "Unreleased").

View Source
const PublishedRelease = "v0.2.16"

PublishedRelease is the single source of truth for the current published module and MCP pin used in install guidance.

Maintenance class this protects (v0.2.10 hygiene, generalized):

  • skills / integrations / README disagree on which tag to install
  • MCP config generator falls back to a stale hardcoded tag
  • portable docs recommend @latest or a personal-machine clone path
  • signed tags ship with stale README pins (next patch, never rewrite history)

When cutting a release:

  1. Set PublishedRelease to the new tag (e.g. "v0.2.11").
  2. Run: go run ./scripts/sync-release-pins
  3. Run: go test . -run VersionDrift
  4. Tag that commit; do not move prior tags.

version_drift_test.go enforces the portable surface stays synchronized.

Variables

View Source
var (
	ErrClosed             = errors.New("evo: output is closed")
	ErrAlreadyResolved    = errors.New("evo: entity is already resolved")
	ErrUnresolvedTask     = errors.New("evo: task has no final state")
	ErrInvalidProgress    = errors.New("evo: invalid progress")
	ErrProgressRegression = errors.New("evo: progress moved backward")
	ErrDuplicateKey       = errors.New("evo: duplicate entity key")
	ErrInvalidConfig      = errors.New("evo: invalid configuration")
	ErrRenderer           = errors.New("evo: renderer failure")
	ErrFinishing          = errors.New("evo: output is finishing")
	ErrLimitExceeded      = errors.New("evo: resource limit exceeded")
	ErrReasonSkipOnly     = errors.New("evo: reason restricted to Skipped was recorded via Kept")
	ErrReasonWrongTask    = errors.New("evo: reason restricted to another task")
	ErrConcurrentRunning  = errors.New("evo: two siblings in the same collection are Running simultaneously")
	ErrDryRunDeclaredLate = errors.New("evo: DeclareDryRun called after a durable row was already emitted")
	// ErrTerminalWithoutSink is recorded when Config.Options supplies a
	// Terminal driver but no primary writer (To), and the driver cannot
	// report its own destination (it does not implement the Sink() io.Writer
	// accessor) — release-gate round 8 finding 2. Without either, a
	// non-interactive Finish has nowhere to write the residual/plain
	// projection and would otherwise render nothing at exit 0.
	ErrTerminalWithoutSink = errors.New("evo: Terminal driver configured without a primary writer")
)

Sentinel misuse and lifecycle errors recorded by the output aggregate.

Functions

func AnyBlockedSoFar added in v0.3.0

func AnyBlockedSoFar() bool

AnyBlockedSoFar reports whether any Task on the default instance is currently Blocked — see Output.AnyBlockedSoFar. Package-level parity with Task/Group/Print* (beginner-7): a caller using the default-instance facade throughout a run should never have to reach for a hosted *Output just to check this one thing.

func AnyFailed added in v0.3.0

func AnyFailed() bool

AnyFailed reports whether any Task on the default instance is currently Failed — see Output.AnyFailed.

func Confirm added in v0.3.0

func Confirm(question string, opts ...ConfirmOption) bool

Confirm asks a yes/no question on the default instance. See Output.Confirm.

func Delay added in v0.2.7

func Delay(d time.Duration) *time.Duration

Delay returns a non-nil *time.Duration for Config fields where zero is meaningful.

cfg.VisibilityDelay = evo.Delay(0)                      // immediate
cfg.VisibilityDelay = evo.Delay(80 * time.Millisecond) // explicit default

func EncodeJSON

func EncodeJSON(s Snapshot) ([]byte, error)

EncodeJSON encodes a snapshot as final JSON (§25.1 / §25.4).

func EncodeJSONL

func EncodeJSONL(events []Event) ([]byte, error)

EncodeJSONL encodes durable events as JSON Lines (§25.2 / §25.4).

func IsCharDevice

func IsCharDevice(w io.Writer) bool

IsCharDevice reports whether w is an *os.File backed by a character device (typical interactive TTY). Pipes, files, and non-file writers return false.

Prefer Config{Stdout, Stderr} for ordinary dual-stream construction. This helper remains for hosts that choose Plain/NoColor from a concrete writer.

func Main

func Main(run func() error) int

Main runs a CLI presentation lifecycle against the package-level default instance (see Init) and returns the process exit code.

func main() {
    evo.Init(evo.Config{Title: "tool"})
    os.Exit(evo.Main(run))
}

run reports only an error; the Conclusion (0/1/2/130) is the sole source of the exit code — see Output.Run for the full lifecycle and signal contract.

func Pluralize added in v0.3.0

func Pluralize(quantity int64, singular string) string

Pluralize returns the plural spelling of singular when quantity != 1 (an irregular table for the common exceptions, else the regular English +s/+es/+ies rule), and singular unchanged when quantity == 1 — the object-pluralization counterpart to conjugatePast's verb table, so a mutation call site stops writing its own singular/plural noun() switch:

worktrees.Remove(n, evo.Pluralize(n, "worktree")) // "1 worktree" / "2 worktrees"

A glob/path/symbol object ("stale origin/*", "*.tmp") renders unchanged at any quantity — see isPluralizableWord — instead of blindly gaining a trailing "s" ("stale origin/*s") that the +s/+es/+ies rule was never designed to produce.

func Print added in v0.3.0

func Print(args ...any)

Print formats like fmt.Sprint and enqueues human-facing text on the default instance.

func Printf added in v0.3.0

func Printf(format string, args ...any)

Printf formats like fmt.Sprintf and enqueues human-facing text on the default instance.

func Println added in v0.3.0

func Println(args ...any)

Println formats like fmt.Sprintln and enqueues a complete line on the default instance.

func RenderPlain

func RenderPlain(s Snapshot, opts PlainOptions) ([]byte, error)

RenderPlain projects a snapshot to plain text without terminal ownership.

func SetDefault added in v0.3.0

func SetDefault(out *Output)

SetDefault installs out as the package-level default Output.

func SlogHandler added in v0.3.0

func SlogHandler() slog.Handler

SlogHandler returns a slog.Handler journaling to the default instance — package-level sugar (release-gate round 8 finding 6) matching Task/Verbose: a caller using the default-instance facade throughout a run should never have to reach for a hosted *Output just for the slog bridge. See Output.SlogHandler for the level policy and full contract.

func TruncateNames added in v0.2.16

func TruncateNames(names []string, visible int, profile ...GlyphProfile) string

TruncateNames joins names for a skip/kept-style summary. Empty names yields "". visible <= 0 uses DefaultVisibleNames. When more names remain than visible, appends the overflow glyph for profile (evo-rec.md's tightened vocabulary: "… +N more", ASCII "... +N more") instead of a bare ", +N" that carries no glyph at all. profile is variadic so the simplest call — TruncateNames(names, visible) — stays correct: an omitted profile renders the Unicode overflow glyph; a caller that has already resolved a GlyphProfile passes it explicitly.

Types

type Action

type Action struct {
	Label                string
	Command              *CommandSpec
	URL                  string
	File                 string
	Explanation          string
	RequiresConfirmation bool
	Destructive          bool
}

Action is a recommended next step for the user.

func Command

func Command(executable string, args ...string) Action

Command builds an action with an executable and arguments. Display-bound strings are sanitized at construction.

func Label added in v0.3.0

func Label(text string) Action

Label builds a plain-text recommended next step with no executable command (e.g. a policy hint like "pass --yes to confirm non-interactively").

type Attachment added in v0.3.0

type Attachment struct {
	Label string
	Value string
}

Attachment is an additional label/value problem attachment.

Named Attachment (not Evidence) because Evidence names the retained process-output sink (see Evidence in capture.go) — this is a single labeled fact attached to a Problem, a different concept from that sink.

type Changes

type Changes struct {
	// contains filtered or unexported fields
}

Changes is a handle for durable effects that already occurred.

func (*Changes) Added

func (c *Changes) Added(quantity int, object string) *Changes

Added records an added quantity.

func (*Changes) Created

func (c *Changes) Created(object string) *Changes

Created records a created object.

func (*Changes) Deleted added in v0.3.0

func (c *Changes) Deleted(quantity int, object string) *Changes

Deleted records a deleted quantity. Part of the verb set unified across TaskHandle/Changes/Plan (C10) — TaskHandle/Plan already had Delete; Changes was missing its past-tense counterpart.

func (*Changes) Pushed added in v0.3.0

func (c *Changes) Pushed(quantity int, object string) *Changes

Pushed records a pushed quantity. Part of the verb set unified across TaskHandle/Changes/Plan (C10) — Push was previously TaskHandle-only.

func (*Changes) Record

func (c *Changes) Record(verb string, quantity int, object string) *Changes

Record records a verb/quantity/object effect. A zero quantity records no row (there is nothing to show) but still remembers verb as the section's intended verb, so a section that ends up with no rows at all still renders "nothing to <verb> <subject>" (evo-rec.md guess-driven default #1: "Zero mutations recorded → nothing to delete") instead of inventing a "did 0" row.

func (*Changes) RecordName added in v0.2.16

func (c *Changes) RecordName(verb, object string) *Changes

RecordName records a verb and one named object without a quantity. Quantity is for collapsed counts; RecordName is one named object.

func (*Changes) Removed

func (c *Changes) Removed(quantity int, object string) *Changes

Removed records a removed quantity.

func (*Changes) Updated

func (c *Changes) Updated(quantity int, object string) *Changes

Updated records an updated quantity.

func (*Changes) Wrote

func (c *Changes) Wrote(object string) *Changes

Wrote records a written object.

type ChangesSnapshot

type ChangesSnapshot struct {
	ID      string
	Subject string
	Records []EffectRecord
	// IntendedVerb is the first mutation verb recorded for this section, even
	// when every record ended up with zero quantity and none survived into
	// Records. Empty when no verb was ever recorded (evo-rec.md "empty effect
	// section grammar"). Never caller-assembled.
	IntendedVerb string
}

ChangesSnapshot is an immutable changes section.

type ColorMode added in v0.2.0

type ColorMode int

ColorMode selects color policy. The zero value is automatic.

const (
	// ColorAuto uses TTY detection and honors NO_COLOR.
	ColorAuto ColorMode = iota
	// ColorAlways forces semantic color even off a TTY.
	ColorAlways
	// ColorNever disables color.
	ColorNever
)

type CommandSpec

type CommandSpec struct {
	Executable string
	Args       []string
	WorkingDir string
}

CommandSpec is an executable plus argv (never a shell string).

type Conclusion

type Conclusion struct {
	State   ConclusionState
	Subject string
	Changed bool
	Partial bool
	// Warned reports that at least one task or collection resolved Warning
	// while the headline State settled on something else (release-gate
	// round 8 finding 3) — an otherwise-OK run must not read as silently
	// clean just because Warning sits below the OK-family headline in
	// precedence. Always false when State is itself StateWarning: that
	// headline already says it (see inferConclusion).
	Warned      bool
	Cancelled   bool
	Explanation string
	Tasks       []TaskSnapshot
	Collections []TasksSnapshot
	Changes     []ChangesSnapshot
	Plans       []PlanSnapshot
	Actions     []Action
	ExitCode    int
}

Conclusion is the multidimensional meaning of a finished command.

func (Conclusion) AnyBlocked

func (c Conclusion) AnyBlocked() bool

AnyBlocked reports whether the finished conclusion is blocked, or any task snapshot is.

type ConclusionJSON

type ConclusionJSON struct {
	State       ConclusionState `json:"state"`
	Changed     bool            `json:"changed"`
	Partial     bool            `json:"partial"`
	Cancelled   bool            `json:"cancelled"`
	ExitCode    int             `json:"exit_code"`
	Explanation string          `json:"explanation,omitempty"`
}

ConclusionJSON is JSON-friendly conclusion.

type ConclusionState

type ConclusionState string

ConclusionState is the human headline for a finished output.

const (
	StateReady     ConclusionState = "ready"
	StateChanged   ConclusionState = "changed"
	StateUnchanged ConclusionState = "unchanged"
	StateWarning   ConclusionState = "warning"
	StateBlocked   ConclusionState = "blocked"
	StateFailed    ConclusionState = "failed"
	StateCancelled ConclusionState = "cancelled"
	StatePlanned   ConclusionState = "planned"
)

type Config

type Config struct {
	// Title is the subject shown in the conclusion (formerly For's argument).
	Title string

	// Subject is an optional durable line rendered once, immediately, right
	// under the title — a repo path, a target host, the thing every
	// projection needs the reader to see up front. Set it once in Config
	// instead of calling out.Println(root) (or whatever the identifying
	// value is) at every projection/command that needs to show it.
	//
	// When the subject text isn't known until after Init (e.g. resolved
	// from a flag parsed later, but still before any other I/O), call
	// Output.Subject(text) instead — same one-shot durable-line semantics,
	// as a post-construction setter (I3).
	Subject string

	// Stdout is the ordinary human stream (default os.Stdout).
	// In FormatData mode, Stdout is reserved for domain payload via ResultWriter;
	// human presentation moves to Stderr.
	Stdout io.Writer
	// Stderr owns diagnostics by default (default os.Stderr).
	Stderr io.Writer
	// Result is an optional domain-payload writer. When nil and Format is
	// FormatData, ResultWriter returns Stdout. Presentation never writes here.
	Result io.Writer
	// Stdin is the facade Confirm reads one answer line from (default os.Stdin).
	Stdin io.Reader

	// Verbosity gates Verbose() print messages (default VerbosityNormal).
	Verbosity Verbosity
	// Color policy (default ColorAuto).
	Color ColorMode
	// Format projection mode (default FormatHuman).
	Format Format

	// Debug configures the debug journal.
	Debug DebugConfig

	// Advanced (optional) — zero values inherit safe defaults.
	Clock    TimeSource
	Redactor Redactor
	Terminal TerminalDriver
	Strict   bool
	Width    int
	// VisibilityDelay is the wait before the first live paint.
	// nil means default (80ms). Non-nil is exact, including 0 for immediate.
	// Use evo.Delay(d) to set a value from a duration literal.
	VisibilityDelay *time.Duration
	MaxFrameRate    int
	MaxEntities     int
	MaxEvents       int
	// Plain disables live interactive frames, on a TTY or off (C3: replaces
	// the former separate ForcePlain and NonInteractive fields — every read
	// site combined them with OR, so there was never a distinct behavior
	// between the two to preserve).
	Plain bool

	// Glyphs selects the state-glyph vocabulary (default GlyphsAuto: Unicode
	// off a TTY or on a UTF-8 locale, ASCII on a non-UTF-8 interactive TTY).
	Glyphs GlyphProfile

	// FailedExitCode is the process exit code when the conclusion is failed.
	// Zero means use ExitFailed (2). Set to 1 for conventional CLI tools that
	// treat any non-zero failure as exit 1 (e.g. quality gates / git hooks).
	FailedExitCode int

	// DryRun declares this run a dry run once, for the whole process: every
	// TaskHandle mutation verb (Delete, Create, Update, Remove, Write, Push,
	// Record, RecordName) renders as a [planned] row with the imperative verb
	// instead of a [changed] row with the past-tense verb. No call site writes
	// its own tense.
	DryRun bool

	// Isolated returns an independent Output that never touches package
	// state: it is not installed as the package-level default and does not
	// arm first paint. Use for parallel tests and embedders that hold their
	// own *Output instead of going through Default()/Task()/Print() et al.
	// This is the one and only opt-out from default installation — it
	// applies identically whether or not Options is also set (release-gate
	// round 8 finding 1): Options is an orthogonal escape hatch for how the
	// Output is built, not for whether it becomes the default.
	Isolated bool

	// Options is the advanced, raw Option escape hatch for tests and
	// specialized embedding (custom Terminal, Clock, exact writer wiring)
	// that need to bypass Config's ordinary stream/TTY/color inference
	// entirely. When set, every other Config field except Title, DryRun, and
	// Subject is ignored and the Output is built from these Options alone.
	// It still installs as the package-level default and arms first paint
	// exactly like every other Init call, unless Isolated is also set —
	// see Isolated and Init.
	Options []Option
}

Config is the sole application-facing construction surface.

Zero values mean automatic/default behavior. Use DefaultConfig() when you need a mutable baseline for advanced fields.

out := evo.Init()
out := evo.Init(evo.Config{Title: "bpp-csharp"})
cfg := evo.DefaultConfig(); cfg.Title = "x"; out := evo.Init(cfg)

func DefaultConfig added in v0.2.0

func DefaultConfig() Config

DefaultConfig returns a fresh ordinary CLI configuration. Mutating the result does not affect later DefaultConfig() calls.

type ConfirmOption added in v0.3.0

type ConfirmOption func(*confirmConfig)

ConfirmOption configures a Confirm gate.

func AssumeYes added in v0.3.0

func AssumeYes(v bool) ConfirmOption

AssumeYes skips the interactive prompt when v is true (the caller's --yes flag). The gate resolves immediately as Done "assumed --yes", and Confirm returns true without touching stdin or the live region.

func ConfirmDetail added in v0.3.0

func ConfirmDetail(lines ...string) ConfirmOption

ConfirmDetail attaches one or more context lines rendered under the "? <question> y/N" prompt line — e.g. what will actually happen, which host is affected. Repeated calls append.

func Destructive added in v0.3.0

func Destructive() ConfirmOption

Destructive annotates the rendered prompt line as "(destructive)".

func PolicyFlag added in v0.3.0

func PolicyFlag(flag string) ConfirmOption

PolicyFlag sets the non-interactive policy hint to a flag on the caller's own executable (e.g. "--apply") instead of PolicyHint's explicit foreign- command spelling — the common case where the flag that unblocks a non-interactive run belongs to the very binary calling Confirm. The executable name is resolved from the same identity Config.Title/I2's executable-basename fallback uses. Reach for PolicyHint instead when the hint should point at a different tool.

func PolicyHint added in v0.3.0

func PolicyHint(command string, args ...string) ConfirmOption

PolicyHint overrides the Next action rendered by Confirm's non-interactive policy block. Without this option the block points at "pass --yes to confirm non-interactively" — wrong for a caller whose confirm flag isn't --yes (e.g. zq clean-repo's --apply). Pass the caller's own executable and args so the hint names the flag that actually unblocks it.

type DebugConfig added in v0.2.0

type DebugConfig struct {
	// Level is the minimum debug journal level. Zero (LevelUnset) resolves to
	// LevelInfo. Use LevelTrace or LevelDebug to surface Debug/Capture mirrors.
	Level LogLevel
	// View selects history vs pane presentation (default History).
	View DebugPresentation
	// PaneHeight is used when View is DebugPresentationPane (default 5).
	PaneHeight int
	// NewestFirst orders the pane (default true when zero-config pane).
	NewestFirst *bool
	// PreserveAlways forces a diagnostic tail on every Finish in pane mode.
	PreserveAlways bool
	// AddSource resolves each record's call site to a source=file.go:line
	// field on human/pane/history rendering (slog.HandlerOptions.AddSource
	// semantics). Off by default: the raw program counter is always kept on
	// LogRecord.PC for machine consumers, but a human debug line never shows
	// a bare pc=<uintptr> unless this is set.
	AddSource bool
}

DebugConfig configures the debug journal presentation.

type DebugPaneOption

type DebugPaneOption interface {
	// contains filtered or unexported methods
}

DebugPaneOption configures DebugPane presentation.

func NewestFirst

func NewestFirst() DebugPaneOption

NewestFirst orders the pane with the most recent record first (default).

func OldestFirst

func OldestFirst() DebugPaneOption

OldestFirst orders the pane chronologically (oldest visible first).

func PaneHeight

func PaneHeight(lines int) DebugPaneOption

PaneHeight sets how many debug records are visible in the pane (not including heading).

func PreserveDebugTail

func PreserveDebugTail() DebugPaneOption

PreserveDebugTail always emits a bounded diagnostic tail under the final report. Without this, pane mode still preserves a tail on failed/blocked/cancelled conclusions.

type DebugPresentation

type DebugPresentation int

DebugPresentation selects how structured debug records project to a TTY (§4.6 / §21.3).

const (
	// DebugPresentationHistory appends durable scrollback above the live region (default).
	DebugPresentationHistory DebugPresentation = iota
	// DebugPresentationPane keeps a bounded rolling viewport inside the live region.
	DebugPresentationPane
)

type EffectRecord

type EffectRecord struct {
	Verb     string
	Quantity int64
	HasQty   bool
	Object   string
}

EffectRecord is one semantic change or plan row.

type EntityOption added in v0.2.3

type EntityOption interface {
	// contains filtered or unexported methods
}

EntityOption configures Task declaration (stable keys). The common path remains Task("label"); options are platform-scale.

func ID added in v0.2.3

func ID(id string) EntityOption

ID sets a stable machine key independent of the human label. Labels may be localized or reworded; IDs should not.

out.Task("download base image", evo.ID("build.base-image.download"))

func StartPhase added in v0.3.0

func StartPhase(text string) EntityOption

StartPhase declares a task with its first Phase already set, in one call — declare-then-phase collapsed into the declaration itself:

out.Task("download base image", evo.StartPhase("resolving tag"))

is exactly out.Task("download base image").Phase("resolving tag"), with no separate statement (and no gap where the task sits Pending) between them.

type EntityState

type EntityState string

EntityState is the lifecycle state of an item or task.

C11 naming sweep: these members stay bare (Done, Failed, Blocked, ...) rather than gaining a State* prefix to match ConclusionState below — prefixing would collide outright with ConclusionState's own StateFailed/ StateBlocked/StateCancelled/StateWarning constants (same package, same identifiers, different types is still a duplicate declaration in Go). Renaming ConclusionState's constants instead would ripple into the JSON wire (schema 0.3, frozen this release) and every existing golden — this is the "document instead" branch the census decision allows.

const (
	Pending    EntityState = "pending"
	Running    EntityState = "running"
	Done       EntityState = "done"
	Warning    EntityState = "warning"
	Blocked    EntityState = "blocked"
	Failed     EntityState = "failed"
	Skipped    EntityState = "skipped"
	Cancelled  EntityState = "cancelled"
	Empty      EntityState = "empty"
	Incomplete EntityState = "incomplete"
	// NotStarted marks a group task that never ran because an earlier sibling
	// already failed or was cancelled — rendered "-  <name>  not started" and
	// excluded from the conclusion (the group's verdict comes from the
	// failed/cancelled sibling, not from its unstarted followers).
	NotStarted EntityState = "not_started"
)

type Event

type Event struct {
	SchemaVersion string
	Sequence      uint64
	Timestamp     time.Time
	Type          string
	OutputID      string
	EntityID      string
	Name          string
	State         string
	Completed     *int64
	Total         *int64
	Activation    string
	Payload       map[string]any
}

Event is an immutable journal record.

type EventJSON

type EventJSON struct {
	SchemaVersion string    `json:"schema_version"`
	Sequence      uint64    `json:"sequence"`
	Type          string    `json:"type"`
	OutputID      string    `json:"output_id,omitempty"`
	EntityID      string    `json:"entity_id,omitempty"`
	Name          string    `json:"name,omitempty"`
	State         string    `json:"state,omitempty"`
	Completed     *int64    `json:"completed,omitempty"`
	Total         *int64    `json:"total,omitempty"`
	Activation    string    `json:"activation,omitempty"`
	Timestamp     time.Time `json:"timestamp,omitempty"`
}

EventJSON is a JSON Lines event record (§25.2).

type Evidence

type Evidence struct {
	// contains filtered or unexported fields
}

Evidence is the retained/redacted process-output sink owned by a Task (preferred) or Output. "Stdout" would lie as a name — it also takes stderr and combined writes; Evidence says what it is for: durable, sanitized proof a failure can point back to.

upgrade := out.Task("brew packages")
proof := upgrade.Evidence() // silent retention by default
if err := run.Run(ctx, "brew", args, proof); err != nil {
    upgrade.Failf("brew upgrade failed: %w", err)
    return nil
}
upgrade.Done()

Prefer task.Run for an *exec.Cmd — it wires Evidence and Phase together in one call. Reach for Evidence directly only when the caller already owns stdout/stderr plumbing (a custom runner, a non-exec.Cmd tool integration).

Combined streams by default (P1): Write (merged), Stdout(), and Stderr() all feed the same bounded ring used by Text/Tail/DetailTail. Linters and most subprocess tools write diagnostics on stderr — route both streams into Evidence (or write the combined pipe into it directly) so failure evidence cannot escape the owning Task.

Semantics:

  • Always retains a bounded ring of sanitized lines (evidence exists even when debug presentation is disabled).
  • Default is silent: no Diagnostics/Debug mirror on success.
  • Opt in with MirrorToDiagnostics / MirrorToDebug.
  • Stdout/Stderr have independent pending buffers (no partial-line merge).
  • DetailTail prefers stderr when separate streams were used, else combined.

func (*Evidence) Close added in v0.3.0

func (c *Evidence) Close() error

Close flushes trailing partial lines.

On the root Evidence (task.Evidence()), every stream pending buffer is flushed so Stdout/Stderr partial lines are retained. On a side writer (Stdout/Stderr), only that stream is flushed.

func (*Evidence) DetailTail added in v0.3.0

func (c *Evidence) DetailTail() ProblemOption

DetailTail returns a ProblemOption attaching a user-visible presentation of the capture tail. Prefers stderr when separate streams were used. Sets Problem.EvidenceTail rather than Problem.Detail: when the same Fail/Block call also carries an explicit Detail, that explicit text still renders (as the primary detail line) and this tail renders as an additional evidence line underneath, regardless of which option was passed first.

func (*Evidence) Empty added in v0.3.0

func (c *Evidence) Empty() bool

Empty reports whether no completed lines and no pending fragments exist.

func (*Evidence) Stderr added in v0.3.0

func (c *Evidence) Stderr() io.Writer

Stderr returns a writer that records lines as stderr with its own pending buffer.

func (*Evidence) Stdout added in v0.3.0

func (c *Evidence) Stdout() io.Writer

Stdout returns a writer that records lines as stdout with its own pending buffer.

func (*Evidence) Text added in v0.3.0

func (c *Evidence) Text() string

Text returns all retained combined lines joined by newlines.

func (*Evidence) Write added in v0.3.0

func (c *Evidence) Write(p []byte) (int, error)

Write implements io.Writer. Safe for concurrent use with Tail/DetailTail.

type EvidenceOption added in v0.3.0

type EvidenceOption interface {
	// contains filtered or unexported methods
}

EvidenceOption configures Evidence.

func KeepLastLines added in v0.1.2

func KeepLastLines(n int) EvidenceOption

KeepLastLines sets how many trailing lines are retained (default 200).

func MaxEvidenceBytes added in v0.3.0

func MaxEvidenceBytes(n int) EvidenceOption

MaxEvidenceBytes sets an approximate byte budget for retained lines (default 256KiB).

func MirrorToDebug added in v0.2.2

func MirrorToDebug() EvidenceOption

MirrorToDebug journals each completed line via Debug when DebugLevel allows. Default is off.

func MirrorToDiagnostics added in v0.2.2

func MirrorToDiagnostics() EvidenceOption

MirrorToDiagnostics copies each completed line to the Diagnostics writer. Default is off — Evidence retains proof without displaying it on success.

type EvidenceStream added in v0.3.0

type EvidenceStream uint8

EvidenceStream identifies which process stream a line came from.

const (
	// EvidenceStreamCombined is Write() on the Evidence itself (merged by the runner).
	EvidenceStreamCombined EvidenceStream = iota
	// EvidenceStreamStdout is output.Stdout().
	EvidenceStreamStdout
	// EvidenceStreamStderr is output.Stderr().
	EvidenceStreamStderr
)

type Failure added in v0.3.0

type Failure struct {
	// contains filtered or unexported fields
}

Failure is the error TaskHandle.Failf/Blockf return. It renders exactly like the fmt.Errorf result it replaces (Error() is the formatted summary plus evidence, Unwrap() reaches the %w-wrapped cause so errors.Is/As keep working), so a bare `return task.Failf("clone %s: %w", url, err)` stays a valid error return.

Failure also carries Next/NextCommand, so the remedy for a failure finally has somewhere to attach at the return site instead of a second statement the caller has to remember to write:

return task.Failf("clone %s: %w", url, err).
	Next(evo.Label("check network access"))

func (*Failure) Error added in v0.3.0

func (f *Failure) Error() string

Error returns the rendered failure message.

func (*Failure) Next added in v0.3.0

func (f *Failure) Next(actions ...Action) *Failure

Next attaches a recommended follow-up action to the task this Failure resolved, and returns the same *Failure so it stays valid as a bare error return.

func (*Failure) NextCommand added in v0.3.0

func (f *Failure) NextCommand(executable string, args ...string) *Failure

NextCommand attaches a recommended command action; see Next.

func (*Failure) Unwrap added in v0.3.0

func (f *Failure) Unwrap() error

Unwrap reaches the %w-wrapped cause, so errors.Is/As traverse through a Failure exactly as they did through the fmt.Errorf error it replaces.

type Field

type Field struct {
	Key       string
	Value     any
	Sensitive bool
}

Field is a structured diagnostic or log field.

type FixedClock

type FixedClock struct {
	T time.Time
}

FixedClock always returns the same instant.

func (FixedClock) Now

func (c FixedClock) Now() time.Time

Now returns the fixed instant.

type Format added in v0.2.0

type Format int

Format selects the overall projection mode. Zero is ordinary human output.

const (
	// FormatHuman is ordinary human (and optional interactive) presentation.
	FormatHuman Format = iota
	// FormatData reserves stdout for the application's domain payload; Evo
	// human presentation goes to stderr (data-command mode).
	FormatData
	// FormatExternal disables inline rendering (snapshots only).
	FormatExternal
)

type GlyphProfile added in v0.3.0

type GlyphProfile int

GlyphProfile selects which glyph vocabulary state markers render in (evo-rec.md "Tightened glyph vocabulary", rule GLYPH-001: glyph selection via capability profile, cell-width measurement not rune counts).

The zero value, GlyphsAuto, keeps today's Unicode vocabulary off a TTY (a non-interactive stream can't show a human mojibake, so there is nothing to guard against) and on any TTY whose locale already advertises UTF-8. It downgrades to the ASCII vocabulary only on an interactive terminal without UTF-8 locale support — the one case where the status column would otherwise render as mojibake.

const (
	// GlyphsAuto detects the vocabulary from locale and TTY interactivity.
	GlyphsAuto GlyphProfile = iota
	// GlyphsUnicode forces the Unicode vocabulary regardless of locale.
	GlyphsUnicode
	// GlyphsASCII forces the ASCII vocabulary regardless of locale.
	GlyphsASCII
)

type GroupHandle added in v0.3.0

type GroupHandle struct {
	// contains filtered or unexported fields
}

GroupHandle is the front door for a sequence of steps that must stop on failure: once a child reaches Failed or Cancelled, every later-declared sibling still unresolved auto-resolves to NotStarted ("- <name> not started") — no caller code required. It is a thin identity layer over Tasks (the existing Output.Tasks collection) that adds get-or-create children. Construct one via evo.Group or Output.Group.

func Group added in v0.3.0

func Group(name string, args ...any) *GroupHandle

Group declares (or, for a repeated name, returns) a self-managing task group on the default instance — see Group for the auto-lifecycle contract. name is a printf format when args are present (fmt.Sprintf semantics).

func (*GroupHandle) Snapshot added in v0.3.0

func (g *GroupHandle) Snapshot() TasksSnapshot

Snapshot returns the group snapshot with derived state.

func (*GroupHandle) Summary added in v0.3.0

func (g *GroupHandle) Summary(text string, args ...any) *GroupHandle

Summary sets a success-oriented group summary. text is a printf format when args are present (fmt.Sprintf semantics) — see Task (C6).

func (*GroupHandle) Task added in v0.3.0

func (g *GroupHandle) Task(name string, args ...any) *TaskHandle

Task declares (or, for a repeated name, returns) a child task in declaration order. name is a printf format when args are present (fmt.Sprintf semantics); the get-or-create key is the formatted name. args may also carry evo.ID to set a stable machine key.

type JSONAction

type JSONAction struct {
	Label   string       `json:"label,omitempty"`
	Command *JSONCommand `json:"command,omitempty"`
	URL     string       `json:"url,omitempty"`
}

JSONAction is a wire-format action.

type JSONChanges

type JSONChanges struct {
	ID      string             `json:"id"`
	Subject string             `json:"subject"`
	Records []JSONEffectRecord `json:"records"`
}

JSONChanges is wire-format changes.

type JSONCollection

type JSONCollection struct {
	ID       string      `json:"id"`
	Name     string      `json:"name"`
	State    EntityState `json:"state"`
	Summary  string      `json:"summary,omitempty"`
	Children []string    `json:"children"`
}

JSONCollection is a wire-format task collection with child IDs (§25.1).

type JSONCommand

type JSONCommand struct {
	Executable string   `json:"executable"`
	Args       []string `json:"args,omitempty"`
}

JSONCommand is argv for display.

type JSONDocument

type JSONDocument struct {
	SchemaVersion   string           `json:"schema_version"`
	Output          JSONOutputMeta   `json:"output"`
	Conclusion      ConclusionJSON   `json:"conclusion"`
	TaskCollections []JSONCollection `json:"task_collections"`
	Tasks           []JSONTask       `json:"tasks"`
	Changes         []JSONChanges    `json:"changes"`
	Plans           []JSONPlan       `json:"plans"`
	Messages        []JSONMessage    `json:"messages,omitempty"`
	Actions         []JSONAction     `json:"actions"`
}

JSONDocument is the final machine projection (§25.1).

type JSONEffectRecord

type JSONEffectRecord struct {
	Verb     string `json:"verb"`
	Quantity *int64 `json:"quantity,omitempty"`
	Object   string `json:"object"`
}

JSONEffectRecord is a change/plan row.

type JSONMessage added in v0.2.0

type JSONMessage struct {
	ID         string `json:"id"`
	Visibility string `json:"visibility"`
	Text       string `json:"text"`
}

JSONMessage is a wire-format user-facing message.

type JSONOutputMeta

type JSONOutputMeta struct {
	ID      string `json:"id"`
	Subject string `json:"subject,omitempty"`
}

JSONOutputMeta identifies the output instance.

type JSONPlan

type JSONPlan struct {
	ID      string             `json:"id"`
	Subject string             `json:"subject"`
	Records []JSONEffectRecord `json:"records"`
}

JSONPlan is wire-format plan.

type JSONProblem

type JSONProblem struct {
	Subject string `json:"subject,omitempty"`
	Summary string `json:"summary,omitempty"`
	Detail  string `json:"detail,omitempty"`
	Count   int64  `json:"count,omitempty"`
	Unit    string `json:"unit,omitempty"`
	Code    string `json:"code,omitempty"`
}

JSONProblem is a wire-format problem (no raw Cause by default).

type JSONProgress

type JSONProgress struct {
	Kind      ProgressKind `json:"kind"`
	Completed int64        `json:"completed"`
	Total     int64        `json:"total"`
}

JSONProgress is wire-format progress.

type JSONTask

type JSONTask struct {
	ID       string        `json:"id"`
	Key      string        `json:"key,omitempty"`
	Name     string        `json:"name"`
	State    EntityState   `json:"state"`
	Phase    string        `json:"phase,omitempty"`
	Summary  string        `json:"summary,omitempty"`
	Progress *JSONProgress `json:"progress,omitempty"`
	Problems []JSONProblem `json:"problems,omitempty"`
}

JSONTask is a wire-format task.

type LiveSurface

type LiveSurface interface {
	TerminalDriver
	Columns() int
	Rows() int
	IsInteractive() bool
	WriteLive(text string)
	ClearLive()
	WriteDurable(line string)
	WriteFinal(text string)
}

LiveSurface is an interactive terminal sink for live-region rendering. testkit.Screen implements this; production drivers can as well.

type LogLevel

type LogLevel int

LogLevel is a diagnostic severity.

The zero value is LevelUnset (Config resolves it to LevelInfo). Named levels start at LevelTrace so ordinary Config{Debug: DebugConfig{Level: LevelTrace}} is expressible without falling through the default path.

const (
	// LevelUnset is the zero value. Config resolve maps it to LevelInfo.
	LevelUnset LogLevel = iota
	// LevelTrace is the most verbose journal level selectable via Config.
	LevelTrace
	// LevelDebug enables Debug journal lines (and Capture MirrorToDebug).
	LevelDebug
	// LevelInfo is the ordinary default (Debug journal suppressed).
	LevelInfo
	// LevelWarn is reserved for future warn-threshold filtering.
	LevelWarn
	// LevelError is reserved for future error-threshold filtering.
	LevelError
)

type LogRecord added in v0.2.4

type LogRecord struct {
	Time    time.Time
	Level   slog.Level
	Message string
	Attrs   []slog.Attr
	PC      uintptr
}

LogRecord is a complete internal log entry preserved from slog (or peer bridges). History and pane projectors read Time/Level/Message/Attrs without lossy remapping beyond the usual Field redaction path.

type MessageSnapshot added in v0.2.0

type MessageSnapshot struct {
	ID         string
	Text       string
	Visibility Visibility
}

MessageSnapshot is one logical user-facing message in the canonical model.

type NoopRedactor

type NoopRedactor struct{}

NoopRedactor leaves strings unchanged.

func (NoopRedactor) RedactString

func (NoopRedactor) RedactString(s string) string

RedactString implements Redactor.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures an Output.

func AlsoWrite

func AlsoWrite(w io.Writer) Option

AlsoWrite adds an additional human projection writer. On Finish, each writer receives the plain projection; failures on one do not skip the others (CON-009).

func Clock

func Clock(ts TimeSource) Option

Clock injects a time source facade.

func DataProjection

func DataProjection() Option

DataProjection selects data-command mode (UI/progress on diagnostic stream) — a self-documenting marker at the call site; the routing itself comes from pairing it with To(stderr)/Diagnostics(stderr) (configToOptions).

func DebugAddSource added in v0.3.0

func DebugAddSource() Option

DebugAddSource resolves each debug record's call site to a source=file.go:line field on human/pane/history rendering, matching slog.HandlerOptions.AddSource semantics. Off by default (release-gate round 6 finding 2): a bare pc=<uintptr> field never belongs on a human debug line; the raw PC still lives on LogRecord for machine consumers regardless of this setting.

func DebugHistory

func DebugHistory() Option

DebugHistory selects durable append-above-and-redraw presentation (v0.4 default).

func DebugLevel

func DebugLevel(level LogLevel) Option

DebugLevel sets the minimum debug emission level. Pass LevelTrace or LevelDebug to surface Debug journal lines.

func DebugPane

func DebugPane(opts ...DebugPaneOption) Option

DebugPane selects a rolling TTY debug viewport at the bottom of the live region.

func Diagnostics

func Diagnostics(w io.Writer) Option

Diagnostics sets the diagnostic writer for Debug history and Capture mirrors. When set and distinct from the primary writer (To), Debug lines are not also written to the human primary stream — use dual-stream for LaunchAgent / data-command layouts (human on stdout, diagnostics on stderr).

func DryRun added in v0.3.0

func DryRun() Option

DryRun declares this run a dry run: TaskHandle mutation verbs (Delete, Create, Update, Remove, Write, Push, Record, RecordName) render as [planned] rows with imperative verbs instead of [changed] rows with past-tense verbs. Set once via Config.DryRun in ordinary application code; this Option exists for the advanced NewWithOptions surface and tests.

func ExternalProjection

func ExternalProjection() Option

ExternalProjection selects snapshot-only host rendering.

func Glyphs added in v0.3.0

func Glyphs(p GlyphProfile) Option

Glyphs selects the glyph capability profile (default GlyphsAuto).

func MaxEntities

func MaxEntities(n int) Option

MaxEntities caps total items and tasks for one Output (0 uses default).

func MaxEvents

func MaxEvents(n int) Option

MaxEvents caps durable journal events; when exceeded, oldest non-critical events are dropped so critical terminal events are retained (CON-008).

func MaxFrameRate

func MaxFrameRate(framesPerSecond int) Option

MaxFrameRate caps interactive redraws per second.

func NoColor

func NoColor() Option

NoColor disables color.

func Plain

func Plain() Option

Plain forces final-report projection (no live spinner region). Semantic color is still emitted unless NoColor is set.

func Redact

func Redact(r Redactor) Option

Redact injects a redactor (Debug fields, Capture lines, problem detail paths).

func ResultStream added in v0.2.3

func ResultStream(w io.Writer) Option

ResultStream sets the domain-payload writer (see Output.ResultWriter). Presentation never writes here. FormatData defaults this to Config.Stdout.

func Stdin added in v0.3.0

func Stdin(r io.Reader) Option

Stdin injects the reader Confirm reads answers from (facade rule — no direct os.Stdin read in Confirm's logic). Default os.Stdin.

func Strict

func Strict() Option

Strict enables panic-on-misuse for tests.

func Terminal

func Terminal(driver TerminalDriver) Option

Terminal injects a terminal driver (interactive projection; v0.2).

func Title added in v0.2.7

func Title(subject string) Option

Title sets the conclusion subject for Config.Options's raw Option path.

func To

func To(w io.Writer) Option

To sets the primary human writer.

func VisibilityDelay

func VisibilityDelay(delay time.Duration) Option

VisibilityDelay sets how long live activity must persist before the first interactive paint (default 80ms). Zero paints immediately. Prevents Phase→fast Done spinner flash (H.2). Domain TimeSource is used for the threshold.

func Width

func Width(columns int) Option

Width sets the terminal width in columns.

type Output

type Output struct {
	// contains filtered or unexported fields
}

Output is the aggregate root for one command's presentation lifecycle.

func Default added in v0.3.0

func Default() *Output

Default returns the package-level default Output, lazily creating one with a zero Config the first time it's needed — package-level Task/Print* never panic even when the caller skipped Init.

func Init added in v0.3.0

func Init(configs ...Config) *Output

Init is the sole Output constructor. It builds an Output from cfg, installs it as the package-level default, and arms first paint — call once, in main, before any I/O:

func main() {
    evo.Init(evo.Config{Title: "repo-retire"})
    os.Exit(evo.Main(run))
}

evo.Init() (zero args) or evo.Init(evo.Config{}) (or evo.Init(evo.DefaultConfig())) all build an ordinary default instance — Init is variadic (I9) so the zero-config call needs no empty Config{} literal. Passing more than one Config uses only the first; there is one construction call, not a merge.

Config.Isolated returns an independent instance that skips both steps — it never touches package state (parallel tests, embedders holding their own *Output).

Config.Options is the advanced raw-Option escape hatch for tests and specialized embedding; when set, ordinary Config fields (besides Title, DryRun, and Subject) are ignored. Options installs as the package-level default and arms first paint exactly like every other Init call — Isolated is the one and only opt-out, orthogonal to Options (release-gate round 8 finding 1: a caller who set Options but not Isolated must still be able to reach the instance they configured via the package-level Task/Print facade, instead of those facades lazily building a second, bare Output that silently drops DryRun/Title/writer wiring).

func (*Output) AnyBlockedSoFar added in v0.3.0

func (o *Output) AnyBlockedSoFar() bool

AnyBlockedSoFar reports whether any Task is currently in the Blocked state — a live, mid-run check (C12: named "SoFar" to distinguish it from Conclusion.AnyBlocked, which reports the finished run's final verdict; the two answer different questions and previously shared one name).

func (*Output) AnyFailed

func (o *Output) AnyFailed() bool

AnyFailed reports whether any Task is currently Failed.

func (*Output) At added in v0.2.0

func (o *Output) At(visibility Visibility) *Printer

At returns a printer for the given visibility.

func (*Output) Cancel

func (o *Output) Cancel(reason string)

Cancel records output-level cancellation via a synthetic cancelled task.

func (*Output) Changes

func (o *Output) Changes(subject string, args ...any) *Changes

Changes starts a durable-effects section. subject is a printf format when args are present (fmt.Sprintf semantics) — see Tasks (C6).

func (*Output) Close

func (o *Output) Close() error

Close is idempotent cleanup; best-effort Finish when needed.

func (*Output) Conclusion

func (o *Output) Conclusion() Conclusion

Conclusion returns the computed conclusion after Finish.

func (*Output) Confirm added in v0.3.0

func (o *Output) Confirm(question string, opts ...ConfirmOption) bool

Confirm quiesces the live region, renders a durable "? <question> y/N" gate, and reads one answer line — owning the whole gate so no call site hand-rolls a y/N prompt that fights the spinner or misreports "no" as failure (evo-rec.md "confirm gate" default).

question is verbatim text, not a printf format (unlike Task/Group/Reason's C6 name argument) — build a dynamic question with fmt.Sprintf before calling Confirm.

Resolution:

  • AssumeYes(true): Done "assumed --yes", returns true, no prompt.
  • No TTY / NonInteractive / plain, without AssumeYes: never blocks on stdin — Blocked "blocked by policy" with a Next hint to pass --yes, returns false.
  • "y"/"yes" (case-insensitive): Done, returns true.
  • Anything else, including empty: Blocked "declined", returns false — exit 1 via Conclusion precedence, never Failed, never Cancelled.
  • Zero-byte EOF on stdin (stdin closed or redirected from /dev/null before any answer arrived): Blocked "no answer — stdin closed" with the same --yes Next hint, returns false — distinct wording from the no-TTY policy block above, since nothing decided to refuse; the stream simply gave no answer.
  • SIGINT/SIGTERM while waiting: the existing signal path (runInterruptible) cancels the gate — it renders Cancelled, not declined — and Confirm returns false.

func (*Output) Debug

func (o *Output) Debug(message string, fields ...Field)

Debug records a structured diagnostic (§4.6 / §21.3).

History mode (default): durable scrollback above the live region (or plain stream). Pane mode: record is journaled and shown in the rolling live pane; not durable scrollback unless a diagnostic tail is preserved at Finish.

When Diagnostics is configured and is a different writer than the primary stream, debug lines go to Diagnostics only (not the human Items/Tasks stream). Use Capture for child-process evidence instead of DebugWriter when you need Fail Detail.

func (*Output) DebugWriter

func (o *Output) DebugWriter() io.WriteCloser

DebugWriter returns a line-oriented writer that emits Debug lines on newline. Partial UTF-8 sequences are buffered; control bytes are sanitized.

Prefer Capture for external process stdout/stderr: DebugWriter is filtered by DebugLevel (default LevelInfo drops all lines) and is the wrong dialect for child-command evidence used in Fail Detail. Use DebugWriter only when you intentionally want DEBUG-level journal lines (and set DebugLevel(Debug)).

func (*Output) DeclareDryRun added in v0.3.0

func (o *Output) DeclareDryRun()

DeclareDryRun switches this run into dry-run mode after construction — a bounded late setter (I8): calling it once any durable row has already streamed is misuse (ErrDryRunDeclaredLate), since those earlier rows would not reflect the switch. There is no argv-sniffing helper; the caller decides (e.g. from a flag parsed after Init) and calls this explicitly, before any Task/Print/Confirm call. A no-op when the run is already dry-run.

func (*Output) Err

func (o *Output) Err() error

Err returns the first recorded misuse error, if any.

func (*Output) Events

func (o *Output) Events() []Event

Events returns a copy of durable events (v0.1 journal).

func (*Output) Evidence added in v0.3.0

func (o *Output) Evidence(opts ...EvidenceOption) *Evidence

Evidence returns a session-level retained/redacted writer with no owning Task. Prefer Task.Evidence so failure evidence attaches to an entity. Session-level Evidence is advanced; ordinary call sites should not use it.

func (*Output) Fail

func (o *Output) Fail(summary string, options ...ProblemOption)

Fail records an output-level failure.

func (*Output) Failf added in v0.3.0

func (o *Output) Failf(format string, args ...any)

Failf records an output-level failure with a formatted summary. fmt.Errorf semantics: a trailing ": %w"/", %w" splits the formatted text into the recorded summary and evidence line exactly like TaskHandle.Failf.

Failf stays void rather than returning an error like TaskHandle.Failf does (release-gate round 4 finding 5): every existing call site uses Failf as a bare statement (e.g. Output.Run's own runInterruptible), and errcheck flags a discarded error return with no lint-config exception on this repo — so matching TaskHandle.Failf's signature here would force every one of those call sites to add a needless `_ = ` just to stay lint-clean. There is also no per-call Next chain to attach an error return to here the way TaskHandle.Failf's *Failure does (Output.Next already covers the output-level case), so a returned error would carry less than TaskHandle.Failf's does anyway. Documented asymmetry, not an oversight.

func (*Output) Finish

func (o *Output) Finish() error

Finish validates, computes conclusion, emits final projections. Projection I/O runs outside the domain lock (§17.1).

func (*Output) Group added in v0.3.0

func (o *Output) Group(name string, args ...any) *GroupHandle

Group declares (or, for a repeated name, returns) a self-managing task group — the front door for a sequence of steps that must stop implying "still might run" once a member has already failed or been cancelled. A second evo.Group("python") call returns the same Group, mirroring Task's get-or-create identity. name is a printf format when args are present (fmt.Sprintf semantics); no args leaves name untouched.

func (*Output) Next

func (o *Output) Next(actions ...Action)

Next attaches output-level actions.

func (*Output) NextCommand

func (o *Output) NextCommand(executable string, args ...string)

NextCommand attaches an output-level command action.

func (*Output) Plan

func (o *Output) Plan(subject string, args ...any) *Plan

Plan starts a would-occur effects section. subject is a printf format when args are present (fmt.Sprintf semantics) — see Tasks (C6).

func (*Output) Print added in v0.2.0

func (o *Output) Print(args ...any)

Print formats like fmt.Sprint and enqueues human-facing text (line-buffered). Errors are recorded on the Output and returned by Finish/Main — not ignored mid-stream.

func (*Output) Printf added in v0.2.0

func (o *Output) Printf(format string, args ...any)

Printf formats like fmt.Sprintf and enqueues human-facing text (line-buffered).

func (*Output) Println added in v0.2.0

func (o *Output) Println(args ...any)

Println formats like fmt.Sprintln and enqueues a complete human-facing line.

func (*Output) ResultWriter added in v0.2.3

func (o *Output) ResultWriter() io.Writer

ResultWriter returns the domain-payload stream. Presentation never writes here.

In FormatData mode this is Config.Result if set, otherwise Config.Stdout — so machine JSON stays pure while Tasks render on stderr. When no result stream is configured, returns io.Discard.

out := evo.Init(evo.Config{Title: "build", Format: evo.FormatData})
// after work succeeds:
_ = json.NewEncoder(out.ResultWriter()).Encode(payload)

func (*Output) Run added in v0.3.0

func (o *Output) Run(run func(*Output) error) int

Run executes a CLI presentation lifecycle against this Output and returns the process exit code — the Isolated-instance counterpart of Main, for a caller holding its own *Output (evo.Init(evo.Config{Isolated: true})).

Typical entrypoint:

func main() {
    out := evo.Init(evo.Config{Title: "tool", Isolated: true})
    os.Exit(out.Run(run))
}

Lifecycle: arm first paint → run → (reconcile run error into model) → Finish → Close.

Exit codes:

  • nil Output → ExitFailed (2)
  • SIGINT/SIGTERM → Cancel on the active task (or the output) → ExitCancelled (130)
  • a second SIGINT/SIGTERM → ExitCancelled (130) returned immediately, without waiting for run to unwind, so the caller's os.Exit(out.Run(...)) exits now
  • Finish/Close bookkeeping misuse (a leftover unresolved task, a double-resolve, ...) is folded into the Conclusion before it renders, so the printed band and Conclusion.ExitCode already agree; a Blocked conclusion keeps ExitBlocked (1) regardless — the documented "Block → exit 1" contract wins over a leftover bookkeeping misuse
  • a genuine renderer/write failure (surfacing only after the band is already flushed) still escalates an otherwise-OK exit code to ExitFailed (2)
  • otherwise Conclusion.ExitCode after reconciling run errors into Fail

Config.FailedExitCode (when non-zero) overrides ExitFailed for a failed conclusion so CLIs that contract on exit 1 can set FailedExitCode: 1.

A non-nil application error is recorded as an output-level Fail before Finish so the human conclusion cannot show [ready] while the process fails.

func (*Output) Scope added in v0.2.3

func (o *Output) Scope(name string) *Scope

Scope returns a namespaced handle. It does not render a visible section.

func (*Output) SlogHandler

func (o *Output) SlogHandler() slog.Handler

SlogHandler returns a slog.Handler that journals every accepted record as structured diagnostics (history or pane), without mutating Output configuration.

Level policy is Config.Debug.Level (one conductor):

out := evo.Init(evo.Config{
    Debug: evo.DebugConfig{Level: evo.LevelDebug},
})
logger := slog.New(out.SlogHandler())

Application human prose uses Print/Printf/Println or Task outcomes — not slog. Infrastructure diagnostics use slog through this handler.

func (*Output) Snapshot

func (o *Output) Snapshot() Snapshot

Snapshot returns an immutable copy of current state.

func (*Output) Subject added in v0.3.0

func (o *Output) Subject(text string)

Subject prints one durable line immediately — the same one-shot semantics as Config.Subject, for a caller who doesn't know the subject text until after Init (e.g. resolved from a flag), but still before any other I/O (I3). A no-op on a nil Output or empty text.

func (*Output) Suspend

func (o *Output) Suspend(fn func() error) error

Suspend temporarily pauses interactive presentation for host-owned output. In v0.3 plain/non-interactive mode this is a no-op around fn. With a live surface it clears the live region, runs fn, then resumes.

func (*Output) Task

func (o *Output) Task(name string, args ...any) *TaskHandle

Task declares a single operation. Optional evo.ID sets a stable machine key. name is a printf format when args are present (fmt.Sprintf semantics) — evo.ID (or any other EntityOption) may be mixed into args in any position and still applies.

func (*Output) Tasks

func (o *Output) Tasks(name string, args ...any) *Tasks

Tasks declares a collection of independent child tasks. name is a printf format when args are present (fmt.Sprintf semantics) — one text spelling shared with Task/Group/Reason (C6); no args leaves name untouched.

func (*Output) Writer added in v0.2.0

func (o *Output) Writer() io.Writer

Writer returns an io.Writer that feeds this printer's line buffer (human stream).

type PlainOptions

type PlainOptions struct {
	Width   int
	NoColor bool
	// Verbose additionally emits per-reason name lists under a task's skip/keep
	// taxonomy line. Counts and the reason partition always render; Verbose
	// only adds the bounded (TruncateNames) name detail.
	Verbose bool
	// Glyphs selects the state-glyph vocabulary. Plain projection has no live
	// TTY to detect, so GlyphsAuto (the default) resolves to GlyphsUnicode —
	// callers rendering off a known non-UTF-8 destination pass GlyphsASCII.
	Glyphs GlyphProfile
}

PlainOptions configures pure plain projection (§25.4).

type Plan

type Plan struct {
	// contains filtered or unexported fields
}

Plan is a handle for effects that would occur but have not.

func (*Plan) Add

func (p *Plan) Add(quantity int, object string) *Plan

Add records a planned addition.

func (*Plan) Create

func (p *Plan) Create(object string) *Plan

Create records a planned create.

func (*Plan) Delete

func (p *Plan) Delete(quantity int, object string) *Plan

Delete records a planned deletion.

func (*Plan) Push added in v0.3.0

func (p *Plan) Push(quantity int, object string) *Plan

Push records a planned push of quantity of object. Part of the verb set unified across TaskHandle/Changes/Plan (C10) — Push was previously TaskHandle-only.

func (*Plan) Record

func (p *Plan) Record(verb string, quantity int, object string) *Plan

Record records a planned verb/quantity/object. A zero quantity records no row but still remembers verb as the section's intended verb — see Changes.Record for the empty-section rationale (evo-rec.md guess-driven default #1).

func (*Plan) RecordName added in v0.2.16

func (p *Plan) RecordName(verb, object string) *Plan

RecordName records a planned verb and one named object without a quantity. Quantity is for collapsed counts; RecordName is one named object.

func (*Plan) Remove

func (p *Plan) Remove(quantity int, object string) *Plan

Remove records a planned removal.

func (*Plan) Update

func (p *Plan) Update(quantity int, object string) *Plan

Update records a planned update.

func (*Plan) Write

func (p *Plan) Write(object string) *Plan

Write records a planned write.

type PlanSnapshot

type PlanSnapshot struct {
	ID      string
	Subject string
	Records []EffectRecord
	// IntendedVerb mirrors ChangesSnapshot.IntendedVerb for plan sections.
	IntendedVerb string
}

PlanSnapshot is an immutable plan section.

type Printer added in v0.2.0

type Printer struct {
	// contains filtered or unexported fields
}

Printer is a visibility-scoped view of Output for Print/Printf/Println.

func Verbose added in v0.2.0

func Verbose() *Printer

Verbose returns a Printer scoped to Verbose visibility on the default instance.

func (*Printer) Print added in v0.2.0

func (p *Printer) Print(args ...any)

Print implements Printer.

func (*Printer) Printf added in v0.2.0

func (p *Printer) Printf(format string, args ...any)

Printf implements Printer.

func (*Printer) Println added in v0.2.0

func (p *Printer) Println(args ...any)

Println implements Printer.

func (*Printer) Writer added in v0.2.0

func (p *Printer) Writer() io.Writer

Writer returns an io.Writer that feeds this printer's line buffer.

type Problem

type Problem struct {
	Code    string
	Subject string
	Summary string
	Detail  string
	// EvidenceTail is a raw evidence tail (typically a capture ring via
	// DetailTail) attached alongside an explicit Detail. When Detail is also
	// set, both render — Detail first, EvidenceTail as an additional evidence
	// line underneath — so an explicit Detail is never silently discarded by
	// an auto-attached or explicitly requested evidence tail (or vice versa).
	// When Detail is empty, EvidenceTail alone renders as the problem's detail
	// body (DetailTail's original, still-supported shape).
	EvidenceTail string
	Severity     string
	Count        int64
	Unit         string
	Location     *SourceLocation
	Evidence     []Attachment
	Actions      []Action
	Fields       []Field
	Cause        error
	Sensitive    bool
}

Problem is structured evidence explaining a negative item or task outcome.

type ProblemOption

type ProblemOption interface {
	// contains filtered or unexported methods
}

ProblemOption configures a problem constructed by Block/Warn/Fail helpers.

func Code

func Code(value string) ProblemOption

Code sets a stable problem code.

func Count

func Count(value int64, unit ...string) ProblemOption

Count sets a quantity and optional unit.

func Detail

func Detail(text string) ProblemOption

Detail sets user-visible detail text (strings only).

func Location

func Location(path string, line, column int) ProblemOption

Location sets a source location on a Problem (renamed from At — C5: a free-function At collided in name, though not in call syntax, with Output.At(visibility), confusing autocomplete and readers alike).

func Next

func Next(action Action) ProblemOption

Next attaches actions to a problem.

func NextCommand

func NextCommand(executable string, args ...string) ProblemOption

NextCommand attaches a recommended command action.

func On

func On(subject string) ProblemOption

On sets the problem subject.

type Progress

type Progress struct {
	Kind      ProgressKind
	Completed int64
	Total     int64
}

Progress is absolute measurement for a task.

type ProgressKind

type ProgressKind string

ProgressKind classifies task measurement.

const (
	Indeterminate ProgressKind = "indeterminate"
	Determinate   ProgressKind = "determinate"
	BytesKind     ProgressKind = "bytes"
)

type ReasonOption added in v0.3.0

type ReasonOption interface {
	// contains filtered or unexported methods
}

ReasonOption constrains how a Reason may be used; a violated constraint is recorded as misuse (Strict panics; production still counts the record).

func ForSkip added in v0.3.0

func ForSkip() ReasonOption

ForSkip restricts a reason to TaskHandle.Skipped — recording it via Kept is misuse.

func OnTask added in v0.3.0

func OnTask(taskName string) ReasonOption

OnTask restricts a reason to the named task — recording it from a different task is misuse.

type Redactor

type Redactor interface {
	// RedactString returns a display-safe form of s.
	RedactString(s string) string
}

Redactor redacts sensitive values before journal, Capture retention, and human rendering.

type Scope added in v0.2.3

type Scope struct {
	// contains filtered or unexported fields
}

Scope is a namespaced declaration handle for plugins and subsystems.

Contract (honest limits):

  • Qualifies evo.ID keys as "scope.key" for stable machine identity.

  • Exposes only Task and Tasks — operations that actually take the namespace.

  • Is NOT a security sandbox: plugins holding *Output bypass Scope entirely.

  • Session Capture, Writer, and SlogHandler stay on *Output (shared session).

    registry := out.Scope("registry") registry.Task("credentials", evo.ID("auth")).Done() // key → "registry.auth"

func (*Scope) Name added in v0.2.3

func (s *Scope) Name() string

Name returns the scope path segment.

func (*Scope) Task added in v0.2.3

func (s *Scope) Task(name string, args ...any) *TaskHandle

Task declares a task; optional evo.ID is prefixed with the scope name. name is a printf format when args are present (fmt.Sprintf semantics).

func (*Scope) Tasks added in v0.2.7

func (s *Scope) Tasks(name string) *Tasks

Tasks declares a task collection under this scope's naming (human name only; child tasks still take evo.ID with scope qualification via Scope.Task).

type Snapshot

type Snapshot struct {
	Version     uint64
	OutputID    string
	Subject     string
	Tasks       []TaskSnapshot
	Collections []TasksSnapshot
	Changes     []ChangesSnapshot
	Plans       []PlanSnapshot
	Messages    []MessageSnapshot
	// Lines is a derived compatibility projection of projected message texts
	// (and legacy debug history lines). Prefer Messages for structured consumers.
	Lines      []string
	Actions    []Action
	Conclusion *Conclusion
	Timestamp  time.Time
	// DryRun mirrors Config.DryRun: the run's mutation verbs are Plan-only
	// (never Changes). The plain/final projection uses this to open with an
	// unmissable marker line — no caller decides whether to announce it.
	DryRun bool
}

Snapshot is an immutable complete presentation state at a version.

type SourceLocation added in v0.3.0

type SourceLocation struct {
	Path   string
	Line   int
	Column int
}

SourceLocation is a path-based source position. Named SourceLocation (not Location) so the Location(...) ProblemOption constructor below can keep that name without colliding with its own return type.

type SystemClock

type SystemClock struct{}

SystemClock uses the real wall clock.

func (SystemClock) Now

func (SystemClock) Now() time.Time

Now returns the system time.

type TaskHandle added in v0.3.0

type TaskHandle struct {
	// contains filtered or unexported fields
}

Task is a handle for one operation with phases or progress.

func Task

func Task(name string, args ...any) *TaskHandle

Task declares (or, for a repeated name, returns) a Task on the default instance. Calling Task with the same name twice returns the same handle — the identity a caller doing evo.Task("branches") from two call sites expects. name is a printf format when args are present (fmt.Sprintf semantics); the get-or-create key is the formatted name.

func (*TaskHandle) Add added in v0.3.0

func (t *TaskHandle) Add(quantity int, object string)

Add records an addition of quantity of object; see Delete for the singular-object convention and the int (not int64) quantity. Part of the verb set unified across TaskHandle/Changes/Plan (C10) — Add was previously Plan-only.

func (*TaskHandle) Block added in v0.3.0

func (t *TaskHandle) Block(summary string, options ...ProblemOption)

Block resolves the task as blocked. This is a statement, not a fluent chain — Block returns nothing, so a bare `task.Block("summary")` is errcheck-clean. A nil *TaskHandle is safe and resolves nothing. Use Blockf to build and return a %w-wrapped error in one line.

func (*TaskHandle) Blockf added in v0.3.0

func (t *TaskHandle) Blockf(format string, args ...any) *Failure

Blockf resolves the task as blocked with a formatted summary and returns a *Failure exactly like Failf — see Failf for the fmt.Errorf %w, summary/evidence split, and Next/NextCommand remedy-attachment contract.

func (*TaskHandle) Bytes added in v0.3.0

func (t *TaskHandle) Bytes(completed, total int64) *TaskHandle

Bytes sets absolute byte progress (units and rate formatting).

func (*TaskHandle) Cancel added in v0.3.0

func (t *TaskHandle) Cancel(reason string) *TaskHandle

Cancel resolves the task as cancelled.

func (*TaskHandle) Create added in v0.3.0

func (t *TaskHandle) Create(object string)

Create records the creation of one named object.

func (*TaskHandle) Delete added in v0.3.0

func (t *TaskHandle) Delete(quantity int, object string)

Delete records a deletion of quantity of object. object is always singular ("branch", not "branches") — the ledger pluralizes it from quantity at render time (I4), so a call site never hand-composes its own singular/plural noun or calls evo.Pluralize itself. quantity is int (not int64) so the common caller shape — Delete(len(x), "...") — compiles without a manual conversion.

func (*TaskHandle) Done added in v0.3.0

func (t *TaskHandle) Done(args ...any) *TaskHandle

Done resolves the task successfully, with no summary (Done()), a literal one (Done("modules cached")), or a printf-formatted one (Done("%d packages", 18), fmt.Sprintf semantics) — one text spelling shared with Task/Group/Reason/Warn (C6), including the same "no args leaves text untouched" rule that keeps a literal "%" safe. A non-string first argument is misuse (ErrInvalidConfig): Done's format position is still meant to be a caller-written string, not an accidental value.

func (*TaskHandle) Each added in v0.3.0

func (t *TaskHandle) Each(items []string) iter.Seq[string]

Each iterates items, driving absolute Progress(i, len(items)) and a phase default of item before each item is yielded — the bar reads "items completed so far", not "items completed including the one still running", so it never shows full while the last item is in flight. Evo owns the counter: because progress is set from the loop index rather than a hand-maintained counter, re-running work for an item inside the loop body cannot double-count or move the bar backward — only advancing to the next item does. Normal completion of the loop seals progress at total/total.

The item-name phase is a courtesy default, not a declared phase: if the loop body calls Phase itself before the next paint, that call's own text is what streams (in plain mode) — the bare item name never forces its own redundant durable line first (beginner-10).

Breaking out of the loop early leaves progress at the count already reached; the task is not auto-resolved (call Done/Fail/etc. explicitly).

func (*TaskHandle) EachN added in v0.3.0

func (t *TaskHandle) EachN(n int) iter.Seq[int]

EachN iterates a count-only loop with no item names, driving absolute Progress(i, n) before each index is yielded — see Each for why the bar reads the count completed so far rather than including the in-flight item. Use Each when items have names worth showing as the phase. Normal completion of the loop seals progress at n/n.

func (*TaskHandle) Evidence added in v0.3.0

func (t *TaskHandle) Evidence(opts ...EvidenceOption) *Evidence

Evidence returns the retained/redacted writer bound to this Task, get-or-create: the first call (from Evidence or PhaseWriter) allocates the ring and every later call returns that same instance, so evidence recorded through either path lands together and survives for DetailTail after Fail.

func (*TaskHandle) Fail added in v0.3.0

func (t *TaskHandle) Fail(summary string, options ...ProblemOption)

Fail resolves the task as failed. This is a statement, not a fluent chain — Fail returns nothing, so a bare `task.Fail("summary")` is errcheck-clean. A nil *TaskHandle is safe and resolves nothing. Use Failf to build and return a %w-wrapped error in one line.

func (*TaskHandle) Failf added in v0.3.0

func (t *TaskHandle) Failf(format string, args ...any) *Failure

Failf resolves the task as failed with a formatted summary and returns a *Failure so a call site can `return` it directly: `return task.Failf("validate policy manifest: %w", err)`, and attach a remedy in the same statement: `.Next(evo.Label("..."))`. fmt.Errorf semantics: %w wraps its argument so errors.Is/As still reach it. See splitWrappedMessage for how a trailing ": %w"/", %w" splits the formatted text into the rendered summary and evidence line.

func (*TaskHandle) Kept added in v0.3.0

func (t *TaskHandle) Kept(reason TaxonomyReason, name string, errs ...error)

Kept accumulates a (reason, name) keep record on the task — same machinery as Skipped, second verb ("! kept N (...)").

func (*TaskHandle) Next added in v0.3.0

func (t *TaskHandle) Next(actions ...Action) *TaskHandle

Next attaches actions.

func (*TaskHandle) NextCommand added in v0.3.0

func (t *TaskHandle) NextCommand(executable string, args ...string) *TaskHandle

NextCommand attaches a command action. args names a foreign tool's own executable explicitly — the common case, since most remedies point at a different tool than the one running right now.

func (*TaskHandle) NextSelf added in v0.3.0

func (t *TaskHandle) NextSelf(args ...string) *TaskHandle

NextSelf attaches a command action that re-runs the caller's own binary with args — a self-referencing remedy ("rerun with --apply") that doesn't restate which binary to run (I6). Uses the same identity source as Confirm's PolicyFlag / I2's Failf fallback: Config.Title when set, else the binary's own basename. Use NextCommand instead when the remedy is a different (foreign) tool.

func (*TaskHandle) Phase added in v0.3.0

func (t *TaskHandle) Phase(text string, args ...any) *TaskHandle

Phase sets the active phase text and starts the task if pending. text is a printf format when args are present (fmt.Sprintf semantics) — one text spelling shared with Done/Task/Group/Reason/Skip (C6; release-gate round 6 finding 4: Confirm's question is the one true non-printf exception now).

func (*TaskHandle) PhaseWriter added in v0.3.0

func (t *TaskHandle) PhaseWriter() io.Writer

PhaseWriter returns a line-buffered io.Writer for narrating a talkative child process: each complete line (CR or LF terminated, trimmed, non-empty) becomes the task's live Phase text, and every byte is also retained in the task's Evidence ring (get-or-create, shared with Task.Evidence) so DetailTail has proof after Fail. Phase text passes through the same sanitize layer as Task.Phase, so hostile escape sequences never reach the display. Off a TTY, these mirrored lines update the live phase only — they never force their own durable row the way an explicit TaskHandle.Phase call does, since the Evidence ring (and its failure-path DetailTail) is already the child's one durable home (release-gate round 9 finding 4). Concurrent-safe.

cmd.Stdout = evo.Task("push").PhaseWriter()

func (*TaskHandle) Progress added in v0.3.0

func (t *TaskHandle) Progress(completed, total int) *TaskHandle

Progress sets absolute completed/total count progress. Counts use int (collection lengths, indices). For byte quantities use Bytes. Prefer absolute Progress over Advance so retries cannot double-count.

func (*TaskHandle) Push added in v0.3.0

func (t *TaskHandle) Push(quantity int, object string)

Push records a push of quantity of object; see Delete for the singular-object convention and the int (not int64) quantity.

func (*TaskHandle) Record added in v0.3.0

func (t *TaskHandle) Record(verb string, quantity int, object string)

Record records an arbitrary imperative verb/quantity/object mutation. Add/Delete/Create/Update/Remove/Write/Push are named shorthands for this.

func (*TaskHandle) RecordLabel added in v0.3.0

func (t *TaskHandle) RecordLabel(label string, quantity int, object string)

RecordLabel records quantity of object (singular; see Delete) under label, verbatim, into the task's Changes ledger. Unlike Record's mutation verbs, label is a classification result (e.g. "ready", "blocked") rather than an imperative action, so it is never conjugated to past tense, and it never moves under [planned] during DryRun — classifying/observing already happened whether or not other mutations on this run are a dry run.

func (*TaskHandle) RecordName added in v0.3.0

func (t *TaskHandle) RecordName(verb, object string)

RecordName records an arbitrary imperative verb and one named object without a quantity. Quantity is for collapsed counts; RecordName is one named object.

func (*TaskHandle) Remove added in v0.3.0

func (t *TaskHandle) Remove(quantity int, object string)

Remove records a removal of quantity of object; see Delete for the singular-object convention and the int (not int64) quantity.

func (*TaskHandle) Run added in v0.3.0

func (t *TaskHandle) Run(cmd *exec.Cmd) error

Run is the ordinary way to shell out from a Task: it executes cmd as this task's subprocess, wiring cmd.Stdout/cmd.Stderr through the same Evidence + PhaseWriter plumbing PhaseWriter uses directly. Each line becomes the task's live Phase, and every byte is retained (redacted, bounded) in the task's Evidence ring so DetailTail has proof after Fail — reach for Evidence directly only when the caller isn't running an *exec.Cmd. If cmd.Stdout/cmd.Stderr already point somewhere (a caller wiring its own log file, say), Run tees into it rather than replacing it.

If the task has no Phase yet, Run sets one from cmd's basename (filepath.Base(cmd.Path) or cmd.Args[0]) so a live view shows what's running before the child ever writes a line.

Run does not touch cmd.Stdin and does not Suspend — a subprocess that needs the terminal (a prompt, a pager) stays on the explicit Suspend path. A context baked into cmd via exec.CommandContext still governs cancellation exactly as it would for a bare cmd.Run(); Run adds no context handling of its own.

Run returns the subprocess error verbatim and never resolves the task — the caller chooses Done/Fail from the result:

cmd := exec.Command("go", "build", "./...")
if err := task.Run(cmd); err != nil {
    return task.Failf("build failed: %w", err)
}
task.Done()

func (*TaskHandle) Skip added in v0.3.0

func (t *TaskHandle) Skip(reason string, args ...any) *TaskHandle

Skip resolves the task as skipped. reason is a printf format when args are present (fmt.Sprintf semantics) — one text spelling shared with Done/Task/Group/Reason/Phase (C6; release-gate round 6 finding 4).

func (*TaskHandle) Skipped added in v0.3.0

func (t *TaskHandle) Skipped(reason TaxonomyReason, name string, errs ...error)

Skipped accumulates a (reason, name) skip record on the task, with an optional trailing errs for evidence of why. It returns nothing — accumulating a record is an act, not a value to chain — is usable before the task resolves, and does not itself resolve the task. The taxonomy line ("! skipped N (a reasonA, b reasonB)") is derived from every accumulated record at render time, so no caller can hand-build (and thereby miscount) the summary; the aggregation key is untouched by errs. Any errs render as one bounded evidence line under the count row (first cause + "(+N more)"), full list under Verbose.

func (*TaskHandle) Snapshot added in v0.3.0

func (t *TaskHandle) Snapshot() TaskSnapshot

Snapshot returns the task snapshot.

func (*TaskHandle) Step added in v0.3.0

func (t *TaskHandle) Step(completed, total int, name string) *TaskHandle

Step sets absolute progress and phase text together under one lock acquisition, so a concurrent worker can never observe one goroutine's count paired with another goroutine's phase name — the exact interleaving two separate Progress(...) + Phase(...) calls (two separate locks) allow.

func (*TaskHandle) Unchanged added in v0.3.0

func (t *TaskHandle) Unchanged(args ...any) *TaskHandle

Unchanged resolves the task successfully, explicitly marking "checked, nothing needed to change" — distinct from an ordinary Done's generic verdict (I7). A run made entirely of Unchanged tasks (no Changes/Plan records, nothing Failed/Blocked/Cancelled/Warning) concludes StateUnchanged instead of the StateReady an ordinary Done gets. Same no-args/literal/printf-formatted shape as Done (C6).

func (*TaskHandle) Update added in v0.3.0

func (t *TaskHandle) Update(quantity int, object string)

Update records an update of quantity of object; see Delete for the singular-object convention and the int (not int64) quantity.

func (*TaskHandle) Warn added in v0.3.0

func (t *TaskHandle) Warn(summary string, args ...any)

Warn resolves the task with a warning. This is a statement, not a fluent chain — Warn returns nothing, so a bare `task.Warn("summary")` is errcheck-clean, matching Fail/Block (beginner-9: doc.go's no-fluent promise). The message gets the same summary placement as Fail/Block (the ⚠ row itself carries it), and the same de-echo as Fail/Block drops the redundant problem row when there's no Detail beyond it (beginner-3). summary is a printf format when fmt args are present — one text spelling shared with Done/Task/Group/Reason (C6); evo.Detail(...) and other ProblemOptions may be mixed into args in any position and still apply.

func (*TaskHandle) Write added in v0.3.0

func (t *TaskHandle) Write(object string)

Write records the writing of one named object.

type TaskSnapshot

type TaskSnapshot struct {
	ID    string
	Key   string // optional stable machine key (evo.ID); empty when unset
	Name  string
	State EntityState
	Phase string
	// ActivityAt is the domain-clock time of the most recent Phase or Progress
	// call; the live renderer uses it to grow a heartbeat suffix once stale
	// (see phaseStaleAfter). Zero when the task has never had Phase/Progress set.
	ActivityAt time.Time
	Progress   Progress
	Summary    string
	Problems   []Problem
	Actions    []Action
	// Skipped/Kept are the disposition taxonomy accumulated by
	// TaskHandle.Skipped/Kept — the source the "! skipped N (...)" / "!  kept
	// N (...)" render lines derive counts and reason partitions from.
	Skipped     []TaxonomyRecord
	Kept        []TaxonomyRecord
	Collection  string
	Declaration int
	// contains filtered or unexported fields
}

TaskSnapshot is an immutable task view.

type Tasks

type Tasks struct {
	// contains filtered or unexported fields
}

Tasks is a handle for a collection of independent child tasks. State is always derived from children; no Done/Fail/Progress methods.

func (*Tasks) Snapshot

func (g *Tasks) Snapshot() TasksSnapshot

Snapshot returns the collection snapshot with derived state.

func (*Tasks) Summary

func (g *Tasks) Summary(text string, args ...any) *Tasks

Summary sets a success-oriented collection summary. text is a printf format when args are present (fmt.Sprintf semantics) — one text spelling shared with Task/Group/Reason (C6).

func (*Tasks) Task

func (g *Tasks) Task(name string, args ...any) *TaskHandle

Task declares a child task in declaration order. Optional evo.ID sets a stable machine key. name is a printf format when args are present (fmt.Sprintf semantics) — evo.ID (or any other EntityOption) may be mixed into args in any position and still applies.

type TasksSnapshot

type TasksSnapshot struct {
	ID          string
	Name        string
	State       EntityState
	Summary     string
	Tasks       []TaskSnapshot
	Declaration int
}

TasksSnapshot is an immutable collection view.

type TaxonomyReason added in v0.3.0

type TaxonomyReason struct {
	// contains filtered or unexported fields
}

TaxonomyReason names why a task skipped or kept an item. It is the opaque handle returned by evo.Reason — duplicate strings merge into one taxonomy bucket so a caller can construct one inline at every call site (evo.Reason("dirty")) without hand-tracking identity, or lift it to a package var once it repeats.

func Reason added in v0.3.0

func Reason(name string, args ...any) TaxonomyReason

Reason returns a get-or-create taxonomy Reason by name on the default instance registry — duplicate strings merge into one bucket, so an inline evo.Reason("protected") at every call site is always legal; lifting it to a package var (var reasonProtected = evo.Reason("protected")) is optional, not required for correctness. name is a printf format when args are present (fmt.Sprintf semantics) — one text spelling shared with Task/Group (C6); evo.ForSkip()/evo.OnTask(...) may be mixed into args in any position and still applies, exactly like Task's evo.ID.

func (TaxonomyReason) Name added in v0.3.0

func (r TaxonomyReason) Name() string

Name returns the reason's display label.

type TaxonomyRecord added in v0.3.0

type TaxonomyRecord struct {
	Reason string
	Name   string
	// Causes holds the sanitized text of any errs passed to Skipped/Kept for
	// this record — evidence for why the disposition happened, rendered as
	// one bounded └─ line under the count row (first cause + "(+N more)"),
	// full list under Verbose.
	Causes []string
}

TaxonomyRecord is one accumulated (reason, name) disposition entry — recorded by TaskHandle.Skipped or TaskHandle.Kept, never assembled by hand.

type TerminalDriver

type TerminalDriver interface {
	ID() string
}

TerminalDriver is the exclusive owner of terminal control sequences. Interactive implementation arrives in v0.2; the interface is defined early so options and tests compile.

type TimeSource

type TimeSource interface {
	Now() time.Time
}

TimeSource provides the current time for deterministic tests. Option constructor is Clock(TimeSource) to match the public API examples.

type Verbosity added in v0.2.0

type Verbosity int

Verbosity selects which message visibilities are projected to the human stream. Zero is normal (non-verbose) human detail.

const (
	// VerbosityNormal projects Normal-visibility messages only.
	VerbosityNormal Verbosity = iota
	// VerbosityVerbose also projects Verbose-visibility messages. It also
	// expands each TaskHandle.Skipped/Kept taxonomy row from its default
	// aggregated "! skipped N (reason1, reason2)" count into one named line
	// per reason ("reason: name1, name2, ..."). The names themselves are
	// never lost at VerbosityNormal — they are always present on the Go
	// TaskSnapshot.Skipped/Kept fields (returned by Output.Snapshot and
	// TaskHandle.Snapshot); VerbosityVerbose only changes whether the plain
	// human render surfaces them. The wire JSONDocument (JSONTask) does not
	// currently carry Skipped/Kept at all — read the Go snapshot directly to
	// get the names programmatically.
	VerbosityVerbose
)

type Visibility added in v0.2.0

type Visibility uint8

Visibility selects whether a message is ordinary or verbose user detail. Zero is VisibilityNormal.

const (
	// VisibilityNormal messages always project at VerbosityNormal (C11:
	// prefixed consistently with VisibilityVerbose — the two enum members
	// previously disagreed on their own naming convention).
	VisibilityNormal Visibility = iota
	// VisibilityVerbose messages project only when Config.Verbosity is VerbosityVerbose.
	VisibilityVerbose
)

Directories

Path Synopsis
agent
catalog
Package catalog is the task-oriented guidance catalog for agent assistance.
Package catalog is the task-oriented guidance catalog for agent assistance.
harness
Package harness evaluates agent-assistance scenarios (§30.9, MCP-022/049).
Package harness evaluates agent-assistance scenarios (§30.9, MCP-022/049).
preview
Package preview generates multi-profile plain previews from snapshots.
Package preview generates multi-profile plain previews from snapshots.
review
Package review provides deterministic static review of Evident Output usage.
Package review provides deterministic static review of Evident Output usage.
rules
Package rules is the stable review-rule registry (Appendix C namespaces).
Package rules is the stable review-rule registry (Appendix C namespaces).
cmd
evident-output command
Command evident-output provides review/preview/explain CLI parity with MCP tools.
Command evident-output provides review/preview/explain CLI parity with MCP tools.
evident-output-mcp command
Command evident-output-mcp is the stdio MCP server.
Command evident-output-mcp is the stdio MCP server.
examples
data-command command
Command data-command: domain JSON on stdout, human presentation on stderr.
Command data-command: domain JSON on stdout, human presentation on stderr.
debug-history command
Command debug-history demos durable debug scrollback via slog.
Command debug-history demos durable debug scrollback via slog.
debug-pane command
Command debug-pane demos rolling slog-text viewport.
Command debug-pane demos rolling slog-text viewport.
doctor command
Command doctor is an environment/health check CLI.
Command doctor is an environment/health check CLI.
install-pipeline command
Command install-pipeline demos Tasks, Progress, Capture, and Main.
Command install-pipeline demos Tasks, Progress, Capture, and Main.
internal/demo
Package demo is reserved for advanced example utilities.
Package demo is reserved for advanced example utilities.
live-progress command
Command live-progress is the ordinary multi-progress demo (user API only).
Command live-progress is the ordinary multi-progress demo (user API only).
migrate command
Command migrate demonstrates the advanced Plan/Changes instance API — the primitives Task's mutation verbs (Delete/Create/Update/…) are built on.
Command migrate demonstrates the advanced Plan/Changes instance API — the primitives Task's mutation verbs (Delete/Create/Update/…) are built on.
print command
Command print is the first adoption rung: Print/Printf/Println like fmt.
Command print is the first adoption rung: Print/Printf/Println like fmt.
repo-status command
Command repo-status is a realistic "is this repo safe to retire?" check.
Command repo-status is a realistic "is this repo safe to retire?" check.
scope-plugin command
Command scope-plugin demos evo.Scope + evo.ID for plugin-owned presentation.
Command scope-plugin demos evo.Scope + evo.ID for plugin-owned presentation.
terminal-driver command
Command terminal-driver is an advanced teaching surface: custom TerminalDriver, frame-by-frame logging, and explicit renderer knobs.
Command terminal-driver is an advanced teaching surface: custom TerminalDriver, frame-by-frame logging, and explicit renderer knobs.
verbose command
Command verbose shows Normal vs Verbose message visibility.
Command verbose shows Normal vs Verbose message visibility.
internal
sanitize
Package sanitize neutralizes untrusted text for terminal-safe display.
Package sanitize neutralizes untrusted text for terminal-safe display.
width
Package width computes terminal cell widths for display text.
Package width computes terminal cell widths for display text.
scripts
sync-release-pins command
Command sync-release-pins rewrites install pins on portable surfaces to match evo.PublishedRelease.
Command sync-release-pins rewrites install pins on portable surfaces to match evo.PublishedRelease.
traceability-check command
Command traceability-check verifies every expected §31 ID is present.
Command traceability-check verifies every expected §31 ID is present.
Package terminal provides production terminal drivers for Evident Output.
Package terminal provides production terminal drivers for Evident Output.
Package testkit provides deterministic clocks, screens, and assertions for evo tests.
Package testkit provides deterministic clocks, screens, and assertions for evo tests.

Jump to

Keyboard shortcuts

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