spells

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: GPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package spells is everything a spell is: the language/runtime adapters magus builds, tests, lints and formats projects with, and the types describing them. The directory holds both halves - the built-in spell sources (.buzz) that ship embedded in the binary, and the Go types the engine speaks about any spell.

There used to be three packages spelling "spell" and three representations of one: a live driver in types, a decoded Descriptor in internal/spellruntime, and a describe-time view in types again, the last two carrying the same facts reached from opposite directions. Collapsing them here is what lets the names drop their prefix - the package carries the noun, so it is spells.Op and spells.Driver rather than types.SpellOp and types.SpellDriver.

The dependency runs one way: spells imports nothing from types, and types imports spells. That is deliberate and load-bearing. Descriptor.Ops is a map[string]Op, so if the describe-time views had stayed in types while Op lived here, the two packages would import each other and neither would compile.

Index

Constants

View Source
const (
	OpKindCommand = "command"
	OpKindService = "service"
)

Op kinds. A kind lives on the op, not the spell: one spell freely mixes command ops and service ops under one name. The kind is inferred from what the op handler returns - a Command (OpKindCommand) or a Service (OpKindService) - so authoring stays a single mgs_listTargets. Both are declarative data differing only in lifecycle (run-to-completion vs long-running), not the imperative handler split magus removed. An empty Op.Kind means OpKindCommand.

View Source
const (
	// FindReviewContract reports the review open for a branch, or nothing when there is none.
	// A branch with no pull request is the ordinary state, not an error.
	//
	// A lookup, never a creation: no name here opens a review, and `find` says so to the next
	// spell author, who would otherwise read `open` as the verb and implement one.
	//
	// It may also report `state` - "open", "merged" or "closed". Answering it is optional and an
	// empty answer reads as open. magus asks rather than working it out from git because a squash
	// merge leaves nothing git can follow: the branch becomes one new commit that is neither an
	// ancestor of the base nor patch-equivalent to it, so a workspace that squash-merges would
	// never see its own merges.
	FindReviewContract = "find_review"

	// ReviewThreadsContract lists the comment threads already on that review, so they can be
	// read and replied to without leaving. It returns a list of records; nothing at all means
	// no threads, and is not an error.
	ReviewThreadsContract = "review_threads"

	// PublishReviewContract sends a batch of drafts as one review.
	PublishReviewContract = "publish_review"

	// ReplyReviewContract answers one existing thread. It returns a BOOL: true when the host
	// took the reply. Stated because a spell that answered with anything else is read as a
	// refusal, and a refusal reported after the reply already posted is how the same sentence
	// reaches a colleague twice.
	ReplyReviewContract = "reply_review"
)
  • Why publishing is a batch
  • Why reading is separate from publishing

The review contract: the reserved function names a spell exports to connect a workspace to wherever its changes are discussed.

A fourth CONTRACT beside the cache, CI and secret ones, detected the same way - by reserved function name on a spell a magusfile selected. It is not a new subsystem.

Whether it rides on the same spell as the other three is up to the vendor. For GitHub it does not: spells/github/actions is inert outside a CI runner by design, and a review happens on a laptop, so the review ops live in spells/github/review and that workspace imports two spells. A vendor whose contracts share a runtime would carry all four in one.

magus knows nothing about GitHub. It calls these four names, and the spell talks to whatever host it was written for over ordinary HTTP: `spells/github/actions` already does exactly that for the Actions cache, with `import "http"` and a bearer token. No vendor CLI has to be installed for a review to work, which is the difference between a token and a binary.

Why publishing is a batch

PublishReviewContract takes every draft at once rather than one comment per call. Self-review is a pass: you read, you accumulate remarks, and only then do you decide the whole thing is worth sending. A per-comment call would publish the first thought before the fifth one had changed your mind about it, and it would turn one outward-facing act - which is what needs confirming - into a series of small ones nobody confirms individually.

Why reading is separate from publishing

ReviewThreadsContract exists so a reader never leaves for the browser to find out what a colleague said. It is the one contract function that makes magus depend on a host being reachable, so it is deliberately its own name: a workspace with no credential, or no pull request open, still publishes nothing and reads nothing, and every other surface works exactly as before.

A spell may implement a SUBSET. Each op is looked up by name at the moment it is called, so a missing one is a capability that provider lacks rather than a broken spell, with nothing to declare either way.

View Source
const ListProjectsContract = "list_projects"

ListProjectsContract is the reserved contract-function name a workspace-provider spell exports. magus invokes it by name once per workspace load and folds the ProvidedProject values it returns into the workspace, so a repo whose projects are owned by another tool (nx, gradle, pnpm, cargo) needs no magusfile per project.

It is a CONTRACT FUNCTION, not an op, for the same reason the remote cache provider's get_artifact/put_artifact are: the work happens in the VM (it shells out to the foreign tool and shapes the answer) and it returns data rather than a Command for magus to fork. The invoker reaches an exported function of this name when no op matches, which is what makes one name enough. Hence Contract, not Op: Op is this package's type for the other thing.

It is deliberately NOT mgs_-prefixed. An mgs_ function takes no arguments and must be pure, because magus calls it before it has selected a target; a provider is neither. Its signature matches every other contract function, and the input callback yields {root}, the absolute workspace root:

fun list_projects(target: Target, cb: fun(any)) > [Project]
View Source
const ModulePrefix = "magus/spell/"

ModulePrefix is the import namespace every spell is reachable under.

Variables

This section is empty.

Functions

func ExtractVersion

func ExtractVersion(output string) (string, bool)

ExtractVersion pulls the first semver-shaped token out of a probe's output and returns it in canonical `vX.Y.Z` form. ok is false when the output carries nothing version-shaped, which is the caller's signal to fall back rather than an error: a tool is allowed to print something magus cannot parse, and refusing to run because of it would be a worse failure than a coarse cache key.

The `v` prefix is not decoration. golang.org/x/mod/semver - whose Major/MajorMinor ARE the narrowing this file needs - requires it and returns "" without it, so emitting canonical form here is what lets those functions be used directly instead of wrapped.

func ModulePath

func ModulePath(name string) string

ModulePath is the literal a magusfile writes to bind this spell's handle: ModulePath("go") is "magus/spell/go", for `import "magus/spell/go"`.

Reported as a STRING on the spell descriptor record rather than resolved to a handle. A handle can only come from a literal import, because internal/describe reads spell imports statically to build the target graph - so a dynamically resolved spell would drop the target-uses-spell edge and under-report the graph without failing. Handing back the path keeps discovery dynamic and the import static: you look the spell up, then write the import yourself.

func ValidBound

func ValidBound(s string) bool

ValidBound reports whether s is a bound VersionBounds.Check can compare.

