Documentation
¶
Overview ¶
Package cliutil holds the verb-support kernel for aiwf's Cobra dispatchers: exit-code constants and the typed error shuttle, the post-verb apply path, identity resolution, flag re-ordering, repo locking, tree-loading with trunk-collision stamping, platform gating, and provenance gate-and-decorate plumbing.
Every cmd/aiwf verb file calls into this package; nothing in internal/* depends on it (it lives under internal/cli/ so it stays invisible to external consumers per Go's internal-package rule).
Index ¶
- Constants
- func AcquireRepoLock(rootDir, verbDisplay string) (release func(), rc int)
- func AllKindNames() []string
- func AssertSupportedOS(goos string) error
- func CompleteEntityIDArg(filter entity.Kind, position int) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)
- func CompleteEntityIDFlag(filter entity.Kind) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)
- func CompleteEntityIDs(filter entity.Kind) ([]string, cobra.ShellCompDirective)
- func ConfiguredTitleMaxLength(rootDir string) int
- func DecorateAndFinish(ctx context.Context, root, label string, t *tree.Tree, result *verb.Result, ...) int
- func FinishVerb(ctx context.Context, root, label string, result *verb.Result, err error) int
- func HasCommits(ctx context.Context, root string) bool
- func IsTerminalPromote(k entity.Kind, newStatus string) bool
- func JoinKinds(ks []entity.Kind) string
- func LoadContractsBlock(rootDir string) (*aiwfyaml.Contracts, error)
- func LoadContractsDoc(rootDir string) (*aiwfyaml.Doc, *aiwfyaml.Contracts, error)
- func LoadEntityScopes(ctx context.Context, root, id string) ([]*scope.Scope, error)
- func LoadTreeWithTrunk(ctx context.Context, rootDir string) (*tree.Tree, []tree.LoadError, error)
- func ParseKind(s string) (entity.Kind, bool)
- func ParseTestsFlag(raw, verbLabel string) (*gitops.TestMetrics, error)
- func ReadBodyFile(path string) ([]byte, error)
- func RegisterFormatCompletion(cmd *cobra.Command)
- func ReorderFlagsFirst(args, knownFlags, knownBoolFlags []string) []string
- func ResolveActor(explicit, root string) (string, error)
- func ResolveActorWithSource(explicit, root string) (actor, source string, err error)
- func ResolveCurrentEntityID(ctx context.Context, root, id string) (string, error)
- func ResolveRoot(explicit string) (string, error)
- func SplitCommaList(s string) []string
- func StatusesForID(id string) []string
- func WrapExitCode(code int) error
- type ExitError
- type ProvenanceContext
Constants ¶
const ( ActorSourceFlag = "--actor flag" ActorSourceGitConfig = "git config user.email" )
Actor source labels surfaced by `aiwf whoami` and used as the second return value of ResolveActorWithSource. Stable strings; do not change without updating tests and documentation.
The pre-I2.5 `aiwf.yaml` source is gone — identity is now runtime- derived per `provenance-model.md`, with `--actor` overriding the git-config default. The aiwf.yaml `actor:` key (if still present) is ignored for resolution; `aiwf doctor` surfaces a deprecation note.
const ( ExitOK = 0 // no error-severity findings (warnings allowed) ExitFindings = 1 // at least one error-severity finding ExitUsage = 2 ExitInternal = 3 )
Exit codes per CLAUDE.md § Go conventions § CLI conventions.
Variables ¶
This section is empty.
Functions ¶
func AcquireRepoLock ¶
AcquireRepoLock takes the per-repo mutation lock and returns a release function plus a zero exit code on success. On failure it prints an explanation to stderr and returns a non-zero exit code the caller must propagate (release will be nil).
Usage in every mutating verb:
release, rc := cliutil.AcquireRepoLock(rootDir, "aiwf add")
if release == nil {
return rc
}
defer release()
Read-only verbs (check, history, status, render, doctor, whoami) must NOT call this — they can run concurrently with mutations.
func AllKindNames ¶
func AllKindNames() []string
AllKindNames returns the entity-kind names as strings, in the canonical iteration order from entity.AllKinds(). Used by the `aiwf add` and `aiwf schema` / `aiwf template` completion functions.
func AssertSupportedOS ¶
AssertSupportedOS gates aiwf to platforms whose POSIX assumptions the engine relies on. The PoC is Unix-only by design (per docs/pocv3/design/design-decisions.md): the pre-push hook is a /bin/sh script, contract validators shell out via POSIX semantics, and the repo lock uses flock(2). On Windows the engine would fail at the first such call with an opaque error; a clear up-front refusal is kinder to the user than a confusing failure deeper in the stack.
Pass runtime.GOOS in main; tests pass canned values to exercise every branch without depending on the test runner's host OS.
func CompleteEntityIDArg ¶
func CompleteEntityIDArg(filter entity.Kind, position int) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)
CompleteEntityIDArg is the standard Cobra positional-arg completion adapter over CompleteEntityIDs. Callers assign it as a command's ValidArgsFunction. Unlike the flag adapter, this version respects the args slice — if the positional in question isn't the first one, it returns no suggestions (so e.g. `aiwf promote E-01 <TAB>` doesn't re-suggest entity ids when the second positional is the new-status).
func CompleteEntityIDFlag ¶
func CompleteEntityIDFlag(filter entity.Kind) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)
CompleteEntityIDFlag is the standard Cobra flag-completion adapter over CompleteEntityIDs. Callers wire it via `cmd.RegisterFlagCompletionFunc(name, cliutil.CompleteEntityIDFlag(kind))` where kind is either "" for all kinds or a specific entity.Kind.
func CompleteEntityIDs ¶
func CompleteEntityIDs(filter entity.Kind) ([]string, cobra.ShellCompDirective)
CompleteEntityIDs returns the live ids in the consumer repo's planning tree, optionally filtered to a single kind. Designed for use as a Cobra ValidArgsFunction or RegisterFlagCompletionFunc body: failures (no aiwf.yaml, malformed tree, unreadable disk) collapse to an empty list rather than spamming the user's shell with errors, satisfying M-054 AC-2's graceful-no-op rule.
func ConfiguredTitleMaxLength ¶
ConfiguredTitleMaxLength returns the consumer's `entities.title_max_length` from aiwf.yaml, or the kernel default when absent (G-0102). Tolerant of a missing aiwf.yaml — the kernel default applies in that case too, so the verb dispatchers in cmd/aiwf can call this unconditionally without a precondition check.
func DecorateAndFinish ¶
func DecorateAndFinish( ctx context.Context, root, label string, t *tree.Tree, result *verb.Result, vErr error, pctx ProvenanceContext, ) int
DecorateAndFinish wraps the verb's post-execution path: when the verb produced a Plan, it runs gateAndDecorate (which enforces the I2.5 allow-rule and stamps provenance trailers), then hands off to FinishVerb to apply the plan and report the outcome.
On allow-rule denial, the plan is abandoned (verb output is validate-then-write, so no disk state has been mutated yet) and the dispatcher exits with the findings code so the user sees the refusal as a clean error.
func FinishVerb ¶
FinishVerb is the post-verb handler shared by every mutating subcommand: it surfaces a Go error as a usage error, renders any findings, applies the plan when present, and prints a one-line summary on success. NoOp results bypass the apply path entirely and print NoOpMessage on stdout.
func HasCommits ¶
HasCommits reports whether root's HEAD points at a real commit. `git log` on an empty repo errors with "your current branch X does not have any commits yet"; this guard converts that into "no events".
func IsTerminalPromote ¶
IsTerminalPromote reports whether `(kind, newStatus)` is a state transition into a terminal status — the trigger for the scope-end side effect. Mirrors entity.AllowedTransitions semantics: a status with no outgoing edges is terminal.
Returns false for unknown kinds or unknown statuses (cautious; the trigger should fire on real terminal moves only).
func JoinKinds ¶
JoinKinds renders a slice of entity.Kind as a comma-separated string (e.g. for "unknown kind X (known: epic, milestone, …)" error messages). Stable ordering: iterates the input slice as-given; callers that need canonical order pass entity.AllKinds() (which is canonical).
func LoadContractsBlock ¶
LoadContractsBlock reads aiwf.yaml from rootDir and returns the contracts: block (nil if absent or if the file itself is absent). A malformed contracts: block is an internal error — the caller (any verb that needs trustworthy bindings) can't proceed.
func LoadContractsDoc ¶
LoadContractsDoc reads aiwf.yaml and returns both the editable Doc and the parsed contracts block. Used by mutating verbs that need to splice the block back into the source.
func LoadEntityScopes ¶
LoadEntityScopes returns every scope ever opened on entity id, in open-order (oldest first), with each scope's current state derived from the entity's commit history. Empty / no-commit repos return (nil, nil).
The walker scans every commit whose `aiwf-entity:` trailer matches id (the same filter `aiwf history` uses) once, applying transitions in commit order:
- aiwf-verb=authorize, aiwf-scope=opened → append a new scope starting in state active.
- aiwf-verb=authorize, aiwf-scope=paused → flip the most-recently- opened still-active scope to paused.
- aiwf-verb=authorize, aiwf-scope=resumed → flip the most-recently- paused scope to active.
- aiwf-scope-ends: <auth-sha> on any commit → mark the matching scope ended (terminal). Auto-end fires when a terminal-promote of the scope-entity carries this trailer (added by step 6); for the initial step 5 cut, hand-crafted fixtures use the same path.
The "most recently X" rule mirrors the verb's pause/resume picker: scopes are appended in chronological open-order, so a backwards walk finds the latest match. Multiple parallel scopes on the same entity are supported; transitions land on the freshest matching state.
func LoadTreeWithTrunk ¶
LoadTreeWithTrunk loads the consumer repo's entity tree and stamps the configured trunk ref's ids onto Tree.TrunkIDs, so the allocator (entity.AllocateID) and the cross-tree ids-unique check both see trunk in their view.
Behavior matches package trunk: when the repo has no remotes the trunk read is silently skipped; when remotes exist but the configured trunk ref does not resolve the call returns an error and the operator sees a clear message.
A missing aiwf.yaml is not fatal — the trunk read uses the default trunk ref in that case.
func ParseKind ¶
ParseKind parses a CLI kind argument (lowercase string) into the entity.Kind constant. Returns the canonical Kind and true on match; the empty Kind and false when no kind in entity.AllKinds() matches s.
func ParseTestsFlag ¶
func ParseTestsFlag(raw, verbLabel string) (*gitops.TestMetrics, error)
ParseTestsFlag parses the --tests flag value (e.g. "pass=12 fail=0 skip=0") into a *gitops.TestMetrics. Empty input returns (nil, nil) (the flag was unset — not an error). Malformed input writes a one-line error to stderr (prefixed with verbLabel) and returns the parse error so the dispatcher exits with cliutil.ExitUsage.
The "metrics parsed to zero" defensive branch returns (nil, nil) — gitops.ParseStrictTestMetrics returns the zero TestMetrics for empty input, but here the trimmed input was non-empty so this shouldn't fire. If it does, treat the flag as not set to avoid emitting a meaningless trailer.
func ReadBodyFile ¶
ReadBodyFile loads body content for `aiwf add --body-file`. A path of "-" reads stdin (so callers can pipe body text without a temp file). Any other value is read as a regular file. Returns the raw bytes; the verb-side resolveAddBody is the rule-checking layer (it refuses content that begins with a frontmatter delimiter so the create commit can't accidentally produce a double-frontmatter file).
func RegisterFormatCompletion ¶
RegisterFormatCompletion wires `--format=` shell completion to the closed set {text, json}. Called by every read-only verb that accepts --format so the shell-completion experience is uniform across the surface (E-14's auto-completion-friendliness rule).
func ReorderFlagsFirst ¶
ReorderFlagsFirst hoists known flags (and their values) to the front of args so Go's stdlib `flag` package — which stops parsing at the first non-flag token — accepts the natural CLI shape:
aiwf cancel M-001 --reason "..." (flag after positional)
Without this, the user is forced to write:
aiwf cancel --reason "..." M-001 (flags first)
which is technically correct but goes against everyone's habits.
knownFlags lists value-taking flags (`--name value`); knownBoolFlags lists boolean flags that do NOT consume a following token. A bool flag wrapped in `--name=value` form is recognized either way. When the same name appears in both lists, the value-taking interpretation wins (defensive — callers should not duplicate).
The function is conservative: a token is treated as a flag only when it starts with `--` or `-` AND its name is in one of the known sets. Unknown flags fall through to the original position so flag.Parse can produce its usual error.
func ResolveActor ¶
ResolveActor picks the actor string for a verb's commit trailer. Precedence: explicit `--actor` > git config user.email derivation. Returns an error when neither yields a valid value or when the explicit value is malformed.
The root parameter is unused but kept for call-site compatibility; future per-repo identity policy (if it ever lands) would consult it.
func ResolveActorWithSource ¶
ResolveActorWithSource is ResolveActor plus the human-readable label of which source produced the value. Used by `aiwf whoami` to explain the precedence outcome to the user.
func ResolveCurrentEntityID ¶
ResolveCurrentEntityID walks the aiwf-prior-entity chain forward from id to its current id. When id has never been reallocated, returns id unchanged. When the entity was renumbered, follows the rename chain — each `aiwf reallocate` commit carries aiwf-prior-entity: <old> and aiwf-entity: <new> — until no further commit names the current id as `prior-entity`.
Used by the I2.5 allow-rule's scope-entity resolution: an authorize commit's aiwf-entity trailer is the id at the time of authorization. After a reallocate, the historical commit stays byte-identical (its SHA remains valid as aiwf-authorized-by:), but the live entity now lives under a new id. Forward-walking the prior-entity chain produces the current id so tree.Reaches can run against the live tree.
Cycle guard: each id may appear at most once. A cycle (which would indicate corrupted history) returns the chain's current position rather than looping.
func ResolveRoot ¶
ResolveRoot picks the consumer repo root. If explicit is non-empty, it is used as-is (resolved to absolute). Otherwise, walks up from cwd looking for aiwf.yaml; if found, uses its parent. If not found, falls back to cwd (lenient pre-init behavior).
This is the canonical exported home for root-dir resolution across the cmd/aiwf surface. Every verb that takes a `--root` flag funnels through ResolveRoot so the flag-empty + autodiscover behavior stays uniform.
func SplitCommaList ¶
SplitCommaList parses comma-separated CLI values into a clean slice (trimmed, empty entries dropped). Shared between --relates-to, --linked-adr, --depends-on, and similar multi-value flags.
func StatusesForID ¶
StatusesForID returns the closed set of statuses that an entity's kind allows, derived from the id's prefix without loading the repo's tree. Used as the static-completion source for `aiwf promote <id> <new-status>`. Returns nil for ids whose kind isn't recognized (composite ids, malformed input) — the completion source then falls back to file completion at the shell level.
func WrapExitCode ¶
WrapExitCode lifts a verb's int return code into the error channel Cobra's RunE expects. ExitOK collapses to nil (success); anything else becomes an *ExitError that the run() loop unwraps. Centralizing the translation keeps every RunE one-liner-shaped.
Types ¶
type ExitError ¶
type ExitError struct {
Code int
}
ExitError carries a verb-handler return code through Cobra's Execute boundary. The CLI's run() loop unwraps it so the wrapped code becomes the process exit status. Without this typed shuttle, Cobra would collapse the 0/1/2/3 contract to "0 or non-zero" because it only knows about its own usage-error return path.
type ProvenanceContext ¶
type ProvenanceContext struct {
Actor string
Principal string
VerbKind verb.VerbKind
TargetID string
CreationRefs []string
MoveSource string
IsTerminalPromote bool
}
ProvenanceContext carries the inputs the cmd dispatcher feeds into the I2.5 allow-rule and trailer-decoration step. Built once per verb invocation.
Actor is the operator (the value `aiwf-actor:` will carry). Principal is the human who authorized the act, set via the `--principal` flag. For human/... actors Principal must be empty (the human is acting directly); for non-human actors Principal is required and gates the agent through Allow.
VerbKind discriminates the act for reachability (see verb.VerbKind). TargetID, CreationRefs, MoveSource feed the allow-rule's reachability check.
IsTerminalPromote is true when the verb is a `promote` whose target state is terminal for the entity's kind. Triggers the scope-end side effect — writing aiwf-scope-ends for every active scope on the target entity.