Exported so a declaration is validated with the SAME parser that later compares it. Validating with a second, looser one accepts bounds Check then cannot read, and every Check against that tool degrades to VerdictUnknown - a window that silently constrains nothing, which is the failure a declared bound exists to prevent.

func ValidatePatch

func ValidatePatch(ops []PatchOp) error

ValidatePatch checks a charm's ops are well-formed: a known op name, a non-root single-rooted JSON Pointer path, and a 'from' pointer for move/copy. Rejecting the root path ("") is what enforces the element-level boundary — a charm rewrites individual args, never swaps the whole argv (let alone cmd).

func VersionToken

func VersionToken(output string, key VersionKey) (token string, note string)

VersionToken reduces a probe's raw output to the string that enters the cache key, and returns a note when it had to degrade.

The note is never an error. Every degradation here is a case where magus can still produce a CORRECT key by being more conservative than the author asked for, and failing the run instead would break a build for a cache-key reason.

Types

type Charm

type Charm struct {
	Ops []PatchOp `json:"ops,omitempty"`
}

Charm declares how one active charm modifies a target's argv: an ordered RFC 6902 JSON Patch applied over the base. Charms are element-level — whole-document (root, empty-path) replacement is rejected by ValidatePatch — so multiple active charms compose without one wiping another. magus-utils types mirrors it to the Buzz `object Charm`, and the magus/charm constructors return it.

type CharmConflict

type CharmConflict struct {
	Name         string `json:"name"                    yaml:"name"`
	OverriddenBy string `json:"overridden_by,omitempty" yaml:"overridden_by,omitempty"`
}

CharmConflict reports an active charm (Name) whose edit is overwritten by another active charm (OverriddenBy) on the same command, so Name has no effect there. The winner is decided by sorted charm name, not declared precedence.

type CharmTraceStep

type CharmTraceStep struct {
	Charm   string   `json:"charm,omitempty"   yaml:"charm,omitempty"`
	Command []string `json:"command"           yaml:"command"`
}

CharmTraceStep is one line of a charm-application trace: the command (cmd as element 0) after the named charm's patch applies on top of the prior step. The base step (before any charm) has an empty Charm.

type Command

type Command struct {
	Bin    string           `json:"bin,omitempty"`
	Args   []string         `json:"args,omitempty"`
	Charms map[string]Charm `json:"charms,omitempty"`
	// Sources, when non-empty, are doublestar globs (relative to the project
	// directory this command runs in) that the RUNNER expands into a file list
	// at EXECUTION time, via the same walk that builds the cache key
	// (cache.ExpandSources) - so a Sources-declaring op inherits the workspace's
	// declared ignore dirs (the core project.IgnoreDirs plus the issuing spell's
	// own mgs_listIgnoreDirs) instead of hardcoding directory names.
	//
	// An op handler runs ONCE with a null Target and is reduced to a static
	// {bin, args} record, so it cannot walk a project directory itself - which is
	// why a spell used to shell out to `find | xargs`. Declaring Sources defers
	// that walk to the runner, per project, with no shell involved.
	//
	// Expanded files are appended to Args in one of two shapes, chosen by
	// SourcesEach: batched (the default, under an ARG_MAX-safe limit) or one
	// invocation per file. A glob set matching nothing runs the command ZERO times
	// and reports success, the equivalent of `xargs -r`.
	//
	// Empty (the default) leaves Args exactly as declared.
	Sources []string `json:"sources,omitempty"`
	// SourcesEach runs Bin once PER file Sources matches (xargs -n1: one file,
	// one invocation), each invocation appending that single file to Args. False
	// (the default) batches every matched file across as few invocations as fit
	// under the runner's ARG_MAX-safe limit. Meaningless without Sources.
	SourcesEach bool `json:"sources_each,omitempty"`
	// Capture makes this command's spell method return its exec record. The field
	// belongs on Command because it is declared by a Command-returning handler;
	// Op carries the resolved copy that dispatch reads.
	Capture bool `json:"capture,omitempty"`
	// Secrets declares the environment this command needs, as env var name -> provider
	// reference: {"NPM_TOKEN": "NPM_TOKEN"} sets $NPM_TOKEN in the child from whatever
	// the workspace's secret provider resolves that reference to.
	//
	// It is the declarative escape hatch for the two shapes that cannot reach a
	// magusfile body: a command op (static argv) and a provided project (no magusfile).
	// Refs resolve through the same secret.Resolver at spawn and are injected into ONLY
	// this child's environment - never into Args, never logged, never returned by
	// `magus describe` - and Resolver.Read registers each value for redaction. Charms
	// patch Args only. Refs are static data, so the op stays hashable and describable
	// without ever holding a secret.
	Secrets map[string]string `json:"secrets,omitempty"`
	// Hints classify a FAILURE of this command into a next step. Each entry pairs a
	// substring of the tool's output with the advice magus prints when the command
	// exits non-zero and that substring appeared. The first declared match wins; a
	// command that succeeds never consults them.
	//
	// This lets a tool's own error teach its fix: "authentication required" from
	// `docker buildx build --push` is a complete diagnosis to anyone who knows docker
	// and an exit code to everyone else.
	//
	// `json:"-"` is LOAD-BEARING. BuiltinsHash marshals the resolved registry into
	// every project's SpellDefVersion, so a serialized field puts its contents in every
	// cache key - rewording a sentence of advice would invalidate every target in every
	// project. Doc is excluded for the same reason. JSON is not used to transport an
	// Op; the only marshal of the registry is the hash itself.
	Hints []Hint `json:"-"`
}

Command is the declarative description of what a command op runs: a program on PATH (Bin), its argument vector (Args), and charm modifiers keyed by charm name. It describes what would run, not the running of it — the static form is what lets the argv be charm-patched, hashed into the cache key, and previewed by `magus describe` without executing. It is the single source of truth shared two ways: magus-utils types mirrors it to the Buzz `object Command` a spell op returns, and the resolved spell Op embeds it. An empty Command (no Bin) is the no-op marker.

Bin or any Args entry may be a bare $NAME token (e.g. "$MAGUS") referencing a value the RUNNER computes for this invocation, replacing a spell shelling out to `sh -c` to expand a variable magus itself set. See internal/interp/bindings' resolveRunnerRefs for the resolution rule and its scope: it is NOT the process environment.

func (Command) SourcesPlaceholder

func (c Command) SourcesPlaceholder() []string

SourcesPlaceholder renders Sources as a single human-readable argv token, for a renderer that cannot execute the runner's real expansion - `magus describe` and the dry-run preview both render a Command outside any project directory, so neither can walk Sources into real files the way runCommand does at execution time. nil when Sources is unset, so an ordinary Command's rendered argv is completely unchanged.

type Descriptor

type Descriptor struct {
	Name     string   `json:"name"`
	Needs    []string `json:"needs,omitempty"`
	Provides []string `json:"provides,omitempty"`
	// IgnoreDirs names non-source directories this spell's ecosystem generates
	// (vendor, node_modules, target, __pycache__) so the input-hashing walk prunes
	// them per-project instead of the engine hardcoding language-specific names.
	// Dot-directories are already skipped structurally, so only non-dot names belong
	// here. Declared by mgs_listIgnoreDirs.
	IgnoreDirs []string `json:"ignore_dirs,omitempty"`
	// Manifests is the ordered list of candidate manifests this spell's ecosystem
	// declares. Ordered because some ecosystems have genuine alternatives - the first
	// one present in a project directory is its manifest. Declared by
	// mgs_listManifests. Distinct from Needs (cache/affected input globs), from a
	// spell's DeclarationFiles (project discovery, not exposed on Descriptor), and
	// from VersionCmd (the toolchain's own version, not the project's).
	Manifests   []Manifest          `json:"manifests,omitempty"`
	Opaque      bool                `json:"opaque,omitempty"`
	TargetNeeds map[string][]string `json:"target_needs,omitempty"`
	Ops         map[string]Op       `json:"targets,omitempty"`
	// Tools is every binary this spell drives, keyed by the bin name an op names in
	// its Command - so no op restates which tool it runs, and everything magus knows
	// about that binary sits in one place.
	//
	// It replaces five separate declarations (a primary probe, named probes, a primary
	// key, named keys, readiness) split across two axes that were never orthogonal.
	// The split also hid its own subtleties: govulncheck declaring no cache key is a
	// deliberate choice, and in two parallel maps that reads as an absence nobody
	// notices rather than a decision someone made.
	//
	// There is no privileged "primary" tool. `go` had one only for historical cache-key
	// reasons, and nothing principled distinguished it from golangci-lint - both are
	// binaries the spell drives, so both key the cache as spell:tool:version.
	Tools map[string]Tool `json:"tools,omitempty"`

	// Language is the canonical source language this spell adapts (e.g. "go",
	// "typescript"), declared by mgs_getLanguage. It tags the spell node so a
	// `language:` query groups the adapter with the files and symbols of that language;
	// empty for a spell that adapts no single source language (docker, cosign).
	Language string `json:"language,omitempty"`
	// DocOps names the ops authored as function handlers (sorted) — as opposed to
	// plain {cmd,args} record ops. `magus doctor` requires a doc comment on each of
	// these for a workspace-local Buzz spell. Not serialized: it is a resolution-path
	// fact (which authoring form an op used), not part of the spell's cache identity,
	// so it stays out of BuiltinsHash.
	DocOps []string `json:"-"`
}

Descriptor is a spell's static description. For built-ins it is produced by compiling each spells/<name>/spell.buzz to bytecode (go:generate magus-utils spells), embedding the blob, and resolving its mgs_ functions at load time.

func (Descriptor) OpNames

func (d Descriptor) OpNames() []string

OpNames returns the spell's op names in sorted order.

func (Descriptor) ServiceOpNames

func (d Descriptor) ServiceOpNames() []string

ServiceOpNames returns the names of the spell's service ops (sorted). A service op runs a long-running process, so its target is never cached.

type Diagnostic

type Diagnostic struct {
	File     string
	Line     int
	Col      int
	Severity string
	Code     string
	Message  string
}

Diagnostic is one finding read out of a tool's output. Line and Col are 1-based; zero means the tool did not say, so a file-level finding needs no sentinel.

func ParseDiagnostics

func ParseDiagnostics(f DiagnosticFormat, text string) []Diagnostic

ParseDiagnostics reads text as format f, returning one Diagnostic per recognized line and skipping the rest. An unknown or absent format yields nothing, so a caller treats "no format declared" and "nothing recognized" the same way: fall back to showing the output as written.

type DiagnosticFormat

type DiagnosticFormat string

DiagnosticFormat names the convention a tool prints its findings in, so magus reads them as records (file, line, severity, message) instead of scraping prose.

A CONVENTION, never a per-tool shape: a registry of per-tool patterns rots, so magus implements documented standards once and tools opt in. The flag that produces it (`-f gnu`, `--format=gcc`) belongs in the op's own args - magus never rewrites argv, which would collide with charms and contradict what `magus describe` prints.

const (
	// DiagnosticNone is the zero value: prose as far as magus is concerned. Default
	// because a mis-parsed line claims a file and a line that do not exist.
	DiagnosticNone DiagnosticFormat = ""
	// DiagnosticGNU is the GNU Coding Standards format,
	// `[program:]file:line[:column]: severity: message`. hadolint spells it `-f gnu`
	// and shellcheck `--format=gcc`; gcc, clang and ruff emit the same skeleton.
	DiagnosticGNU DiagnosticFormat = "gnu"
)

func (DiagnosticFormat) String

func (v DiagnosticFormat) String() string

String renders v for an error message: the value, or "unset" when empty.

func (DiagnosticFormat) Valid

func (v DiagnosticFormat) Valid() bool

Valid reports whether v is a declared DiagnosticFormat. The zero value is valid: it means the field was not set, which callers distinguish from a wrong value.

A switch rather than a scan over Values: Values allocates a fresh slice per call so its result can never be mutated by a caller, which is the right trade for a helper that builds an error message and the wrong one for a predicate.

func (DiagnosticFormat) Values

func (v DiagnosticFormat) Values() []string

Values lists the DiagnosticFormat values a caller may choose, excluding the zero value.

type Driver

type Driver interface {
	// Name returns the stable identifier for this spell or tool.
	Name() string
	// Invoke runs the spell or tool with the given request.
	// Implementations ignore fields they don't use.
	Invoke(ctx context.Context, req InvokeRequest) (InvokeResponse, error)
}

Driver is implemented by both spells (*Spell) and MCP tools. Metadata (markers, claims, sources) is not part of this interface.

type Hint

type Hint struct {
	// Contains is matched against the failed command's stdout and stderr SEPARATELY,
	// never against the two joined: a substring spanning the seam of two independent
	// streams would fire on output that appeared in neither.
	Contains string `json:"contains"`
	// Advise is the text magus prints. Write the command to run rather than the
	// diagnosis - it is printed after the tool's own error, which already said what
	// went wrong.
	Advise string `json:"advise"`
}

Hint is one failure classification: when a command fails and Contains appears in its output, magus prints Advise.

The field names are the authoring surface. A spell writes these in Buzz, so they are the same words in Go, in the generated mirror, and in a spell file:

Hint{contains = "authentication required", advise = "run `docker login <registry>`"}

Contains rather than "match": it names the actual operation (strings.Contains, not a pattern), and `match` is RESERVED in Buzz, so such a field could only ever be written `@"match" = ...`. PatchOp.From hit the same wall and solved it with a differing Buzz name, which then drifted from the Go name and silently produced empty fields.

type InvokeRequest

type InvokeRequest struct {
	Target string         // build target or sub-action
	Dir    string         // project directory; empty for workspace-level MCP tools
	Params map[string]any // MCP tool parameters; ignored by *Spell
}

InvokeRequest is the unified invocation payload for Driver. Execution charms (including "rw") are carried on the context, not here.

type InvokeResponse

type InvokeResponse struct {
	Text string // human-readable output
	Data any    // structured result for MCP tools; nil for *Spell
}

InvokeResponse is the unified result payload for Driver.

type Manifest

type Manifest struct {
	Value string `json:"value"`
	// LockCandidates names the lockfiles this ecosystem MIGHT resolve Value into, of
	// which exactly one will exist - not the lockfiles a project has. package.json
	// resolves into any of pnpm-lock.yaml, package-lock.json, npm-shrinkwrap.json,
	// yarn.lock or a bun lockfile depending on which package manager is in use, and
	// pyproject.toml into any of poetry.lock, pdm.lock, uv.lock or Pipfile.lock. Go and
	// Rust have exactly one each, which is what makes the plural easy to misread: a
	// consumer that takes the first element is right for go.sum and wrong for npm.
	//
	// Bare filenames rather than Paths, because a lockfile's DIRECTORY is not knowable
	// here. mgs_ functions run during spell discovery, before a project exists, and a
	// workspace hoists one lockfile to its root to serve many manifests. Which
	// directory holds the live one is resolved by walking up from the project, not
	// declared. (Locks are also relative to nothing, so Path's base - the reason Path
	// beats a string elsewhere - has nothing to carry.)
	LockCandidates []string `json:"lock_candidates,omitempty" buzz:"lockCandidates"`
}

Manifest is one manifest an ecosystem declares: the file a project's dependencies are declared in (go.mod, package.json, Cargo.toml, pyproject.toml), plus the lockfiles that ecosystem resolves them into.

Value keeps the field name Path uses so the two decode identically. mgs_listManifests returned [Path] before this type existed, and the decoder reads keys structurally, so a spell still returning Path values loads as a Manifest declaring no lockfile rather than failing. That is the whole compat story; there is no second decode path.

type Op

type Op struct {
	// Kind is the op's lifecycle kind (OpKind*); empty means OpKindCommand.
	Kind string `json:"kind,omitempty"`
	Command
	// Service is set only for a service op (Kind == OpKindService); nil otherwise.
	Service *Service `json:"service,omitempty"`
	Capture bool     `json:"capture,omitempty"`
	// Doc is the handler function's documentation comment (see buzz Chunk.Doc),
	// surfaced by `magus describe` and enforced by `magus doctor` for local Buzz
	// spells. Empty for command built-ins (their Doc is not serialized in bytecode).
	// omitempty keeps it out of BuiltinsHash so the cache key is unaffected.
	Doc string `json:"doc,omitempty"`
}

Op is a single dispatchable surface of a spell — one tool-native Operation (see docs/operations.md). An op is one of two declarative shapes, tagged by Kind: a command op (OpKindCommand, the default) whose embedded Command Bin/Args run via PATH with no script VM; or a service op (OpKindService) whose Service describes a long-running process `magus run` blocks on. Either way the form is declarative, so the argv is charm-patched and rendered by `magus describe` without executing.

For a service op the embedded Command mirrors Service.Command, so every fork/render/cache path reads the op uniformly. Command.Bin may be empty, for a no-op marker op.

In-VM spell logic is not an op kind: a remote cache provider is a separate contract magus invokes by name, and other custom logic belongs in a magusfile target body.

Capture makes the op's magusfile method return the {stdout, stderr, code, ok} record instead of void, for ops whose output is the point rather than the exit code. It is Go-internal, not mirrored to Buzz.

func (Op) IsService

func (o Op) IsService() bool

IsService reports whether the op is a service op (a long-running process) rather than a command op (run to completion).

func (Op) Key

func (o Op) Key() []string

Key returns the lines identifying this op's work for the cache: the command it runs, which is the honest answer to "what work is this" and what lets two entry points onto the same op share an entry.

A service op contributes nothing, and that is correct rather than a gap: a service-backed target is forced NoCache at run.go's step construction, so it is never replayed and has no key to protect. A function-op computes its argv in-VM, so an empty Bin likewise has nothing to say.

func (Op) OpKind

func (o Op) OpKind() string

OpKind returns the op's kind, resolving the empty default to OpKindCommand so callers dispatch on one canonical value.

type Option

type Option func(*Spell)

Option configures NewSpell.

func WithCommandConflicts

func WithCommandConflicts(fn func(target string, charms []string) (conflicts []CharmConflict, ok bool, err error)) Option

WithCommandConflicts sets the charm-conflict detector used by `magus describe` to report active charms whose edit another active charm overrides.

func WithCommandExplainer

func WithCommandExplainer(fn func(target string, charms []string) (steps []CharmTraceStep, ok bool, err error)) Option

WithCommandExplainer sets the charm-trace renderer used by `magus describe target --explain`. See Spell.ExplainCommand.

func WithCommandRenderer

func WithCommandRenderer(fn func(target string, charms []string) (cmd string, args []string, ok bool, err error)) Option

WithCommandRenderer sets the fork-command renderer used by `magus describe` to preview the charm-applied argv without executing. See Spell.RenderCommand.

func WithDeclarationDirGlobs

func WithDeclarationDirGlobs(globs ...string) Option

func WithDeclarationFiles

func WithDeclarationFiles(files ...string) Option

func WithDependsOn

func WithDependsOn(fn func(dir string) []string) Option

func WithDocRequiredTargets

func WithDocRequiredTargets(targets ...string) Option

WithDocRequiredTargets records the function-handler targets `magus doctor` requires a doc comment on (workspace-local Buzz spells).

func WithIgnoreDirs

func WithIgnoreDirs(dirs ...string) Option

func WithInternal

func WithInternal() Option

WithInternal marks a registration as dispatch plumbing rather than a spell a user binds, keeping it out of every surface that enumerates spells.

It exists for exactly one registration: `magusfile`. A spell is defined as a library of tool-native ops for ONE TOOLCHAIN (go-build, cargo-clippy, eslint) - see docs/concepts/spells.md, whose built-in table has never listed magusfile. The magusfile registration adapts no toolchain and contributes no ops; it reuses the driver interface so a magusfile's own targets dispatch through the same path. Registering it plainly made the code contradict the docs: `magus describe spells` listed a spell the reference says does not exist, and because every project is DISCOVERED by having a magusfile, `magus ls` stamped "spell: magusfile" on all of them - a field that told a reader nothing, since it was true by construction.

func WithInvoker

func WithInvoker(fn func(ctx context.Context, req InvokeRequest) (any, error)) Option

WithInvoker sets the function that runs a target; a spell with none is a no-op. The invoker receives the full request (so function-ops can read Params) and returns structured Data (nil for fork targets), surfaced via InvokeResponse.

func WithLanguage

func WithLanguage(language string) Option

WithLanguage sets the canonical source language the spell adapts, used to tag the spell node so a `language:` query reaches the adapter alongside that language's code.

func WithManifests

func WithManifests(manifests ...Manifest) Option

WithManifests sets the ordered candidate manifests this spell's ecosystem declares. See Spell.Manifests for the ordering contract and how this differs from WithSources, WithDeclarationFiles, and WithVersionProbe.

func WithOpaque

func WithOpaque() Option

WithOpaque marks the spell as opaque: it delegates to a foreign process that manages its own dependency graph, so magus treats the project as a black box rather than tracking per-file inputs. Informational only.

func WithOutputs

func WithOutputs(outputs ...string) Option

func WithServiceTargets

func WithServiceTargets(names ...string) Option

WithServiceTargets records which of the spell's targets are backed by a service op (long-running). The runner forces such targets uncacheable so a re-run restarts the process instead of replaying a completed-target result.

func WithServiceView

func WithServiceView(fn func(target string) (view *ServiceView, ok bool)) Option

WithServiceView sets the static service-facts accessor used by `magus describe target` to describe a service op before it runs.

func WithSources

func WithSources(sources ...string) Option

func WithTargetCharms

func WithTargetCharms(charms map[string][]string) Option

WithTargetCharms records the charm names each target declares, for discovery (e.g. `magus describe`). The map is cloned to prevent caller mutation.

func WithTargetDocs

func WithTargetDocs(docs map[string]string) Option

WithTargetDocs records each target handler's doc comment, surfaced by `magus describe`. The map is cloned to prevent caller mutation.

func WithTargetSources

func WithTargetSources(sources map[string][]string) Option

WithTargetSources attaches workspace-root globs for the cache key per target. The map is cloned to prevent caller mutation.

func WithTargets

func WithTargets(targets ...string) Option

func WithTools

func WithTools(tools map[string]Tool) Option

WithTools declares every binary this spell drives, keyed by bin name.

func WithVersionProber

func WithVersionProber(fn func(ctx context.Context, cmd Command, dir string) (string, error)) Option

WithVersionProber injects how a tool's version argv is run. The engine owns process execution, so this package never spawns anything itself.

type PatchOp

type PatchOp struct {
	Op    PatchOpKind `json:"op"`
	Path  string      `json:"path"`
	Value string      `json:"value,omitempty"`
	// From is the move/copy source JSON Pointer. The Buzz field is named fromPtr
	// because `from` is a reserved word in Buzz, so no mirror can declare it; the JSON
	// stays "from" (RFC 6902). Everything on the Buzz side of the boundary - the charm
	// constructors that emit it, the decoder that reads it back, and the mirror - uses
	// fromPtr. They did not always agree: the constructors emitted "from", which the
	// mirror said was called fromPtr, so an annotated charm would have read an empty
	// field. Nothing caught it because nothing referenced the Buzz name.
	From string `json:"from,omitempty" buzz:"fromPtr"`
}

PatchOp is one RFC 6902 operation over a target's argv array. Path/From are single-token JSON Pointers (RFC 6901) into the array — "/N" for an index, or "/-" (add only) for the append position. Value is the string element for add/replace/test; From is the source pointer for move/copy. magus-utils types mirrors it to the Buzz `object PatchOp`.

type PatchOpKind

type PatchOpKind string

PatchOpKind is one JSON Patch (RFC 6902) operation name. A charm is an ordered patch applied over the target's base argv, treated as a JSON array of strings.

It is a defined type rather than a bare string so the Buzz mirror can declare it as an enum: charm.buzz used to write `op = "add"` by hand at every constructor, where a typo produced a patch that failed validation at load with a message naming the value rather than the line. `PatchOpKind.add` is checked when the spell compiles.

const (
	// OpNone is the zero value. It is not a valid operation - ValidatePatch rejects
	// it - and exists so the mirror's enum has a default case to name.
	OpNone    PatchOpKind = ""
	OpAdd     PatchOpKind = "add"
	OpRemove  PatchOpKind = "remove"
	OpReplace PatchOpKind = "replace"
	OpMove    PatchOpKind = "move"
	OpCopy    PatchOpKind = "copy"
	OpTest    PatchOpKind = "test"
)

func (PatchOpKind) String

func (v PatchOpKind) String() string

String renders v for an error message: the value, or "unset" when empty.

func (PatchOpKind) Valid

func (v PatchOpKind) Valid() bool

Valid reports whether v is a declared PatchOpKind. The zero value is valid: it means the field was not set, which callers distinguish from a wrong value.

A switch rather than a scan over Values: Values allocates a fresh slice per call so its result can never be mutated by a caller, which is the right trade for a helper that builds an error message and the wrong one for a predicate.

func (PatchOpKind) Values

func (v PatchOpKind) Values() []string

Values lists the PatchOpKind values a caller may choose, excluding the zero value.

type ProvidedProject

type ProvidedProject struct {
	// Path is the project's directory relative to the WORKSPACE ROOT, forward
	// slashes. Required. It must stay inside the root and must not be "." - the root
	// project is the one the magusfile that wired the provider already owns.
	Path string `json:"path"`
	// Name is the human label, for a foreign tool whose project name is not its
	// directory (an nx project named "@acme/ui" rooted at libs/ui). Empty derives
	// one from the path, exactly as a magusfile-declared project does.
	Name string `json:"name"`
	// Spells names the spells to bind, contributing their ops, sources and outputs.
	Spells []string `json:"spells"`
	// DependsOn names upstream projects, resolved exactly as magus\project's
	// "depends_on" is: a bare path is workspace-relative, a dot-relative one is
	// relative to this project.
	//
	// The buzz tag is load-bearing, not cosmetic: this record mirrors the option keys
	// an AUTHOR writes in magus\project({...}), which is snake_case, so the generator's
	// lowerCamel default would silently rename the key the decoder reads. (The
	// host-returned ProjectEntry mirror spells the same concept dependsOn because it
	// mirrors a returned struct, not an authored map.)
	DependsOn []string `buzz:"depends_on" json:"depends_on"`
	// Sources and Outputs are globs relative to THIS PROJECT'S directory, not to the
	// workspace root - the same anchor magus\project's options use, and the anchor
	// baseStep joins against the project path. A provider reporting a foreign tool's
	// workspace-relative globs must re-anchor them first: for a project at libs/foo,
	// "libs/foo/**/*.ts" is wrong and "**/*.ts" is right.
	//
	// The anchor also bounds what is expressible: an output the foreign tool writes
	// OUTSIDE the project directory (nx's dist/{projectRoot} convention) has no
	// project-relative spelling and cannot be declared here.
	Sources []string `json:"sources"`
	Outputs []string `json:"outputs"`
	// Exclusive marks the project as must-not-run-alongside-peers in a batch.
	Exclusive bool `json:"exclusive"`
}

ProvidedProject is one project a workspace provider supplies: the same facts a magusfile author would have written in magus\project({...}), reported by the tool that already knows them. Buzz sees it as `object Project` in magus/spell (the Go name carries the adjective because types.Project and types.ProjectEntry already exist; the authoring surface does not need it).

The fields match that options map one for one, so a workspace has ONE vocabulary for configuring a project. Two are missing: "targets" and "watch_ignore" are magus execution POLICY, which no foreign tool knows, so they stay in the magusfile and layer on afterwards via magus\project("libs/foo", {...}).

Spells is a list of NAMES rather than handles: a spell cannot hold another spell's handle, so a provider has only the name to give. Every name must resolve.

The record crosses INTO magus as a Buzz object and is marshaled back out as JSON by the provider cache, keyed by the json tags below. Those tags are not decoration - without them the cache encoded under Go field names, which musttag could not see. A rename here is a wire change in two directions: regenerate the mirror, and bump providerCacheVersion so existing entries miss.

type Secret

type Secret struct {
	// Value is the resolved credential.
	Value string
}

Secret is what a provider spell's resolve_secret op returns: one resolved credential.

A typed return rather than a bare `str`, and the distinction that makes it worth having is narrow but real. Buzz does not check host-call RESULTS or object field literals - which is why magus\secret.read still hands a magusfile a plain string, and why there is no Buzz-level Secret type there. It DOES check function signatures, so `resolve_secret(...) > Secret` is enforced: a provider that returns something else fails to compile rather than failing at the first read.

It also gives the contract somewhere to grow: a provider that knows its credential's lifetime, or wants to declare a cache identity for it, has a field to add without another breaking change.

What it does NOT do is mask anything inside the spell. Buzz has no self-masking type; the Go side wraps this in a secret.Value the moment it crosses the boundary, and that is where the protection starts.

type Service

type Service struct {
	Command   Command `json:"command,omitempty"`
	Readiness Command `json:"readiness,omitempty"`
	Stop      Command `json:"stop,omitempty"`
	// Distinct, when non-empty, opts this service out of shared-instance dedup and
	// silences its near-duplicate (MGS5001) warning. It is a required reason string
	// (the golangci-lint nolintlint model): being distinct without a reason is
	// meaningless, so the reason IS the value. Recorded so `magus doctor` can audit
	// every deliberate divergence and flag reasons that no longer apply (a distinct
	// service with no remaining near-duplicate is a stale suppression).
	Distinct string `json:"distinct,omitempty"`
	// Idle overrides the per-service idle timeout (a duration like "30m") after which
	// the daemon reaps this shared service once its last dependent releases. Empty
	// uses the daemon's global default. Consumed by the service supervisor.
	Idle string `json:"idle,omitempty"`
}

Service is the declarative description of a long-running process a service op manages. Command (required) is the process. Run directly (`magus run <target>`) it is forked in the foreground and blocked on (Ctrl-C signals the child); reached as a dependency it is supervised in the background (see internal/service). Readiness and Stop are optional: Readiness is a probe polled until it exits 0 (how the supervisor learns the process is up and gates dependents on it), and Stop is a graceful-shutdown command run instead of signaling the process (also replayed by the daemon's crash reaper). Like Command each is static data - inspectable, cache-keyable, charm-patchable. It is a distinct return type (vs Command) so an op's kind is inferred from what it returns. magus-utils types mirrors it to the Buzz `object Service` a service op returns.

type ServiceView

type ServiceView struct {
	Readiness   []string `json:"readiness,omitempty"   yaml:"readiness,omitempty"`   // probe command polled until it exits 0, if any
	Stop        []string `json:"stop,omitempty"        yaml:"stop,omitempty"`        // graceful-shutdown command, if any
	Idle        string   `json:"idle,omitempty"        yaml:"idle,omitempty"`        // idle-timeout override (a duration), else the daemon default
	Distinct    string   `json:"distinct,omitempty"    yaml:"distinct,omitempty"`    // dedup opt-out reason; empty means the instance is shared
	Fingerprint string   `json:"fingerprint,omitempty" yaml:"fingerprint,omitempty"` // content hash that keys shared-instance dedup
}

ServiceView is the static, pre-run description of a service op, shown by `magus describe target` when the target is a service. Every field is known without starting the service; live registry state (ref-count, probe status) needs the daemon and is not part of this static view.

type Spell

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

Spell teaches magus how to build/test/lint/format projects of a given language. Spells are interned singletons registered at init() time; all fields are unexported.

func NewSpell

func NewSpell(name string, opts ...Option) *Spell

NewSpell constructs a Spell with the given name and options.

func (*Spell) Charms

func (s *Spell) Charms(target string) []string

func (*Spell) ConflictingCharms

func (s *Spell) ConflictingCharms(target string, charms []string) (conflicts []CharmConflict, ok bool, err error)

ConflictingCharms returns the active charms whose edit is overridden by another active charm on the target's command (both edit the same argument; the loser has no effect). ok is false on the same conditions as RenderCommand. It executes nothing; `magus describe target ...:a,b` surfaces the result before a run.

func (*Spell) DeclarationDirGlobs

func (s *Spell) DeclarationDirGlobs() []string

func (*Spell) DeclarationFiles

func (s *Spell) DeclarationFiles() []string

func (*Spell) DependsOn

func (s *Spell) DependsOn(dir string) []string

DependsOn returns in-workspace dependency paths for the project at dir.

func (*Spell) DocRequiredTargets

func (s *Spell) DocRequiredTargets() []string

DocRequiredTargets returns the function-handler targets `magus doctor` requires a doc comment on. Non-empty only for workspace-local Buzz spells (record-style {cmd,args} ops and Teal spells, whose comments aren't captured, are excluded).

func (*Spell) ExplainCommand

func (s *Spell) ExplainCommand(target string, charms []string) (steps []CharmTraceStep, ok bool, err error)

ExplainCommand returns the charm-application trace for a static preview: step 0 is the base command (no charms), and each later step is the command after one more active charm's patch, in the deterministic order magus applies them. ok is false on the same conditions as RenderCommand (no renderer, function-op, no-op marker). A non-nil err means an active charm's patch does not apply to this op's argv (see RenderCommand). It executes nothing.

func (*Spell) HasVersionProbe

func (s *Spell) HasVersionProbe() bool

HasVersionProbe reports whether ANY tool can report a version, so a caller can skip the probe pass entirely for a spell that declares none.

func (*Spell) IgnoreDirs

func (s *Spell) IgnoreDirs() []string

IgnoreDirs returns the non-source directory names this spell's ecosystem generates (vendor, node_modules, target, ...), declared by mgs_listIgnoreDirs. The input-hashing walk prunes them for a project this spell resolves, so the engine holds no language-specific directory names. Dot-directories are skipped structurally and never appear here.

func (*Spell) Internal

func (s *Spell) Internal() bool

Internal reports whether this registration is dispatch plumbing rather than a spell a user binds. See WithInternal.

func (*Spell) Invoke

func (s *Spell) Invoke(ctx context.Context, req InvokeRequest) (InvokeResponse, error)

Invoke implements Driver. A nil invoke func is a no-op. Fork-target spells ignore req.Params and return no Data; function-op spells (Buzz ops declared with "fn") receive req.Params and return their result as Data, the channel the remote cache provider and other Go callers read. Charms (including the built-in "rw") ride on ctx; a target that cares reads them via HasCharm.

func (*Spell) IsServiceTarget

func (s *Spell) IsServiceTarget(name string) bool

IsServiceTarget reports whether target name is backed by a service op (a long-running process). The runner forces such targets uncacheable.

func (*Spell) Language

func (s *Spell) Language() string

Language returns the canonical source language the spell adapts (e.g. "go", "typescript"), or "" when it adapts no single language. It tags the spell node so a `language:` query groups the adapter with that language's files and symbols.

func (*Spell) Manifests

func (s *Spell) Manifests() []Manifest

Manifests returns the ordered candidate manifests this spell's ecosystem declares (go.mod, package.json, Cargo.toml, pyproject.toml), each with the lockfiles that ecosystem might resolve it into, as declared by mgs_listManifests. Ordered because a language can have genuine alternatives (Python's pyproject.toml / setup.py / setup.cfg): the first candidate present in a project directory is that project's manifest, not all of them at once.

A manifest answers two questions that happen to share a file. It carries the project's own VERSION, which is what this was originally for, and it declares the project's DEPENDENCIES, which is what its lock candidates lead to. Go is the case that makes them look like one question - go.mod holds both - and npm is the case that separates them, since package.json pins neither its own resolved dependency versions nor, in a workspace, the lockfile's location.

Do not confuse this with three adjacent but distinct facts: Sources (mgs_listRequiredGlobs) answers "what feeds my targets" and often already lists a manifest AND its lockfiles as cache/affected inputs (package.json and pnpm-lock.yaml among **/*.ts and friends) - that is a different question from "what declares this project". DeclarationFiles answers "a directory holding this file IS a project of mine" (discovery), used today only by the magusfile spell. VersionProbe is the TOOLCHAIN's version (`go version`), which feeds cache keys, not the project's own version.

func (*Spell) Name

func (s *Spell) Name() string

Name implements Driver.

func (*Spell) Opaque

func (s *Spell) Opaque() bool

func (*Spell) Outputs

func (s *Spell) Outputs() []string

func (*Spell) ProbeVersion

func (s *Spell) ProbeVersion(ctx context.Context, tool, dir string) (string, error)

ProbeVersion runs one tool's version argv in dir and returns its raw output. It returns "" for a tool that declares no argv - including one whose version is a constant, where the caller reads Tool.Key.Const instead of spawning anything.

func (*Spell) RenderCommand

func (s *Spell) RenderCommand(target string, charms []string) (cmd string, args []string, ok bool, err error)

RenderCommand returns the command a fork target would run with the given charms applied — cmd plus the charm-patched argv — for static preview (`magus describe`). ok is false when the spell has no renderer, the target is a function-op (its argv is computed in-VM, not statically knowable), or it is a no-op marker. A non-nil err means an active charm's patch is valid in shape but does not apply to this target's argv (an out-of-range index, a failing `test` op): the charm is dead on this target and the caller surfaces it instead of dropping the command line silently. It executes nothing.

func (*Spell) ServiceView

func (s *Spell) ServiceView(target string) (view *ServiceView, ok bool)

ServiceView returns the static, pre-run description of a service target (its readiness probe, stop command, idle override, distinct reason, and fingerprint). ok is false when the target is not a service or the spell carries no service data. It executes nothing.

func (*Spell) Sources

func (s *Spell) Sources() []string

func (*Spell) TargetDoc

func (s *Spell) TargetDoc(target string) string

TargetDoc returns the documentation comment of the named target's handler, or "" when undocumented or unknown.

func (*Spell) TargetSources

func (s *Spell) TargetSources() map[string][]string

func (*Spell) Targets

func (s *Spell) Targets() []string

func (*Spell) Tool

func (s *Spell) Tool(name string) (Tool, bool)

Tool returns what this spell declares about one binary, and whether it declares it.

func (*Spell) ToolNames

func (s *Spell) ToolNames() []string

ToolNames returns the binaries this spell drives, sorted, so a caller iterates them deterministically and the cache key they produce is stable.

type Tool

type Tool struct {
	// Probe is the command that prints this binary's version, its result narrowed by
	// Key and mixed into the cache key. A zero Command means magus never asks -
	// correct for a tool that cannot report one, where Key.Const supplies the token.
	//
	// A Command rather than a bare argv so it matches Ready below: both are "run this
	// and read the result", and two shapes for that inside one record is the kind of
	// seam a reader has to hold in their head for no reason.
	Probe Command `json:"probe,omitempty"`
	// Key narrows what Probe's output contributes to the cache key. The zero value
	// keys on the whole output; see VersionKey.
	Key VersionKey `json:"key,omitempty"`
	// Ready gates an op on this binary being usable, for a client whose server may be
	// down. Its result is a precondition and never enters a cache key.
	Ready Command `json:"ready,omitempty"`
	// Supported is the version window this spell's ops work against. Empty accepts any
	// version.
	//
	// It is the fourth question about a tool, after does it exist, what version, and is
	// it usable. Without it a too-old binary fails with whatever that tool says about
	// an unrecognized flag - the same misleading failure readiness exists to prevent,
	// one step over. Checked against the extracted version, so it needs Probe.
	//
	// It states what the OPS need, not what a repo has qualified: "go::test does not
	// work below 1.21" belongs here, "we have not moved to node 25 yet" belongs in the
	// workspace's own bounds, which are intersected with this one.
	Supported VersionBounds `json:"supported,omitempty"`
	// Diagnostics names the convention this binary prints findings in; empty means
	// prose. On the tool rather than the op because the format is the binary's:
	// hadolint reports the same way whichever op invokes it.
	Diagnostics DiagnosticFormat `json:"diagnostics,omitempty"`
}

Tool is everything a spell declares about one binary it drives.

Keyed by bin name in Descriptor.Tools, which is what lets an op resolve its own entry through the Command.Bin it already names.

func (Tool) HasProbe

func (t Tool) HasProbe() bool

HasProbe reports whether magus can learn a version for this tool, by running one or by being handed a constant.

type Verdict

type Verdict int

Verdict names how a probed version relates to a window.

const (
	// VerdictInside means the version satisfies every bound that was declared.
	VerdictInside Verdict = iota
	// VerdictTooOld means the version is below Min.
	VerdictTooOld
	// VerdictTooNew means the version is at or above Below.
	VerdictTooNew
	// VerdictUnknown means the comparison could not be made - an unparsable probed
	// version, or a bound that is not a version. It is never a violation: a window
	// magus cannot evaluate must not fail a build, the same way an unprobeable tool
	// is not "too old".
	VerdictUnknown
)

type VersionBounds

type VersionBounds struct {
	// Min is the oldest version accepted, inclusive. Empty accepts any.
	Min string `json:"min,omitempty" yaml:"min,omitempty"`
	// Below is the first version REJECTED, exclusive. Empty accepts any.
	Below string `json:"below,omitempty" yaml:"below,omitempty"`
}

VersionBounds is the window of versions a binary is allowed to report: an inclusive floor and an exclusive ceiling, each a plain version.

Two named bounds rather than one constraint string, deliberately: a range language is a mini-language in a field, where the comma means AND and `^` and `~` mean materially different things across npm, Composer and the library this parses with. A toolchain window needs a floor and sometimes a ceiling, not disjunction.

The shape also makes a defect unrepresentable: the single-constraint form reported every failure as ToolTooOld, so a too-NEW binary was told it was too old.

Below is EXCLUSIVE because that is the bound people mean. "Not the 25 line" written as an inclusive `<= 24` rejects 24.19.0.

Declared by two owners with different authority, and intersected before use: a spell states what its ops need to function at all, and a workspace states policy. Neither can loosen the other.

func (VersionBounds) Check

func (b VersionBounds) Check(version string) Verdict

Check reports how version sits in the window.

A bound that fails to parse yields VerdictUnknown rather than being skipped. Skipping would let a typo silently widen the window to everything, which is the failure mode a declared bound exists to prevent; callers that can reject at declaration time do so, and this is the backstop for the ones that cannot.

func (VersionBounds) Intersect

func (b VersionBounds) Intersect(other VersionBounds) VersionBounds

Intersect returns the narrower of two windows, bound by bound.

Narrower always wins because the two declarations answer different questions and neither may relax the other: a spell says what its ops need to run, a workspace says what it has qualified. An empty bound on either side contributes nothing, so a workspace that sets only a ceiling keeps the spell's floor.

An unparsable bound is kept rather than discarded. Dropping it would silently widen the result, and Check turns it into VerdictUnknown where the reader can see it.

func (VersionBounds) IsZero

func (b VersionBounds) IsZero() bool

IsZero reports whether the window constrains nothing.

type VersionComponent

type VersionComponent string

VersionComponent names how much of a probed version reaches the cache key.

The values are the SemVer spec's own component names, and the same strings node-semver's inc() accepts and diff() returns - nothing here is magus vocabulary a reader has to learn. It is a defined type rather than a bare string so a Go SDK caller cannot typo one silently; a magusfile spelling is checked at decode.

const (
	// VersionNone is the zero value: no extraction happens and the probe's whole
	// output keys the cache. It is not a component - it is the ABSENCE of a request
	// to find one, and it is the default precisely because guessing which number in
	// a tool's output is its version is a guess magus should not make unasked.
	VersionNone VersionComponent = ""
	// VersionMajor extracts a semver and keeps the major, so every 2.x shares one entry.
	VersionMajor VersionComponent = "major"
	// VersionMinor extracts a semver and keeps major.minor, so patch releases share one.
	VersionMinor VersionComponent = "minor"
	// VersionPatch extracts a semver and keeps major.minor.patch. It narrows nothing a
	// team has to reason about - two builds of one version agree - so it is the right
	// declaration for a tool that pads its version line with build identity.
	VersionPatch VersionComponent = "patch"
)

func (VersionComponent) KeyFunc

func (c VersionComponent) KeyFunc() (VersionKeyFunc, error)

KeyFunc returns the narrowing this component names, as the same func(string) string shape golang.org/x/mod/semver already exports - so a Go SDK caller can pass semver.Major directly instead of going through a VersionComponent at all.

It errors rather than falling back, because an unknown component reaching here is a declaration bug: the callers that accept authored input validate first.

func (VersionComponent) String

func (v VersionComponent) String() string

String renders v for an error message: the value, or "unset" when empty.

func (VersionComponent) Valid

func (v VersionComponent) Valid() bool

Valid reports whether v is a declared VersionComponent. The zero value is valid: it means the field was not set, which callers distinguish from a wrong value.

A switch rather than a scan over Values: Values allocates a fresh slice per call so its result can never be mutated by a caller, which is the right trade for a helper that builds an error message and the wrong one for a predicate.

func (VersionComponent) Values

func (v VersionComponent) Values() []string

Values lists the VersionComponent values a caller may choose, excluding the zero value.

type VersionKey

type VersionKey struct {
	// Const is an author-supplied constant used as the token verbatim, for a tool that
	// cannot report its own version at all. No process is spawned; the author edits the
	// string by hand to invalidate. When set, UpTo is ignored.
	Const string `json:"const,omitempty"`
	// UpTo asks for extraction and names how much of the extracted version to keep.
	// Absent means no extraction: the whole output keys the cache.
	UpTo VersionComponent `json:"upTo,omitempty"`
}

VersionKey declares what a probed tool contributes to the cache key.

The zero value keys on the probe's WHOLE output. Extraction is opt-in because finding the version means guessing which number is the version, and that guess is wrong often enough to matter: govulncheck prints the Go version first and its vulnerability database's date last, so guessing picks the wrong number AND discards the field that decides whether the verdict holds.

Declaring UpTo is two requests at once: extract a semver, and keep this much of it. UpTo patch exists to shed the commit hashes and build timestamps tools pad with.

func (VersionKey) IsZero

func (k VersionKey) IsZero() bool

IsZero reports whether the key asks for anything beyond the exact version.

type VersionKeyFunc

type VersionKeyFunc func(version string) string

VersionKeyFunc narrows an extracted version to the string that enters the cache key.

It is a func type rather than a single-method interface because that is where Go landed: slices.SortFunc over sort.Interface, http.HandlerFunc over a Handler you must declare a type for. golang.org/x/mod/semver's Major, MajorMinor, and Canonical all satisfy it as written.

Directories

Path Synopsis
gen

Jump to

Keyboard shortcuts

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