Documentation
¶
Overview ¶
Package std is the single source of truth for host-binding APIs that magusfiles call into. Each module (os, fs, vcs, …) declares its Methods here as a Module value with typed args, return types, and a Go Impl. The magus-utils bindings tool consumes these declarations and emits the Buzz trampolines into internal/interp/bindings/gen from the same Impl.
Index ¶
- Constants
- Variables
- func ArchiveCompress(ctx context.Context, src, dest string, opts map[string]any) (types.CompressResult, error)
- func ArchiveList(ctx context.Context, src string, opts map[string]any) ([]types.ArchiveEntry, error)
- func ArchiveReadFile(ctx context.Context, src, name string, opts map[string]any) (string, error)
- func ArchiveUncompress(ctx context.Context, src, dest string, opts map[string]any) (types.UncompressResult, error)
- func BuzzMethodName(m Method) string
- func BuzzSignature(mod Module, m Method) string
- func BuzzStdlibEquiv(module, method string) (string, bool)
- func CamelCase(s string) string
- func CharmAfter(_ context.Context, argv []string, anchor string, vals []string) (spells.Charm, error)
- func CharmAfterFunc(ctx context.Context, argv []string, fn Callback, vals []string) (spells.Charm, error)
- func CharmAppend(_ context.Context, vals []string) (spells.Charm, error)
- func CharmBefore(_ context.Context, argv []string, anchor string, vals []string) (spells.Charm, error)
- func CharmBeforeFunc(ctx context.Context, argv []string, fn Callback, vals []string) (spells.Charm, error)
- func CharmCopy(_ context.Context, argv []string, anchor, to string) (spells.Charm, error)
- func CharmCopyFunc(ctx context.Context, argv []string, fn Callback, to string) (spells.Charm, error)
- func CharmDrop(_ context.Context, argv []string, anchor string) (spells.Charm, error)
- func CharmDropFunc(ctx context.Context, argv []string, fn Callback) (spells.Charm, error)
- func CharmMove(_ context.Context, argv []string, anchor, to string) (spells.Charm, error)
- func CharmMoveFunc(ctx context.Context, argv []string, fn Callback, to string) (spells.Charm, error)
- func CharmPath(_ context.Context, argv []string, anchor string) (string, error)
- func CharmPathFunc(ctx context.Context, argv []string, fn Callback) (string, error)
- func CharmPrepend(_ context.Context, vals []string) (spells.Charm, error)
- func CharmSet(_ context.Context, argv []string, anchor, val string) (spells.Charm, error)
- func CharmSetFunc(ctx context.Context, argv []string, fn Callback, val string) (spells.Charm, error)
- func CharmTest(_ context.Context, argv []string, anchor string) (spells.Charm, error)
- func CharmTestFunc(ctx context.Context, argv []string, fn Callback) (spells.Charm, error)
- func CryptoBase64DecodeBytes(_ context.Context, s string) ([]byte, error)
- func CryptoBase64EncodeBytes(_ context.Context, data []byte) (string, error)
- func CryptoHmacSha256(_ context.Context, key, data []byte) ([]byte, error)
- func CryptoHmacSha256Hex(ctx context.Context, key, data []byte) (string, error)
- func CryptoMd5File(ctx context.Context, path string) (string, error)
- func CryptoMd5Hex(_ context.Context, data string) (string, error)
- func CryptoPublicKey(_ context.Context, alg, keyEnv string) (string, error)
- func CryptoSha1File(ctx context.Context, path string) (string, error)
- func CryptoSha1Hex(_ context.Context, data string) (string, error)
- func CryptoSha256File(ctx context.Context, path string) (string, error)
- func CryptoSha256Hex(_ context.Context, data string) (string, error)
- func CryptoSha512File(ctx context.Context, path string) (string, error)
- func CryptoSha512Hex(_ context.Context, data string) (string, error)
- func CryptoSign(_ context.Context, alg, data, keyEnv string) (string, error)
- func CryptoSignFile(ctx context.Context, alg, path, keyEnv string) (string, error)
- func CryptoVerify(_ context.Context, alg, data, sigHex, pubHex string) (bool, error)
- func CwdFromContext(ctx context.Context) (string, bool)
- func DiffEqual(_ context.Context, a, b string) (bool, error)
- func DiffStat(_ context.Context, a, b string) (string, error)
- func DiffUnified(_ context.Context, a, b, fromLabel, toLabel string, contextLines int) (string, error)
- func EffectiveCwd(ctx context.Context) (string, error)
- func EnvExpand(ctx context.Context, s string) (string, error)
- func EnvGet(ctx context.Context, name string) (string, error)
- func EnvGetOr(ctx context.Context, name, def string) (string, error)
- func EnvHome(_ context.Context) (string, error)
- func EnvList(ctx context.Context) (map[string]string, error)
- func EnvLoadDotenv(ctx context.Context, path string) error
- func EnvLookup(ctx context.Context, name string) (string, bool, error)
- func EnvParseDotenv(_ context.Context, content string) (map[string]string, error)
- func EnvReadDotenv(ctx context.Context, path string) (map[string]string, error)
- func EnvRequire(ctx context.Context, name string) (string, error)
- func EnvSet(ctx context.Context, name, value string) error
- func EnvUnset(ctx context.Context, name string) error
- func FieldFuncName(f Field) string
- func FieldResolverPackage(f Field) (importPath, pkgIdent string)
- func FieldResolverTakesCtx(f Field) bool
- func FmtSprintf(_ context.Context, format string, args ...string) (string, error)
- func FsAppendFile(ctx context.Context, path, content string) error
- func FsBasename(_ context.Context, path string) (string, error)
- func FsChmod(ctx context.Context, path string, mode int) error
- func FsCopyDir(ctx context.Context, src, dst string) error
- func FsCopyFile(ctx context.Context, src, dst string) error
- func FsDirname(_ context.Context, path string) (string, error)
- func FsExists(ctx context.Context, path string) (bool, error)
- func FsExt(_ context.Context, path string) (string, error)
- func FsGlob(ctx context.Context, pattern string) ([]types.Path, error)
- func FsIsDir(ctx context.Context, path string) (bool, error)
- func FsIsFile(ctx context.Context, path string) (bool, error)
- func FsJoin(_ context.Context, parts ...string) (string, error)
- func FsListDir(ctx context.Context, path string) ([]string, error)
- func FsMkdirAll(ctx context.Context, path string, perm int) error
- func FsReadFile(ctx context.Context, path string) (string, error)
- func FsReadLines(ctx context.Context, path string) ([]string, error)
- func FsReadlink(ctx context.Context, path string) (string, error)
- func FsRemove(ctx context.Context, path string) error
- func FsRemoveAll(ctx context.Context, path string) error
- func FsRename(ctx context.Context, src, dst string) error
- func FsSize(ctx context.Context, path string) (int, error)
- func FsStat(ctx context.Context, path string) (types.FileInfo, error)
- func FsSymlink(ctx context.Context, target, link string) error
- func FsTempDir(ctx context.Context, prefix string) (string, error)
- func FsTempFile(ctx context.Context, prefix string) (string, error)
- func FsWalk(ctx context.Context, root string, cb Callback) error
- func FsWatch(ctx context.Context, paths []string, cb Callback) error
- func FsWriteFile(ctx context.Context, path string, content string) error
- func FsWriteFileAtomic(ctx context.Context, path string, content string) error
- func FsWriteLines(ctx context.Context, path string, lines []string) error
- func HTTPDownload(ctx context.Context, url, dest string, headers map[string]string, ...) (int, error)
- func HTTPGet(ctx context.Context, url string, headers map[string]string, ...) (types.HTTPResponse, error)
- func HTTPPost(ctx context.Context, url, body string, headers map[string]string, ...) (types.HTTPResponse, error)
- func HTTPRequest(ctx context.Context, method, url, body string, headers map[string]string, ...) (types.HTTPResponse, error)
- func HTTPServe(ctx context.Context, opts map[string]any) (int, error)
- func HostPlatform() (osName, arch, variant string)
- func LogAt(ctx context.Context, level, message string, attrs map[string]any) error
- func LogDebug(ctx context.Context, message string, attrs map[string]any) error
- func LogError(ctx context.Context, message string, attrs map[string]any) error
- func LogInfo(ctx context.Context, message string, attrs map[string]any) error
- func LogTrace(ctx context.Context, message string, attrs map[string]any) error
- func LogWarn(ctx context.Context, message string, attrs map[string]any) error
- func MagusAffected(ctx context.Context, base string) (types.AffectedResult, error)
- func MagusAffectedImpact(ctx context.Context, base string, opts map[string]any) (types.ImpactResult, error)
- func MagusAttention(ctx context.Context, args []string, opts map[string]any) (map[string]any, error)
- func MagusBustCache(ctx context.Context, projectPath string) error
- func MagusClearLedger(ctx context.Context) (int, error)
- func MagusCmd(ctx context.Context, sub string, args []string, opts map[string]any) (types.ExecResult, error)
- func MagusDescribe(ctx context.Context, args []string, opts map[string]any) (types.ExecResult, error)
- func MagusDescribeFile(ctx context.Context, paths []string, opts map[string]any) (types.FileReport, error)
- func MagusDiagnoseDrift(ctx context.Context, outputs, inputs []string) (types.DriftResult, error)
- func MagusDiff(ctx context.Context, opts map[string]any) (types.Diff, error)
- func MagusDoctor(ctx context.Context, args []string, opts map[string]any) (types.DoctorReport, error)
- func MagusGraph(ctx context.Context) (types.GraphView, error)
- func MagusHasCharm(ctx context.Context, name string) (bool, error)
- func MagusInsight(ctx context.Context, opts map[string]any) (types.InsightReport, error)
- func MagusListLedger(ctx context.Context) (types.LeaseReport, error)
- func MagusProjects(ctx context.Context) (types.ProjectsOutput, error)
- func MagusPutLedger(ctx context.Context, id string, opts map[string]any) (types.Lease, error)
- func MagusRaise(_ context.Context, code, message string, opts map[string]any) error
- func MagusRegisterLedger(ctx context.Context, id, base string) (types.Lease, string, error)
- func MagusRun(ctx context.Context, args []string, opts map[string]any) (types.ExecResult, error)
- func MagusTargets(ctx context.Context, opts map[string]any) (types.TargetGraphOutput, error)
- func MagusWhere(ctx context.Context, dir string) (string, error)
- func MarkdownFrontmatter(_ context.Context, source string) (string, error)
- func MarkdownStripFrontmatter(_ context.Context, source string) (string, error)
- func MarkdownToHTML(_ context.Context, source string) (string, error)
- func MathClamp(_ context.Context, x, lo, hi float64) (float64, error)
- func MathMax(_ context.Context, nums []float64) (float64, error)
- func MathMean(ctx context.Context, nums []float64) (float64, error)
- func MathMedian(_ context.Context, nums []float64) (float64, error)
- func MathMin(_ context.Context, nums []float64) (float64, error)
- func MathRound(_ context.Context, x float64, places int) (float64, error)
- func MathSum(_ context.Context, nums []float64) (float64, error)
- func MathTrunc(_ context.Context, x float64) (float64, error)
- func MethodFuncName(m Method) string
- func MethodImplPackage(m Method) (importPath, pkgIdent string)
- func MethodSource(m Method, repoRoot string) (string, int)
- func NetFreePort(ctx context.Context) (int, error)
- func NetIsPortOpen(ctx context.Context, host string, port int) (bool, error)
- func NetWaitForPort(ctx context.Context, host string, port, timeoutMs int) (bool, error)
- func OsExec(ctx context.Context, cmd string, args []string, dir string, ...) (types.ExecResult, error)
- func OsExecutable(_ context.Context) (string, error)
- func OsExit(ctx context.Context, code int) error
- func OsHostname(_ context.Context) (string, error)
- func OsNumCPU(_ context.Context) (int, error)
- func OsPlatform(_ context.Context) (string, string, string, error)
- func OsRetry(ctx context.Context, max int, fn Callback, opts map[string]any) (any, error)
- func OsShell(_ context.Context, line string, shell string) (types.ShellCommand, error)
- func OsSleep(ctx context.Context, ms float64) error
- func OsStdinIsTerminal(_ context.Context) (bool, error)
- func OsWhich(_ context.Context, cmd string) (string, error)
- func OsWithEnv(ctx context.Context, env map[string]string, cb Callback) error
- func OsWithSlots(ctx context.Context, n int, cb Callback) error
- func PathAbs(ctx context.Context, path string) (string, error)
- func PathClean(_ context.Context, path string) (string, error)
- func PathExpandUser(_ context.Context, path string) (string, error)
- func PathIsAbs(_ context.Context, path string) (bool, error)
- func PathMatch(_ context.Context, pattern, path string) (bool, error)
- func PathMatchAny(ctx context.Context, patterns []string, path string) (bool, error)
- func PathRel(_ context.Context, base, target string) (string, error)
- func PlatformArch(_ context.Context, name, style string) (string, error)
- func PlatformCPUs(_ context.Context) (int, error)
- func PlatformMemory(ctx context.Context) (int, error)
- func PlatformOS(_ context.Context, name, style string) (string, error)
- func Register(m Module)
- func RegisterSource(m SourceModule)
- func SemverCanonical(_ context.Context, v string) (string, error)
- func SemverCompare(_ context.Context, a, b string) (int, error)
- func SemverIsValid(_ context.Context, v string) (bool, error)
- func SemverMajor(_ context.Context, v string) (string, error)
- func SemverMajorMinor(_ context.Context, v string) (string, error)
- func SemverNext(_ context.Context, v string) (types.SemverNext, error)
- func SemverParse(_ context.Context, v string) (types.SemverVersion, error)
- func SemverSatisfies(_ context.Context, v, constraint string) (bool, error)
- func SortNatural(_ context.Context, items []string) ([]string, error)
- func SortSemver(_ context.Context, items []string) ([]string, error)
- func SortStrings(_ context.Context, items []string) ([]string, error)
- func StringsCamelCase(_ context.Context, s string) (string, error)
- func StringsCapitalize(_ context.Context, s string) (string, error)
- func StringsCollapseWs(_ context.Context, s string) (string, error)
- func StringsCompare(_ context.Context, a, b string) (int, error)
- func StringsContains(_ context.Context, s, substr string) (bool, error)
- func StringsEllipsis(_ context.Context, s string, length int) (string, error)
- func StringsFields(_ context.Context, s string) ([]string, error)
- func StringsKebabCase(_ context.Context, s string) (string, error)
- func StringsLines(_ context.Context, s string) ([]string, error)
- func StringsPadLeft(_ context.Context, s string, length int, pad string) (string, error)
- func StringsPadRight(_ context.Context, s string, length int, pad string) (string, error)
- func StringsPascalCase(_ context.Context, s string) (string, error)
- func StringsSnakeCase(_ context.Context, s string) (string, error)
- func StringsSplitN(_ context.Context, s, sep string, n int) ([]string, error)
- func StringsTrimPrefix(_ context.Context, s, prefix string) (string, error)
- func StringsTrimSuffix(_ context.Context, s, suffix string) (string, error)
- func StringsUpperFirst(_ context.Context, s string) (string, error)
- func StringsWords(_ context.Context, s string) ([]string, error)
- func TemplateRender(_ context.Context, tmpl string, data any) (string, error)
- func TemplateRenderPartials(_ context.Context, tmpl string, data any, partials map[string]string) (string, error)
- func TermClearScreen(_ context.Context) error
- func TermColorize(_ context.Context, s, style string) (string, error)
- func TermIsInteractive(_ context.Context) (bool, error)
- func TermNotify(ctx context.Context, message, level string, ttlMs int) error
- func TermPick(ctx context.Context, items []string, prompt, initialFilter string, ...) (int, error)
- func TermSizeOf(_ context.Context) (types.TermSize, error)
- func TermWantsColor(_ context.Context) (bool, error)
- func TimeAdd(_ context.Context, unixMillis float64, duration string) (float64, error)
- func TimeDiff(_ context.Context, a, b float64) (float64, error)
- func TimeFormat(_ context.Context, layout string, unixMillis float64) (string, error)
- func TimeNowISO(_ context.Context) (string, error)
- func TimeParse(_ context.Context, layout, value string) (float64, error)
- func TimeParseDuration(_ context.Context, duration string) (float64, error)
- func UUIDRandomHex(_ context.Context, n int) (string, error)
- func UUIDRandomToken(_ context.Context, n int) (string, error)
- func UUIDv4(_ context.Context) (string, error)
- func UUIDv7(_ context.Context) (string, error)
- func ValidateModule(m Module) error
- func VcsBase(ctx context.Context) (string, error)
- func VcsChangedFiles(ctx context.Context, base string) ([]types.Path, error)
- func VcsCmd(ctx context.Context, args []string, opts map[string]any) (types.ExecResult, error)
- func VcsCommit(ctx context.Context, rev string) (types.Commit, error)
- func VcsDescribe(ctx context.Context) (string, error)
- func VcsDirtyDiff(ctx context.Context, paths []string) (string, error)
- func VcsHistory(ctx context.Context, limit int) ([]types.Commit, error)
- func VcsIsDirty(ctx context.Context, paths []string) (bool, error)
- func VcsName(ctx context.Context) (string, error)
- func VcsRef(ctx context.Context) (string, error)
- func VcsRoot(ctx context.Context) (string, error)
- func VcsStatus(ctx context.Context, paths []string) (types.Status, error)
- func VcsTags(ctx context.Context, pattern string) ([]types.VCSTag, error)
- func WithCwd(ctx context.Context, dir string) context.Context
- type Analyzer
- type Arg
- type Callback
- type Field
- type Method
- type Module
- type Namespace
- type Ret
- type SourceModule
- type StatusError
- type TypeTag
Constants ¶
const SignEd25519 = "ed25519"
SignEd25519 is the only algorithm signing accepts today.
Variables ¶
var Archive = Module{ Name: "archive", Doc: "Archive creation and extraction with automatic format detection. Supports tar, zip, tar.gz, tar.bz2, tar.xz, and tar.zst. Symlinks and non-regular entries are skipped.", Methods: []Method{ { Name: "uncompress", Doc: "Extract the archive at src into dest. Returns a table with fields: files (extracted paths relative to dest) and bytes (total uncompressed bytes written). opts keys: strip (int, strip N leading path components), max_size (int, uncompressed byte cap, default 10 GiB), threads (int, parallel decode workers; 0 or omitted = auto).", Args: []Arg{ {Name: "src", Type: TypeString}, {Name: "dest", Type: TypeString}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "UncompressResult"}}, Raises: true, Impl: ArchiveUncompress, }, { Name: "compress", Doc: "Create an archive at dest from src (a file or directory). Format is inferred from dest extension (.tar, .tar.gz, .tgz, .tar.zst, .zip). Returns a table with fields: files (archived paths relative to src), bytes_in (raw bytes read), bytes_out (compressed bytes written). opts keys: format (string, override format detection), threads (int, parallel encode workers; 0 or omitted = auto), level (int, compression level; -1 = format default), follow_symlinks (bool, default false), max_size (int, output byte cap, default 10 GiB).", Args: []Arg{ {Name: "src", Type: TypeString}, {Name: "dest", Type: TypeString}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "CompressResult"}}, Raises: true, Impl: ArchiveCompress, }, { Name: "list", Doc: "List the archive at src without extracting it, as entries of {name, size, is_dir} sorted by name. Use it to check what an archive holds - that a release tarball carries the binary you expect, or how large an entry is - before paying to extract it. Symlinks and devices are skipped, matching uncompress. opts keys: threads (int, parallel decode workers; 0 or omitted = auto).", Args: []Arg{ {Name: "src", Type: TypeString}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAny, Object: "[ArchiveEntry]"}}, Raises: true, Impl: ArchiveList, }, { Name: "read_file", Doc: "Return the contents of the single entry named name inside the archive at src, without extracting anything to disk. Reading one manifest out of a release tarball is the usual reason. Raises when name is not in the archive, or when the entry exceeds max_size. opts keys: max_size (int, byte cap on the entry, default 10 GiB), threads (int, parallel decode workers; 0 or omitted = auto).", Args: []Arg{ {Name: "src", Type: TypeString}, {Name: "name", Type: TypeString}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: ArchiveReadFile, }, }, }
var Charm = Module{ Name: "charm", WASM: true, Doc: "Constructors for charm values: RFC 6902 JSON Patches over a target's argv (see docs/charms.md).", Methods: []Method{ { Name: "append", Doc: "Append vals to the end of the argv.", Args: []Arg{{Name: "vals", Type: TypeStringSlice}}, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmAppend, }, { Name: "prepend", Doc: "Insert vals at the front of the argv, in order.", Args: []Arg{{Name: "vals", Type: TypeStringSlice}}, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmPrepend, }, { Name: "after", Doc: "Insert vals immediately after the first argv element equal to anchor.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}, {Name: "vals", Type: TypeStringSlice}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmAfter, }, { Name: "before", Doc: "Insert vals immediately before the first argv element equal to anchor.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}, {Name: "vals", Type: TypeStringSlice}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmBefore, }, { Name: "set", Doc: "Replace the first argv element equal to anchor with val.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}, {Name: "val", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmSet, }, { Name: "drop", Doc: "Drop (remove) the first argv element equal to anchor.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmDrop, }, { Name: "after_func", Doc: "Insert vals after the first argv element for which fn(s) is truthy.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}, {Name: "vals", Type: TypeStringSlice}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmAfterFunc, }, { Name: "before_func", Doc: "Insert vals before the first argv element for which fn(s) is truthy.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}, {Name: "vals", Type: TypeStringSlice}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmBeforeFunc, }, { Name: "set_func", Doc: "Replace the first argv element for which fn(s) is truthy with val.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}, {Name: "val", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmSetFunc, }, { Name: "drop_func", Doc: "Drop (remove) the first argv element for which fn(s) is truthy.", Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmDropFunc, }, { Name: "path", Doc: `Return the JSON Pointer ("/N") of the first argv element equal to anchor - the index, auto-calculated, for hand-built move/copy/test ops.`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeString}}, Impl: CharmPath, }, { Name: "path_func", Doc: `Return the JSON Pointer ("/N") of the first argv element for which fn(s) is truthy.`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}}, Raises: true, Returns: []Ret{{Type: TypeString}}, Impl: CharmPathFunc, }, { Name: "move", Doc: `Move the first argv element equal to anchor to the JSON Pointer to ("/-" end, "/0" front, or charm.path(...)).`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}, {Name: "to", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmMove, }, { Name: "move_func", Doc: `Move the first argv element for which fn(s) is truthy to the JSON Pointer to.`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}, {Name: "to", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmMoveFunc, }, { Name: "copy", Doc: `Copy the first argv element equal to anchor to the JSON Pointer to ("/-" end, "/0" front, or charm.path(...)).`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}, {Name: "to", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmCopy, }, { Name: "copy_func", Doc: `Copy the first argv element for which fn(s) is truthy to the JSON Pointer to.`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}, {Name: "to", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmCopyFunc, }, { Name: "test", Doc: `Guard: assert the first argv element equal to anchor is still at its position when the patch applies (else the run errors).`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "anchor", Type: TypeString}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmTest, }, { Name: "test_func", Doc: `Guard: assert the first argv element for which fn(s) is truthy is still at its position when the patch applies.`, Args: []Arg{{Name: "argv", Type: TypeStringSlice}, {Name: "fn", Type: TypeFunc}}, Raises: true, Returns: []Ret{{Type: TypeAnyMap, Object: "Charm"}}, Impl: CharmTestFunc, }, }, }
Charm is magus.extra.charm: the constructor set for the values a spell target lists under `charms`. Each constructor returns an RFC 6902 JSON Patch (the { ops = [...] } record Decode reads) over the target's base argv, treated as a JSON array of strings. Anchors are resolved to numeric pointers here, at spell author/load time, so the stored patch is pure positional RFC 6902. The patches of the active charms are concatenated and applied at run time (fork.go).
var Crypto = Module{ Name: "crypto", WASM: true, Doc: "Content digests (SHA-256/512; SHA-1 and MD5 for legacy-checksum interop) and Ed25519 signing.", Methods: []Method{ { Name: "sha256_hex", Doc: "Return the lowercase hex SHA-256 digest of data.", Args: []Arg{{Name: "data", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: CryptoSha256Hex, }, { Name: "sha256_file", Doc: "Return the lowercase hex SHA-256 digest of the file at path.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoSha256File, }, { Name: "sha512_hex", Doc: "Return the lowercase hex SHA-512 digest of data.", Args: []Arg{{Name: "data", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: CryptoSha512Hex, }, { Name: "sha512_file", Doc: "Return the lowercase hex SHA-512 digest of the file at path.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoSha512File, }, { Name: "sha1_hex", Doc: "Return the lowercase hex SHA-1 digest of data. For interop with legacy/git checksums only - SHA-1 is not collision-resistant; use sha256 for anything security-relevant.", Args: []Arg{{Name: "data", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: CryptoSha1Hex, }, { Name: "sha1_file", Doc: "Return the lowercase hex SHA-1 digest of the file at path. For interop with legacy/git checksums only - SHA-1 is not collision-resistant; use sha256 for anything security-relevant.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoSha1File, }, { Name: "sign", Doc: "Sign data with the private key in the named environment variable and return the lowercase hex signature. alg is \"ed25519\". " + "The key is NAMED, never passed: a value that never enters Buzz cannot be interpolated into a log.", Args: []Arg{{Name: "alg", Type: TypeString, Enum: "SignAlgorithm"}, {Name: "data", Type: TypeString}, {Name: "key_env", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoSign, }, { Name: "sign_file", Doc: "Sign the file at path, write the detached signature to path + \".sig\", and return the lowercase hex signature. alg is \"ed25519\".", Args: []Arg{{Name: "alg", Type: TypeString, Enum: "SignAlgorithm"}, {Name: "path", Type: TypeString}, {Name: "key_env", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoSignFile, }, { Name: "verify", Doc: "Report whether sig_hex is a valid signature over data for the hex public key pub_hex. alg is \"ed25519\".", Args: []Arg{{Name: "alg", Type: TypeString, Enum: "SignAlgorithm"}, {Name: "data", Type: TypeString}, {Name: "sig_hex", Type: TypeString}, {Name: "pub_hex", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: CryptoVerify, }, { Name: "public_key", Doc: "Return the lowercase hex PUBLIC key for the private key in the named environment variable, so a publisher can print what its readers must pin. alg is \"ed25519\".", Args: []Arg{{Name: "alg", Type: TypeString, Enum: "SignAlgorithm"}, {Name: "key_env", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoPublicKey, }, { Name: "md5_hex", Doc: "Return the lowercase hex MD5 digest of data. For interop with legacy checksum manifests only - MD5 is broken; use sha256 for anything security-relevant.", Args: []Arg{{Name: "data", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: CryptoMd5Hex, }, { Name: "md5_file", Doc: "Return the lowercase hex MD5 digest of the file at path. For interop with legacy checksum manifests only - MD5 is broken; use sha256 for anything security-relevant.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: CryptoMd5File, }, { Name: "hmac_sha256", Doc: "Return the raw HMAC-SHA256 of data keyed by key, as a BYTE LIST. key and data may each be a str or a byte list, which is what lets one result key the next call - the shape an AWS SigV4 signing chain needs (kDate to kRegion to kService to kSigning). Use hmac_sha256_hex for the final signature you actually send.", Args: []Arg{ {Name: "key", Type: TypeByteSlice}, {Name: "data", Type: TypeByteSlice}, }, Returns: []Ret{{Type: TypeByteSlice}}, Impl: CryptoHmacSha256, }, { Name: "hmac_sha256_hex", Doc: "Return the lowercase hex HMAC-SHA256 of data keyed by key - the form a signature header carries, once the signing key has been derived with hmac_sha256.", Args: []Arg{ {Name: "key", Type: TypeByteSlice}, {Name: "data", Type: TypeByteSlice}, }, Returns: []Ret{{Type: TypeString}}, Impl: CryptoHmacSha256Hex, }, { Name: "base64_encode_bytes", Doc: "Encode raw bytes as standard (padded) base64. The byte-list counterpart to encoding/base64's encode, for data that came from another byte-level call and must not round-trip through a rune-oriented str.", Args: []Arg{{Name: "data", Type: TypeByteSlice}}, Returns: []Ret{{Type: TypeString}}, Impl: CryptoBase64EncodeBytes, }, { Name: "base64_decode_bytes", Doc: "Decode standard (padded) base64 into a byte list; errors on invalid input. Returns bytes rather than a str so arbitrary binary survives - a decoded key or archive would not.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeByteSlice}}, Raises: true, Impl: CryptoBase64DecodeBytes, }, }, }
Crypto is the "crypto" host module: content digests for checksum manifests (SHA256SUMS for release assets), Ed25519 signing, and HMAC. Not a general crypto toolkit - there is no encryption. SHA-256/512 are the strong defaults; SHA-1 and MD5 exist for interop with legacy checksums and are not collision-resistant - never use them for anything security-relevant.
Signing is here so a magusfile can publish a signed artifact without a Go program in the middle. Two things about its shape:
The ALGORITHM is a parameter rather than part of the method name, so a second algorithm is a case in signAlgorithm rather than four more methods. checkAlg is the runtime half, rejecting anything but SignEd25519 by name.
The KEY is named by environment variable rather than passed as a value: a key that never becomes a Buzz string cannot be interpolated into a log line, an error, or a captured run output.
var Diff = Module{ Name: "diff", WASM: true, Doc: "Unified line diffs, for reporting what drifted rather than only that something did.", Methods: []Method{ { Name: "unified", Doc: "Return a unified diff of a and b, or \"\" when they are identical - so the result doubles as the check. from_label and to_label name the two sides in the +++/--- header (default \"a\" and \"b\"); pass the file path and something like \"regenerated\" to make a drift report read like a patch. context is the unchanged lines kept around each hunk, defaulting to 3 as git does.", Args: []Arg{ {Name: "a", Type: TypeString}, {Name: "b", Type: TypeString}, {Name: "from_label", Type: TypeString, Optional: true}, {Name: "to_label", Type: TypeString, Optional: true}, {Name: "context", Type: TypeInt, Optional: true}, }, Returns: []Ret{{Type: TypeString}}, Impl: DiffUnified, }, { Name: "equal", Doc: "Report whether a and b are identical after normalizing line endings and a single trailing newline. It is the comparison a drift gate wants: a file that differs only by CRLF or by whether the last line is newline-terminated has not drifted in any sense a reader cares about, and a bare == would say it had.", Args: []Arg{ {Name: "a", Type: TypeString}, {Name: "b", Type: TypeString}, }, Returns: []Ret{{Type: TypeBool}}, Impl: DiffEqual, }, { Name: "stat", Doc: "Return a one-line summary of the change: \"3 added, 1 removed\", or \"\" when a and b are identical. For a report that wants the shape of a drift without the whole patch - a summary line per file, with unified reserved for the one the reader drills into.", Args: []Arg{ {Name: "a", Type: TypeString}, {Name: "b", Type: TypeString}, }, Returns: []Ret{{Type: TypeString}}, Impl: DiffStat, }, }, }
Diff is the "diff" host module: compare two texts and say what changed.
magus is in the drift business - a generate target is a drift gate, `magus affected ci` fails when committed output no longer matches regenerated output - and until now the language those gates are written in could not say WHAT differed. A magusfile could report "archive.md is out of date" and nothing more, which leaves the reader to regenerate locally and diff by hand to find out whether the change is theirs or a tool-version artifact.
Unified format specifically, rather than a bespoke rendering: it is what git, `diff -u`, every code review tool and every developer already reads, and it pastes into an issue unchanged.
LINE-based, not character-based. A build tool compares generated files, manifests and command output, all of which are line-oriented; a character-level diff of a 4000-line generated file is unreadable and costs far more to compute.
var Env = Module{ Name: "env", WASM: true, Doc: "Process environment variable access.", Methods: []Method{ { Name: "get", Doc: "Return the value of name, or \"\" if unset. Use lookup to tell unset from set-but-empty.", Args: []Arg{{Name: "name", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: EnvGet, }, { Name: "lookup", Doc: "Return (value, found); found is false when name is unset or stripped by the sandbox.", Args: []Arg{{Name: "name", Type: TypeString}}, Returns: []Ret{{Type: TypeString}, {Type: TypeBool}}, Impl: EnvLookup, }, { Name: "set", Doc: "Set name to value in the current process environment.", Args: []Arg{{Name: "name", Type: TypeString}, {Name: "value", Type: TypeString}}, Returns: nil, Raises: true, Impl: EnvSet, }, { Name: "list", Doc: "Return all environment variables as a name->value map.", Args: nil, Returns: []Ret{{Type: TypeStringMap}}, Impl: EnvList, }, { Name: "unset", Doc: "Remove name from the current process environment.", Args: []Arg{{Name: "name", Type: TypeString}}, Returns: nil, Raises: true, Impl: EnvUnset, }, { Name: "expand", Doc: "Replace $VAR and ${VAR} references in s with their values (sandbox-stripped names expand to \"\").", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: EnvExpand, }, { Name: "home", Doc: "Return the current user's home directory.", Args: nil, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: EnvHome, }, { Name: "get_or", Doc: "Return the value of name, or def when name is unset or stripped by the sandbox. Unlike get, an empty string is returned as-is - def only applies when the variable is absent.", Args: []Arg{{Name: "name", Type: TypeString}, {Name: "def", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: EnvGetOr, }, { Name: "require", Doc: "Return the value of name, or raise when it is unset or stripped by the sandbox. The fail-fast complement to get/lookup: a CI magusfile that needs GITHUB_TOKEN states the requirement once instead of threading a lookup-then-fatal check through every caller. A set-but-empty variable satisfies the requirement (its empty value is returned).", Args: []Arg{{Name: "name", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: EnvRequire, }, { Name: "parse_dotenv", Doc: "Parse .env-format content into a name->value map. Supports KEY=VALUE, blank lines, # comments, a leading `export` keyword, single/double quotes (double-quoted values honor \\n \\t \\\" \\\\ escapes), and inline comments after unquoted values. Pure: it does not touch the process environment.", Args: []Arg{{Name: "content", Type: TypeString}}, Returns: []Ret{{Type: TypeStringMap}}, Impl: EnvParseDotenv, }, { Name: "read_dotenv", Doc: "Read a .env file and return its name->value map (parse_dotenv over the file contents). Errors if the file cannot be read.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeStringMap}}, Raises: true, Impl: EnvReadDotenv, }, { Name: "load_dotenv", Doc: "Read a .env file and set each variable in the process environment, without overwriting names already set (the dotenv convention) or names the sandbox strips. A no-op in a recording/dry-run.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: nil, Raises: true, Impl: EnvLoadDotenv, }, }, }
Env is the "env" host module: process environment-variable access, filtered by the sandbox policy.
var Fmt = Module{ Name: "fmt", WASM: true, Doc: "String formatting (printf-style).", Methods: []Method{ { Name: "sprintf", Doc: "Format string args into the template using Go printf verbs (e.g. %s, %q). Returns the formatted string.", Args: []Arg{{Name: "format", Type: TypeString}, {Name: "args", Type: TypeString, Variadic: true}}, Returns: []Ret{{Type: TypeString}}, Impl: FmtSprintf, }, }, }
Fmt is the "fmt" host module: string formatting via Go's printf verbs. It exists to collapse long "+"-concatenation chains into a single readable call. Args are strings (the variadic boundary only carries strings), so use %s/%q - numeric verbs like %d have nothing typed to act on.
var Fs = Module{ Name: "fs", Doc: "Filesystem and path primitives.", Methods: []Method{ { Name: "glob", Doc: "Return paths matching pattern (doublestar-style).", Args: []Arg{{Name: "pattern", Type: TypeString}}, Returns: []Ret{{Type: TypeAny, Object: "[Path]"}}, Raises: true, Impl: FsGlob, }, { Name: "dirname", Doc: "Directory portion of path.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: FsDirname, }, { Name: "basename", Doc: "Final element of path.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: FsBasename, }, { Name: "exists", Doc: "True iff path exists.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: FsExists, }, { Name: "read_file", Doc: "Return the contents of path as a string.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: FsReadFile, }, { Name: "write_file", Doc: "Write content to path (mode 0644).", Args: []Arg{{Name: "path", Type: TypeString}, {Name: "content", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsWriteFile, }, { Name: "mkdir_all", Doc: "Create path and parents (default mode 0755).", Args: []Arg{ {Name: "path", Type: TypeString}, {Name: "perm", Type: TypeInt, Optional: true, Default: int(0o755)}, }, Returns: nil, Raises: true, Impl: FsMkdirAll, }, { Name: "join", Doc: "Join path elements with the OS separator.", Args: []Arg{{Name: "parts", Type: TypeString, Variadic: true}}, Returns: []Ret{{Type: TypeString}}, Impl: FsJoin, }, { Name: "remove_all", Doc: "Recursively remove path (no error if missing).", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsRemoveAll, }, { Name: "remove", Doc: "Remove a single file or empty directory (no error if missing). Unlike remove_all it refuses a non-empty directory, so a wrong path costs one error rather than a recursive delete.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsRemove, }, { Name: "rename", Doc: "Move or rename src to dst, creating dst's parent directory if needed. Within one filesystem this is atomic, which is what makes it the last step of a write-to-temp-then-swap. Across filesystems the underlying rename fails rather than silently copying; copy_file plus remove is the explicit form for that.", Args: []Arg{{Name: "src", Type: TypeString}, {Name: "dst", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsRename, }, { Name: "size", Doc: "Return path's size in bytes. Raises when path does not exist; stat returns the whole FileInfo when more than the size is wanted.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeInt}}, Raises: true, Impl: FsSize, }, { Name: "temp_file", Doc: "Create a new empty temporary file (in os.TempDir()) with an optional name prefix and return its path. The file is left in place for the caller to write and remove; temp_dir is the form for a whole tree.", Args: []Arg{ {Name: "prefix", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: FsTempFile, }, { Name: "write_file_atomic", Doc: "Write content to path so a reader sees either the old bytes or the new ones, never a partial file: the content goes to a temporary file in the same directory, is flushed to disk, then renamed over path. Use it for anything another process may read while a target runs - a generated file, a lockfile, a cache index. write_file is the cheaper form when nothing else is looking.", Args: []Arg{ {Name: "path", Type: TypeString}, {Name: "content", Type: TypeString}, }, Returns: nil, Raises: true, Impl: FsWriteFileAtomic, }, { Name: "list_dir", Doc: "Return directory entries; empty if path does not exist.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeStringSlice}}, Raises: true, Impl: FsListDir, }, { Name: "ext", Doc: "File-name extension of path, including the leading dot (\"\" if none).", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: FsExt, }, { Name: "is_dir", Doc: "True iff path exists and is a directory. A sandbox-denied path raises rather than reading as false.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: FsIsDir, }, { Name: "is_file", Doc: "True iff path exists and is a regular file. A sandbox-denied path raises rather than reading as false.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: FsIsFile, }, { Name: "stat", Doc: "Return metadata for path as {size, mtime, mode, is_dir}: size in bytes, mtime as Unix millis, mode as the integer permission bits. Errors if path is missing.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeAnyMap, Object: "FileInfo"}}, Raises: true, Impl: FsStat, }, { Name: "copy_file", Doc: "Copy the file at src to dst (overwriting), preserving its permission bits.", Args: []Arg{{Name: "src", Type: TypeString}, {Name: "dst", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsCopyFile, }, { Name: "copy_dir", Doc: "Recursively copy the directory tree at src to dst, preserving permission bits.", Args: []Arg{{Name: "src", Type: TypeString}, {Name: "dst", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsCopyDir, }, { Name: "watch", Doc: "Blocking. Watch paths (directories, recursively) and call callback with each debounced batch of changed paths until the callback returns true or the run is interrupted.", Args: []Arg{ {Name: "paths", Type: TypeStringSlice}, {Name: "callback", Type: TypeFunc}, }, Returns: nil, Raises: true, Impl: FsWatch, }, { Name: "walk", Doc: "Recursively walk the directory tree rooted at root, calling callback(path, is_dir) for each entry. Return true from callback to stop the walk early. Sandbox-denied entries are silently skipped.", Args: []Arg{ {Name: "root", Type: TypeString}, {Name: "callback", Type: TypeFunc}, }, Returns: nil, Raises: true, Impl: FsWalk, }, { Name: "append_file", Doc: "Append content to path (creating if absent, mode 0644).", Args: []Arg{{Name: "path", Type: TypeString}, {Name: "content", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsAppendFile, }, { Name: "chmod", Doc: "Change the permission bits of path to mode (octal integer, e.g. 0755).", Args: []Arg{{Name: "path", Type: TypeString}, {Name: "mode", Type: TypeInt}}, Returns: nil, Raises: true, Impl: FsChmod, }, { Name: "symlink", Doc: "Create a symbolic link at link pointing to target.", Args: []Arg{{Name: "target", Type: TypeString}, {Name: "link", Type: TypeString}}, Returns: nil, Raises: true, Impl: FsSymlink, }, { Name: "readlink", Doc: "Return the target of the symbolic link at path.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: FsReadlink, }, { Name: "temp_dir", Doc: "Create a new temporary directory (in os.TempDir()) with an optional name prefix and return its path.", Args: []Arg{ {Name: "prefix", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: FsTempDir, }, { Name: "read_lines", Doc: "Read path and return its lines as a list, with the line terminators stripped. A single trailing newline yields no extra empty element; an empty file yields an empty list.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeStringSlice}}, Raises: true, Impl: FsReadLines, }, { Name: "write_lines", Doc: "Write lines to path (mode 0644), each followed by a newline. The companion to read_lines: write_lines(p, read_lines(p)) round-trips a newline-terminated file.", Args: []Arg{{Name: "path", Type: TypeString}, {Name: "lines", Type: TypeStringSlice}}, Returns: nil, Raises: true, Impl: FsWriteLines, }, }, }
Fs is the "fs" host module: filesystem and path primitives.
var HTTP = Module{ Name: "http", Doc: "HTTP client. Requests run ONCE unless given a retry policy.", Methods: []Method{ { Name: "get", Doc: "Send a GET request; returns {status, body, headers}." + optsDoc, Args: []Arg{ {Name: "url", Type: TypeString}, {Name: "headers", Type: TypeStringMap, Optional: true}, {Name: "opts", Type: TypeAnyMap, Optional: true}, {Name: "retry", Type: TypeAnyMap, Optional: true, Object: "HttpRetry"}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "HttpResponse"}}, Raises: true, Impl: HTTPGet, }, { Name: "download", Doc: "GET url and stream the response body straight to dest, returning the HTTP status. The body never becomes a Buzz string, so arbitrary binary (a release tarball, an image layer) survives intact and a large file costs no proportional memory. A non-2xx status writes nothing. Pair it with crypto.sha256_file to verify what you fetched before using it." + optsDoc, Args: []Arg{ {Name: "url", Type: TypeString}, {Name: "dest", Type: TypeString}, {Name: "headers", Type: TypeStringMap, Optional: true}, {Name: "opts", Type: TypeAnyMap, Optional: true}, {Name: "retry", Type: TypeAnyMap, Optional: true, Object: "HttpRetry"}, }, Returns: []Ret{{Type: TypeInt}}, Raises: true, Impl: HTTPDownload, }, { Name: "post", Doc: "Send a POST request with body; returns {status, body, headers}." + optsDoc, Args: []Arg{ {Name: "url", Type: TypeString}, {Name: "body", Type: TypeString}, {Name: "headers", Type: TypeStringMap, Optional: true}, {Name: "opts", Type: TypeAnyMap, Optional: true}, {Name: "retry", Type: TypeAnyMap, Optional: true, Object: "HttpRetry"}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "HttpResponse"}}, Raises: true, Impl: HTTPPost, }, { Name: "request", Doc: "Send an HTTP request; returns {status, body, headers}." + optsDoc, Args: []Arg{ {Name: "method", Type: TypeString}, {Name: "url", Type: TypeString}, {Name: "body", Type: TypeString, Optional: true}, {Name: "headers", Type: TypeStringMap, Optional: true}, {Name: "opts", Type: TypeAnyMap, Optional: true}, {Name: "retry", Type: TypeAnyMap, Optional: true, Object: "HttpRetry"}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "HttpResponse"}}, Raises: true, Impl: HTTPRequest, }, { Name: "server", Doc: "Start a static file server in the background from an options map and return the bound port. " + "opts keys: dir (string) serves a single directory; OR mounts (a map of URL-prefix -> dir, e.g. " + "{\"/\": \"docs/gen\", \"/console/\": \"console/gen\"}) serves multiple roots where a request routes to " + "the LONGEST matching prefix, so \"/console/\" wins over \"/\" for a /console/ path and the matched prefix " + "is stripped before the file lookup. Exactly one of dir or mounts is required. port (int, optional) binds " + "that port; 0 (the default) scans upward from 8080 and binds the first available one. Unknown keys are " + "rejected. Serves localhost only and runs until the process exits, so pair it with a blocking call like fs.watch.", Args: []Arg{ {Name: "opts", Type: TypeAnyMap}, }, Returns: []Ret{{Type: TypeInt}}, Raises: true, Impl: HTTPServe, }, { Name: "byteSize", Doc: "Byte length of the file at path. The companion to uploadChunked: the size a Content-Range needs, which len() on a Buzz string cannot give for binary data.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeInt}}, Raises: true, Extern: true, }, { Name: "upload_chunked", BuzzName: "upload_chunked", Doc: "Send the file at src as the request body. chunk_size > 0 sends it in slices (capped at 32 MiB), each carrying a Content-Range header - the resumable-upload convention GitHub Actions Cache and RFC 7233 servers expect; chunk_size <= 0 sends it in one request. Returns the final [status, body].", Args: []Arg{ {Name: "method", Type: TypeString}, {Name: "url", Type: TypeString}, {Name: "src", Type: TypeString}, {Name: "chunk_size", Type: TypeInt}, {Name: "headers", Type: TypeStringMap, Optional: true}, }, Returns: []Ret{{Type: TypeAny}}, Raises: true, Extern: true, }, }, }
HTTP is the "http" host module: an HTTP client with automatic retry on transient errors and curl-style per-request control over retry and failure.
Security note: outbound requests are audited but NOT blocked when the sandbox is active. There is no SSRF guard: any URL a magusfile passes is fetched, including localhost, internal services, and the cloud metadata endpoint. Only pass URLs you trust.
var Log = Module{ Name: "log", WASM: true, Doc: "Emit a message at a level through magus's own logger, so it honors -q/-v/-vv, renders in the run's format, is redacted, and is captured in the run log. Unlike std\\print, which is an uncontrolled bare line.", Methods: []Method{ { Name: "trace", Doc: "Log at trace level (shown at -vvv). For detail worth having when reconstructing a run and noise at any other time.", Args: []Arg{{Name: "message", Type: TypeString}, {Name: "attrs", Type: TypeAnyMap, Optional: true}}, Returns: nil, Impl: LogTrace, }, { Name: "debug", Doc: "Log at debug level (shown at -v).", Args: []Arg{{Name: "message", Type: TypeString}, {Name: "attrs", Type: TypeAnyMap, Optional: true}}, Returns: nil, Impl: LogDebug, }, { Name: "info", Doc: "Log at info level (shown by default, hidden by -q).", Args: []Arg{{Name: "message", Type: TypeString}, {Name: "attrs", Type: TypeAnyMap, Optional: true}}, Returns: nil, Impl: LogInfo, }, { Name: "warn", Doc: "Log at warn level: something the reader should act on eventually.", Args: []Arg{{Name: "message", Type: TypeString}, {Name: "attrs", Type: TypeAnyMap, Optional: true}}, Returns: nil, Impl: LogWarn, }, { Name: "error", Doc: "Log at error level. This RECORDS a problem; it does not fail the target - raise (or os\\exit) is what ends a run.", Args: []Arg{{Name: "message", Type: TypeString}, {Name: "attrs", Type: TypeAnyMap, Optional: true}}, Returns: nil, Impl: LogError, }, { Name: "at", Doc: "Log at a level chosen at runtime. Use it when the level is DATA rather than a literal - mapping a scanner's severity onto magus's levels, say - and the five named methods when it is not.", Args: []Arg{ {Name: "level", Type: TypeString, Enum: "LogLevel"}, {Name: "message", Type: TypeString}, {Name: "attrs", Type: TypeAnyMap, Optional: true}, }, Returns: nil, Raises: true, Impl: LogAt, }, }, }
Log is the "log" host module: a magusfile's way to say something at a level.
NOT a second logger: every method calls slog at magus's process-wide default, the one cmd/magus installs from the verbosity flags. A magusfile therefore inherits, with nothing to wire up:
- LEVEL FILTERING from -q/-v/-vv/-vvv, so log.debug costs nothing on a normal run instead of printing unconditionally.
- THE HANDLER, so a message renders in the same compact style as the rest of the run under `pretty`, and as a JSON object under --log-format=json, landing on the same stream as everything else.
- SECRET REDACTION, so a value that came from a secret provider is masked on its way out rather than at the call site.
- JOURNAL CAPTURE, so the message is in the run's persisted log and reachable later through `magus query output`.
`std\print` writes a bare line to the script output stream: it cannot be quieted or raised to a level, is not captured as a structured event, and is not redacted.
Pure compute at this layer (the handler does the I/O), so it is WASM-safe.
var Magus = Module{ Name: "magus", Doc: "Magus core primitives.\n\n" + "Three provider namespaces are wired by the runtime rather than declared here, so " + "they do not appear in the method list below: `magus\\cache.remote(<spell>)` selects " + "a remote cache provider, `magus\\ci.provider(<spell>)` a CI provider, and " + "`magus\\secret.provider(<spell>)` / `magus\\secret.read(<ref>)` a secret provider and " + "the credentials read through it. Each takes an imported spell handle. " + "`magus\\secret.endpoint(<grant>)` serves the case `read` cannot: it returns a loopback " + "base URL a CHILD PROCESS is pointed at instead of the real API, so magus attaches the " + "credential on the way upstream and the child never holds it. It takes an object with " + "ref/host/header/prefix fields, declared in your own magusfile. For your own code, " + "`read` is the ordinary choice. See " + "[Secrets](../../concepts/secrets.md), [Remote cache](../../concepts/cache/remote.md) " + "and [CI integration](../../guides/integrations/ci.md).\n\n" + "`import \"magus\"` resolves in a `magus buzz` script as well as in a magusfile, and a " + "script run inside a workspace reads that workspace: `projects`, `affected`, `projectGraph`, " + "`where` and `insight` all answer in-process, and so does `magus\\ledger` (list, put, " + "register, clear): the lease ledger an orchestrating agent declares about work it handed " + "out (see types.Lease). There is deliberately no `magus ledger` CLI " + "subcommand, so this namespace and the magus_ledger MCP tool are the only doors onto " + "it. Only the members that DECLARE into " + "the workspace being loaded (`magus\\project`, the provider selections above) raise " + "[MGS1022](../codes/magusfile/MGS1022.md) in a script - there is nothing for them to " + "declare into. Run a script outside any workspace and the reading members raise it too, " + "since there is no workspace to read. The nested-command methods (`cmd`, `run`, " + "`describe`, `doctor`) work there either way and discover the workspace themselves.", Methods: []Method{ { Name: "cmd", Doc: "Escape hatch: run `magus <sub> <args>` for a subcommand with no dedicated method (status, affected, agent, graph, ...). Its signature is the typed methods' signature with the subcommand pushed in front: magus.cmd(sub, args, [opts]) beside magus.run(args, [opts]), same argv, same opts, same ExecResult. The SUBCOMMAND is a typed argument rather than args[0] because it is the part of the invocation magus can reason about - it stays readable in the signature and greppable in the source, while the remaining argv stays free-form. Prefer the dedicated methods (run, describe, doctor) when one exists - magus.cmd warns when sub names one that has. Returns {stdout, stderr, code, ok}; raises on non-zero exit (catch for non-fatal use). opts.root sets the global --root workspace; opts.dir runs it in another directory (relative to the target's, like proc.exec); opts.quiet captures the output without echoing it to the console.", Args: []Arg{ {Name: "sub", Type: TypeString}, {Name: "args", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "ExecResult"}}, Raises: true, Impl: MagusCmd, }, { Name: "projects", Doc: "The workspace's projects: {workspace, count, projects}, each project {path, dir, spell, spells, sources, outputs, dependsOn, exclusive}. Annotate the result `> Projects` (magus's own type, no import needed) for compile-checked field access. The sibling of magus.targets(), which answers the same question one level down. Unlike magus.cmd(\"ls\") - the CLI spelling, which this member deliberately does not mirror - it reads the workspace already open on the context: no subprocess, no second workspace load, no JSON round-trip.", Args: nil, Returns: []Ret{{Type: TypeAnyMap, Object: "Projects"}}, Raises: true, Impl: MagusProjects, }, { Name: "targets", Doc: "The TARGET dependency graph of every project: {projects}, each project {path, name, engine, nodes, cycle, dependsOn} and each node {name, declared, doc, dependencies, charms, spells, crossDependencies, inputs, outputs}. Annotate the result `> TargetGraph` (magus's own type, no import needed) for compile-checked field access. This is the per-project view magus.projectGraph() does not carry: that is the project-level DAG, this is the targets inside each one. Read statically from the magusfile source, so it never runs a target body. Served in-process from the workspace on the context when there is one, and through a nested magus when there is not - so the same call works from a magusfile and from a `magus buzz` script with no workspace.", Args: []Arg{ {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "TargetGraph"}}, Raises: true, Impl: MagusTargets, }, { Name: "affected", Doc: "Compute the VCS-affected project set against base (empty uses the configured base ref): {base, changed, seed, filesBySeed, affected}. Served in-process from the workspace on the context - no subprocess. Raises when the diff cannot be computed, rather than reporting an empty set, since an empty set and an uncomputable one mean opposite things to a caller deciding what to build.", Args: []Arg{ {Name: "base", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "Affected"}}, Raises: true, Impl: MagusAffected, }, { Name: "project_graph", Doc: "The project dependency DAG as {nodes, dependsOn, blastRadius}. nodes is in TOPOLOGICAL order, so iterating it is already a valid build order; dependsOn gives each node's direct predecessors and blastRadius how many projects it can transitively affect. Served in-process from the workspace on the context - no subprocess.", Args: nil, Returns: []Ret{{Type: TypeAnyMap, Object: "Graph"}}, Raises: true, Impl: MagusGraph, }, { Name: "where", Doc: "Return the project path containing dir, or null when dir is inside no project. Served in-process from the workspace on the context - no subprocess.", Args: []Arg{ {Name: "dir", Type: TypeString}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: MagusWhere, }, { Name: "raise", Doc: "Fail with a CODED diagnostic instead of a bare string, so a caller can branch on the code: `catch (e) { if (e.code == \"ACME1001\") ... }`. code is yours to define and namespace - anything but the MGS prefix, which is magus's own. opts.cause is the error being wrapped, usually the value from an inner catch; it is appended to the message the way Go's %w renders one, and the failure it came from stays reachable underneath. opts.url is the page documenting the code, rendered as the `see:` line the CLI prints under its own diagnostics.", Args: []Arg{ {Name: "code", Type: TypeString}, {Name: "message", Type: TypeString}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: nil, Raises: true, Impl: MagusRaise, }, { Name: "run", Doc: "Run `magus run <args>` recursively in the target's project directory and capture its output. Child invocations share the parent's concurrency budget over the local socket. Returns {stdout, stderr, code, ok}; raises on non-zero exit (catch for non-fatal use). opts.root sets the global --root workspace; opts.dir runs it in another directory (relative to the target's, like proc.exec); opts.quiet captures the output without echoing it to the console.", Args: []Arg{ {Name: "args", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "ExecResult"}}, Raises: true, Impl: MagusRun, }, { Name: "describe", Doc: "Run `magus describe <args>` in the target's project directory and capture its output. Returns {stdout, stderr, code, ok}; raises on non-zero exit (catch for non-fatal use). opts.root sets the global --root workspace; opts.dir runs it in another directory (relative to the target's, like proc.exec); opts.quiet captures the output without echoing it to the console. Unlike a raw binary call, the working directory is always the contextual project dir, so a nested project describes itself, not the root workspace.", Args: []Arg{ {Name: "args", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "ExecResult"}}, Raises: true, Impl: MagusDescribe, }, { Name: "insight", Doc: "Every VCS-history lens as one typed report: {hotspots, affinity, ownership, trend, volatility, unreferenced}. Annotate the result `> InsightReport` for compile-checked field access - `r.ownership.projects` gives each project's primary author and bus-factor flag, `r.hotspots.files` the churn-by-complexity ranking, `r.volatility` the targets that flapped. Takes the window as `{commits, since}` and renders nothing - presentation is the caller's job. Read straight off the workspace already open on the context - no subprocess, no second workspace load, no JSON round-trip. Works from a magusfile target and from a `magus buzz` script run inside a workspace; raises MGS1022 only when there is no workspace to read.", Args: []Arg{ {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "InsightReport"}}, Raises: true, Impl: MagusInsight, }, { Name: "affected_impact", Doc: "The VCS-affected set and WHY each project is in it: {base, changedFileCount, changedFiles, seedProjects, affectedProjects, notes}, each affected project carrying whether it was a seed and the files that pulled it in. Annotate the result `> Impact`. This is `magus affected --impact`, a forensic mode that reports the set without running a target - unlike `magus affected list`, which dispatches a target across every affected project to answer the same question. Computed in-process from the workspace on the context, so it needs a magusfile target rather than a bare `magus buzz` script. opts.commits caps the commits scanned; opts.since bounds the window (90d, 12w, 6mo, 1y).", Args: []Arg{ {Name: "base", Type: TypeString, Optional: true}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "Impact"}}, Raises: true, Impl: MagusAffectedImpact, }, { Name: "describe_file", Doc: "Classify paths against the workspace's declared globs: for each, the owning project and whether it is a declared `output` (generated - regenerate it, never hand-edit), a declared `source` (it feeds cache keys and the affected set), `maintained` (magus writes it outside any target - commit it, never ignore it), or `unclaimed`. Returns a typed DoctorReport-style envelope {definition, count, files}, not text to re-parse: this is the question \"can I disregard this changed file\", and a caller branches on `role` rather than grepping. Runs a nested magus, so it needs no workspace on the context and works from a `magus buzz` script.", Args: []Arg{ {Name: "paths", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "FileReport"}}, Raises: true, Impl: MagusDescribeFile, }, { Name: "diff", Doc: "Read the working tree's uncommitted changes, annotated and ordered by what they can break: for each file the owning project, whether it is a declared `output` (generated - the source edit is the review), how widely its changed symbols are referenced (`reach`), whether it is public API `surface`, observed `coverage`, how often it has been changing (`churn`), and which agent sessions wrote it (`touches`). Files come back in the order magus recommends READING them - generated last whatever its reach, then widest reach first - so a caller renders the list as given rather than sorting it again. Returns a typed Diff envelope; branch on `role` and `surface` rather than grepping text. Runs a nested magus, so it needs no workspace on the context and works from a `magus buzz` script.", Args: []Arg{ {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "Diff"}}, Raises: true, Impl: MagusDiff, }, { Name: "doctor", Doc: "Validate the workspace and return what every check found: {workspace, checks, summary}, each check {name, status, message, details} with status `ok`, `fail`, or `advice` (advice is worth knowing and never a gate). Annotate the result `> DoctorReport` for compile-checked field access. A caller branches on a check's status rather than grepping console text for the word fail. It does NOT raise when a check fails: doctor exits non-zero precisely when it has something to report, and raising would discard the report. Gate on `summary.fail` instead, which says more than an exit code does. It DOES raise when the underlying `magus doctor` subprocess itself cannot be launched or its output cannot be decoded - an infrastructure failure, not a check result. opts.root sets the global --root workspace; opts.dir runs it in another directory (relative to the target's, like proc.exec).", Args: []Arg{ {Name: "args", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "DoctorReport"}}, Raises: true, Impl: MagusDoctor, }, { Name: "attention", Doc: "List the OPEN attention requests of this repository's session store: {requests, store}, each request {id, outcome, source, where, lease, message, ...} as `magus session attention -o json` reports them. Read-only by design: a magusfile may refuse to proceed while a request is open, but disposing one is a human act (see the workspace doctrine's Manual-on-purpose table), so no method here closes anything - the person runs `magus session dispose <id> -reason <text>`. Runs a nested magus, so it works from a `magus buzz` script as well as a magusfile; opts.root and opts.dir as on doctor. Raises only when the subprocess cannot run or its output cannot decode.", Args: []Arg{ {Name: "args", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap}}, Raises: true, Impl: MagusAttention, }, { Name: "diagnose_drift", Doc: "Diagnose why a generate gate's declared outputs drifted and RETURN the verdict {drifted, code, message, url, files} so the caller decides whether to fail or warn. Pass the target's output globs and (optional) input globs, project-relative. code is MGS4006 when a declared input changed (real drift, commit it), MGS4005 when the inputs are unchanged but a dev build produced differing output (version/tool skew, not your change), or MGS4003 when a release build's identical inputs still differ (a reproducibility bug). files are the drifted outputs as Paths based at the repository root. drifted is false with every field zero when the outputs are clean. It lives here rather than on vcs because choosing between those codes is magus policy; vcs only supplies the probe. Composes vcs.status; does not replace it.", Args: []Arg{ {Name: "outputs", Type: TypeStringSlice}, {Name: "inputs", Type: TypeStringSlice, Optional: true}, }, Returns: []Ret{{Type: TypeAny, Object: "DriftResult"}}, Raises: true, Impl: MagusDiagnoseDrift, }, { Name: "bust_cache", Doc: "Invalidate the build cache. Escape hatch - prefer modeling missing inputs as Sources. No arg clears all; a project path clears one project.", Args: []Arg{ {Name: "project_path", Type: TypeString, Optional: true}, }, Returns: nil, Raises: true, Impl: MagusBustCache, }, { Name: "has_charm", Doc: "True when execution charm `name` is active, letting a target body branch on a charm carried in context (e.g. has_charm(\"rw\")).", Args: []Arg{ {Name: "name", Type: TypeString}, }, Returns: []Ret{{Type: TypeBool}}, Impl: MagusHasCharm, }, { Name: "project", Doc: "Declare this directory's project: its spell, sources, outputs, and options. A magusfile calls it once at top level. Raises MGS1022 in a `magus buzz` script, which has no workspace to declare into.", Args: []Arg{{Name: "config", Type: TypeAny}, {Name: "opts", Type: TypeAny, Optional: true}}, Extern: true, }, { Name: "describe_module", Doc: "The host modules magus exposes, with their fields, methods and rendered Buzz signatures - the records `magus describe module` prints. Omit `name` for every module; pass one to detail it.", Args: []Arg{{Name: "name", Type: TypeString, Optional: true}}, Returns: []Ret{{Type: TypeAny, Object: "[Module]"}}, Raises: true, Extern: true, }, { Name: "canonical_name", Doc: "The canonical form of a magus entity name - a target, charm, or spell op. `build2` gains a '-' you did not type; `HTTPServer` breaks before its last letter. Returns the NAME, never a spell handle: a handle can only come from a literal import, because the target graph is built by reading imports statically.", Args: []Arg{{Name: "name", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Extern: true, }, { Name: "fatal", Doc: "Log at error level, then abort the run with exit status 1.", Args: []Arg{{Name: "msg", Type: TypeString, Optional: true}}, Extern: true, }, { Name: "pry", Doc: "Drop into an interactive REPL at this point, with the calling scope in hand. A no-op while the magusfile is only being parsed.", Extern: true, }, }, Namespaces: []Namespace{ { Name: "log", Doc: "Emitting a message without changing control flow: the four levels, plus hint. magus\\fatal and magus\\raise are deliberately NOT here - they END the run rather than report on it, and grouping them by how they look rather than what they do is what made this surface hard to read.", Methods: []Method{ { Name: "debug", Doc: "Log at debug level. See magus.info.", Args: []Arg{{Name: "msg", Type: TypeString, Optional: true}, {Name: "fields", Type: TypeStringMap, Optional: true}}, Extern: true, }, { Name: "error", Doc: "Log at error level. See magus.info. Logging an error does not abort; magus.fatal does.", Args: []Arg{{Name: "msg", Type: TypeString, Optional: true}, {Name: "fields", Type: TypeStringMap, Optional: true}}, Extern: true, }, { Name: "hint", Doc: "Emit an advisory nudge: non-fatal, deduped, and suppressed when hints are toggled off.", Args: []Arg{{Name: "msg", Type: TypeString, Optional: true}}, Extern: true, }, { Name: "info", Doc: "Log at info level. The only way to log from a magusfile; there is no separate log module on this surface.", Args: []Arg{{Name: "msg", Type: TypeString, Optional: true}, {Name: "fields", Type: TypeStringMap, Optional: true}}, Extern: true, }, { Name: "warn", Doc: "Log at warn level. See magus.info.", Args: []Arg{{Name: "msg", Type: TypeString, Optional: true}, {Name: "fields", Type: TypeStringMap, Optional: true}}, Extern: true, }, }, }, { Name: "cache", Doc: "Remote cache provider selection.", Methods: []Method{{ Name: "remote", Doc: "Select the remote cache provider, given an imported spell handle. Declared at the top level of the root magusfile.", Args: []Arg{{Name: "spell", Type: TypeAnyMap}}, Extern: true, }}, }, { Name: "ci", Doc: "CI provider selection.", Methods: []Method{{ Name: "provider", Doc: "Select the CI provider, given an imported spell handle.", Args: []Arg{{Name: "spell", Type: TypeAnyMap}}, Extern: true, }}, }, { Name: "secret", Doc: "Secret provider selection, and the credentials read through it.", Methods: []Method{ { Name: "provider", Doc: "Select the secret provider, given an imported spell handle.", Args: []Arg{{Name: "spell", Type: TypeAnyMap}}, Extern: true, }, { Name: "read", Doc: "Read a credential by reference through the selected provider. Unlike the selections, this is called from inside a target, so its failure IS something a caller can handle.", Args: []Arg{{Name: "ref", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Extern: true, }, { Name: "endpoint", Doc: "Open a loopback base URL carrying the credential a grant names, for a CHILD PROCESS to be pointed at instead of the real API. magus attaches the credential upstream, so the child never holds it.", Args: []Arg{{Name: "grant", Type: TypeAny}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Extern: true, }, }, }, { Name: "workspace", Doc: "Workspace-level declarations made from the root magusfile.", Methods: []Method{{ Name: "provider", Doc: "Select the workspace provider, given an imported spell handle.", Args: []Arg{{Name: "spell", Type: TypeAnyMap}}, Extern: true, }}, }, { Name: "ledger", Doc: "The declared lease ledger: what an orchestrating agent said about work it " + "handed out, recorded so a human can see the plan the agents are running. Rows are " + "DECLARATIONS - this store records them and refuses nothing; the agent guard is what " + "reads them to grade a write, and register's verdict is a fact it hands back rather " + "than a gate. See the field docs on " + "types.Lease. This namespace and the magus_ledger MCP tool are the only " + "doors onto it - there is deliberately no `magus ledger` CLI subcommand. Bound by " + "hand in internal/interp/bindings (buildLedgerNS), not generated: a Namespace's " + "methods are Extern by construction (see std.Namespace), so there is no Impl for " + "codegen to reflect a trampoline from, the same reason magus\\secret.read is hand-bound.", Methods: []Method{ { Name: "list", Doc: "Every row as one typed report: {leases, overlaps}. leases are in the order " + "they were declared; overlaps are derived on this read - every pair of live " + "(non-terminal) leases whose declared owned_paths intersect - the same " + "derivation the magus_ledger MCP tool's \"list\" op and the console's " + "/api/v1/ledger route use, so the three cannot disagree about a collision. " + "Annotate the result `> LeaseReport` for compile-checked field access. " + "Read straight off the workspace already open on the context - no subprocess. " + "Works from a magusfile target and from a `magus buzz` script run inside a " + "workspace; raises MGS1022 only when there is no workspace to read.", Returns: []Ret{{Type: TypeAnyMap, Object: "LeaseReport"}}, Raises: true, Extern: true, }, { Name: "put", Doc: "Record or advance one row, merging only the fields opts names: parent, " + "goal, checkpoint, owned_paths, forbidden_paths, depends_on, tier, " + "validation, state (declared, running, pass, fail, no_return), read_only. A " + "key opts omits is left untouched, so a later put in a lease's lifecycle (e.g. " + "{state = \"running\"}) advances it without erasing what an earlier put " + "declared; a key present with an empty value is an explicit clear. id is the " + "row's identity to upsert on - the value an orchestrator should also put in " + "the worker's prompt, so the console can join activity to the row. " + "created/updated/releases are stamped by the store and cannot be set here. " + "Returns the stored row. Read straight off the workspace already open on the " + "context - no subprocess. Works from a magusfile target and from a `magus " + "buzz` script run inside a workspace; raises MGS1022 only when there is no " + "workspace to read.", Args: []Arg{ {Name: "id", Type: TypeString}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "Lease"}}, Raises: true, Extern: true, }, { Name: "register", Doc: "Report the base a worker actually landed on, and learn how it compares " + "with the checkpoint the lease was handed. reported_base is a checkpoint token " + "in the form `magus vcs checkpoint -o name` prints: `<rev>`, or `<rev>+<digest>` " + "when the tree is dirty. Returns {lease, advice}: the stored row, and a " + "sentence naming both revisions and what to do next. The row carries " + "reported_base, registered, and base_verdict - one of match (same token), " + "revision-match (same revision, different uncommitted patch), diverged " + "(different revision), or unknown (the lease was declared without a " + "checkpoint, so there is nothing to compare against). The verdict is a FACT " + "returned and recorded, NEVER a refusal: a diverged registration succeeds " + "like any other, and what to do about it is the caller's and the " + "orchestrator's call. The one write here that does not create the row it " + "names - an id nothing declared means the worker was handed the wrong id, so " + "it raises rather than inventing a row. Read straight off the workspace " + "already open on the context - no subprocess. Works from a magusfile target " + "and from a `magus buzz` script run inside a workspace; raises MGS1022 only " + "when there is no workspace to read.", Args: []Arg{ {Name: "id", Type: TypeString}, {Name: "reported_base", Type: TypeString}, }, Returns: []Ret{{Type: TypeAnyMap}}, Raises: true, Extern: true, }, { Name: "clear", Doc: "Drop every row, which is how a fresh plan starts. Returns how many rows " + "it dropped - a fresh or already-empty ledger clears 0, which is not an " + "error. Clearing is also how one orchestrator can silently erase another's " + "plan, so a caller unsure whether it owns the whole ledger should list() " + "first. Read straight off the workspace already open on the context - no " + "subprocess. Works from a magusfile target and from a `magus buzz` script " + "run inside a workspace; raises MGS1022 only when there is no workspace to " + "read.", Returns: []Ret{{Type: TypeInt}}, Raises: true, Extern: true, }, }, }, }, }
Magus declares the host-declarable subset of the magus module. The remaining methods (target, dispatch, deps, pry, register) and the PROVIDER NAMESPACES (magus\cache, magus\ci, magus\secret) are VM-infrastructure: they manipulate the per-VM target registry and store/invoke VM-side function values, so they cannot share a Go Impl across backends and remain as hand-written trampolines in bindings/magus.go.
var Markdown = Module{ Name: "markdown", WASM: true, Doc: "GitHub-Flavored Markdown to semantic HTML.", Methods: []Method{ { Name: "to_html", Doc: "Render GitHub-Flavored Markdown to semantic HTML. Strips a leading YAML frontmatter block (a \"---\" fenced header at the top of the document) before rendering. Auto-IDs every heading so #fragment links resolve, and rewrites relative .md links (incl. README.md -> index.html) to their generated .html equivalents. Raw HTML in the source is passed through (intended for trusted, first-party docs).", Args: []Arg{{Name: "source", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: MarkdownToHTML, }, { Name: "frontmatter", Doc: "Parse the leading YAML frontmatter block (a \"---\" fenced header at the top of the document) and return it as a JSON object string; decode with serialize.jsonDecode. Returns \"{}\" when no frontmatter is present.", Args: []Arg{{Name: "source", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: MarkdownFrontmatter, }, { Name: "strip_frontmatter", Doc: "Return the Markdown body with any leading YAML frontmatter block removed (the source unchanged when none is present).", Args: []Arg{{Name: "source", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: MarkdownStripFrontmatter, }, }, }
Markdown is the "markdown" host module: GitHub-Flavored Markdown to semantic HTML. It backs the docs-site generator, which renders each docs/*.md page.
var Math = Module{ Name: "math", WASM: true, Doc: "Rounding to a decimal place, clamping, and aggregation over a list of numbers.", Methods: []Method{ { Name: "round", Doc: "Round x to places decimal places, half away from zero. places defaults to 0 (the nearest whole number) and may be negative to round to tens, hundreds and so on. Rendering a coverage percentage or a duration is the usual reason; Buzz's floor and ceil cannot express it.", Args: []Arg{ {Name: "x", Type: TypeFloat}, {Name: "places", Type: TypeInt, Optional: true}, }, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: MathRound, }, { Name: "trunc", Doc: "Discard x's fractional part, rounding TOWARD ZERO - so -1.7 is -1, where floor gives -2. The difference matters whenever a value can be negative and you meant \"drop the decimals\".", Args: []Arg{{Name: "x", Type: TypeFloat}}, Returns: []Ret{{Type: TypeFloat}}, Impl: MathTrunc, }, { Name: "clamp", Doc: "Constrain x to the range lo..hi, returning lo when x is below it and hi when above. Sizing parallelism is the usual reason - clamp(cpus / 2, 1, 8) never yields zero workers. Raises when lo is greater than hi, which is a caller bug rather than an empty range.", Args: []Arg{ {Name: "x", Type: TypeFloat}, {Name: "lo", Type: TypeFloat}, {Name: "hi", Type: TypeFloat}, }, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: MathClamp, }, { Name: "sum", Doc: "Add every number in the list; an empty list sums to 0. Non-numeric items are skipped rather than counted as zero.", Args: []Arg{{Name: "nums", Type: TypeFloatSlice}}, Returns: []Ret{{Type: TypeFloat}}, Impl: MathSum, }, { Name: "mean", Doc: "The arithmetic mean. Raises on an empty list rather than returning 0, because 0 is a real average and \"there was nothing to average\" is not - returning it would let an empty set silently pass a floor check.", Args: []Arg{{Name: "nums", Type: TypeFloatSlice}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: MathMean, }, { Name: "median", Doc: "The middle value, averaging the two middle values for an even count. Prefer it to mean when reporting what a TYPICAL run costs: one pathological outlier moves a mean and barely moves a median. Raises on an empty list, like mean.", Args: []Arg{{Name: "nums", Type: TypeFloatSlice}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: MathMedian, }, { Name: "min", Doc: "The smallest number in the list. Distinct from Buzz's own minInt/minDouble, which compare exactly two values. Raises on an empty list.", Args: []Arg{{Name: "nums", Type: TypeFloatSlice}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: MathMin, }, { Name: "max", Doc: "The largest number in the list. Distinct from Buzz's own maxInt/maxDouble, which compare exactly two values. Raises on an empty list.", Args: []Arg{{Name: "nums", Type: TypeFloatSlice}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: MathMax, }, }, }
Math is the "math" host module: the arithmetic Buzz's own math module lacks, layered onto it rather than replacing it.
Buzz already provides abs/ceil/floor/sqrt/pow/exp/log, the trig functions, and two-argument minInt/maxInt/minDouble/maxDouble. What it has none of is ROUNDING TO A PLACE and AGGREGATION OVER A LIST, which between them are most of what a build actually computes: a coverage percentage rendered to one decimal, the mean of a set of timings, the median that says what a typical run costs when one outlier would drag a mean.
The aggregations SKIP non-numeric items rather than treating them as zero. A silent zero shifts a mean and moves a median without anything reporting it, which is the worst failure mode for a number a gate then compares against a floor.
Pure computation: WASM-safe, no filesystem, no environment.
var Net = Module{ Name: "net", Doc: "TCP readiness and port allocation: wait for a service to accept connections, and find a free port.", Methods: []Method{ { Name: "wait_for_port", Doc: "Block until a TCP connection to host:port succeeds, then return true; return false when the timeout elapses first. Use it after starting a dev server or a container instead of sleeping a guessed number of seconds. timeout_ms defaults to 30000. Returns FALSE rather than raising on timeout, so a caller can fall back or report its own message; the run's cancellation is honored, so Ctrl-C does not wait out the timeout.", Args: []Arg{ {Name: "host", Type: TypeString}, {Name: "port", Type: TypeInt}, {Name: "timeout_ms", Type: TypeInt, Optional: true}, }, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: NetWaitForPort, }, { Name: "is_port_open", Doc: "Report whether a TCP connection to host:port succeeds right now, with no waiting. The single-shot form of wait_for_port - for deciding whether a service is ALREADY running before starting another one.", Args: []Arg{ {Name: "host", Type: TypeString}, {Name: "port", Type: TypeInt}, }, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: NetIsPortOpen, }, { Name: "free_port", Doc: "Ask the operating system for an unused TCP port and return it. Bind it promptly: the port is released before this returns, so between the call and your server's own bind another process could take it. That race is unavoidable for any \"find a free port\" answer and is why this is for choosing a dev-server port, not for anything that must not collide.", Returns: []Ret{{Type: TypeInt}}, Raises: true, Impl: NetFreePort, }, }, }
Net is the "net" host module: the two questions a build actually asks about TCP, and nothing else.
It is NOT a networking library. There is no listener, no client, no protocol - http already covers requests. What was missing is the pair of things every target that starts a service needs and had to shell out for:
- "is it up yet?" - a dev server, a database container, a mock backend. The shell form is a `until nc -z localhost 3000; do sleep 0.1; done` loop, which is unportable (nc's flags differ across BSD, GNU and busybox), silently infinite when the service never starts, and invisible to magus.
- "give me a port nobody is using" - so two targets running in parallel, or two workspaces on one machine, do not collide on a hardcoded 3000.
var Os = Module{ Name: "os", Doc: "The machine and this process: platform triple, CPU count, hostname, the running magus binary, and the two members that shadow Buzz's own (exit, sleep). Running OTHER processes lives in the proc module.", Methods: []Method{ { Name: "with_env", Doc: "Add env vars to subprocesses `proc\\exec` / `proc\\shell` start inside callback. Never touches the process's own environment - a lookup like os.env inside callback does not see them.", Args: []Arg{ {Name: "env", Type: TypeStringMap}, {Name: "callback", Type: TypeFunc}, }, Returns: nil, Raises: true, Impl: OsWithEnv, }, { Name: "platform", Doc: "Return the Docker/OCI platform triple: (os, arch, variant).", Args: nil, Returns: []Ret{{Type: TypeString}, {Type: TypeString}, {Type: TypeString}}, Impl: OsPlatform, }, { Name: "exit", Doc: "Abort the current run with the given exit code - typically after logging an error. Does NOT call os.Exit (that would kill a shared daemon); it raises, ending the target, and the code becomes magus's process exit status.", Args: []Arg{{Name: "code", Type: TypeInt}}, Returns: nil, Raises: true, Impl: OsExit, }, { Name: "sleep", Doc: "Pause for the given number of milliseconds (fractional allowed), matching Buzz's os.sleep. Cancellable: if the run is interrupted it returns early with the cancellation error rather than blocking.", Args: []Arg{{Name: "ms", Type: TypeFloat}}, Returns: nil, Raises: true, Impl: OsSleep, }, { Name: "num_cpu", Doc: "Return the number of logical CPUs available, for sizing a command's own internal parallelism (see os.with_slots).", Args: nil, Returns: []Ret{{Type: TypeInt}}, Impl: OsNumCPU, }, { Name: "hostname", Doc: "Return the host machine's name.", Args: nil, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: OsHostname, }, { Name: "executable", Doc: "Return the absolute path of the running magus binary. Pair it with fs.stat inside a long-lived watch loop to detect that the binary was rebuilt or upgraded underneath the process, which means any output it goes on to generate would be stale.", Args: nil, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: OsExecutable, }, { Name: "retry", Doc: "Call fn up to max times, retrying on error with exponential backoff; returns fn's value on success. opts: {backoff_ms:float (default 500), max_backoff_ms:float (default 30000)}.", Args: []Arg{ {Name: "max", Type: TypeInt}, {Name: "fn", Type: TypeFunc}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAny}}, Raises: true, Impl: OsRetry, }, }, }
Os is the "os" host module: direct-exec primitives (no shell invocation).
var Path = Module{ Name: "path", WASM: true, Doc: "Pure path-string math: abs, rel, clean, is_abs, expand_user, and glob matching.", Methods: []Method{ { Name: "abs", Doc: "Return the absolute form of path, resolved against the current directory and lexically cleaned.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: PathAbs, }, { Name: "rel", Doc: "Return a relative path from base to target; errors if no relative path exists.", Args: []Arg{{Name: "base", Type: TypeString}, {Name: "target", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: PathRel, }, { Name: "clean", Doc: "Return the shortest lexically-equivalent path (resolves . and .., collapses separators).", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: PathClean, }, { Name: "is_abs", Doc: "Report whether path is absolute.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Impl: PathIsAbs, }, { Name: "matches", Doc: "Report whether path matches a doublestar glob (** crosses directory separators, * does not). Purely lexical: unlike fs.glob it touches no filesystem, so it is what filters a list already in hand - the changed files from vcs.changed_files, the entries from archive.list - against the same pattern syntax a target's sources use. Raises on a malformed pattern rather than reporting no match, so a typo is not read as \"nothing changed\".", Args: []Arg{ {Name: "pattern", Type: TypeString}, {Name: "path", Type: TypeString}, }, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: PathMatch, }, { Name: "matches_any", Doc: "Report whether path matches ANY of the patterns; an empty pattern list is false. The common shape of a declared source or ignore set, which is a list rather than one glob.", Args: []Arg{ {Name: "patterns", Type: TypeStringSlice}, {Name: "path", Type: TypeString}, }, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: PathMatchAny, }, { Name: "expand_user", Doc: "Expand a leading ~ (or ~/...) to the current user's home directory; other paths are returned unchanged.", Args: []Arg{{Name: "path", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: PathExpandUser, }, }, }
Path is the "path" host module: pure path-string math (no filesystem access). fs.* already covers dirname/basename/join/glob; these are the remaining computations a spell otherwise has to shell out to `realpath`/`readlink -f` for - absolute/relative resolution, lexical cleaning, and ~ expansion. Nothing here touches disk, so no sandbox check applies; reading/writing the resolved path still goes through the sandbox-aware fs.* calls.
var Platform = Module{ Name: "platform", WASM: true, Doc: "Normalize OS/architecture identifiers across naming conventions (aarch64<->arm64, Darwin<->darwin).", Methods: []Method{ { Name: "arch", Doc: "Normalize an architecture identifier (x86_64, aarch64, armv7l, ...) to canonical Go GOARCH (amd64, arm64, arm). With style, render that result in a convention (go|uname); raises on an unknown style. Returns \"\" when the identifier is unrecognized.", Args: []Arg{ {Name: "name", Type: TypeString}, {Name: "style", Type: TypeString, Optional: true, Enum: "PlatformStyle"}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: PlatformArch, }, { Name: "os", Doc: "Normalize an OS identifier (Darwin, macOS, win, ...) to canonical Go GOOS (darwin, windows). With style, render that result in a convention (go|uname); raises on an unknown style. Returns \"\" when the identifier is unrecognized.", Args: []Arg{ {Name: "name", Type: TypeString}, {Name: "style", Type: TypeString, Optional: true, Enum: "PlatformStyle"}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: PlatformOS, }, { Name: "memory_bytes", Doc: "How much memory this process may commit, in BYTES, or 0 when it cannot be determined (any host other than Linux or macOS). Narrowed by a container's memory ceiling where there is one, the way cpus() honors a CPU quota. Note magus.project targets take memory_mb in MEGABYTES. Size work that scales on memory rather than cores with this: `go test` defaults its package parallelism to the CPU count, which is the wrong axis under -race, where each test binary carries the race detector's shadow memory. Branch on 0 rather than treating it as \"no memory\".", Returns: []Ret{{Type: TypeInt}}, Impl: PlatformMemory, }, { Name: "cpus", Doc: "How many CPUs this process may use (Go's GOMAXPROCS, which honors a container quota where the OS-visible core count does not). Pair with memory_bytes() when sizing parallel work: the smaller of the two limits is the one that matters.", Returns: []Ret{{Type: TypeInt}}, Impl: PlatformCPUs, }, }, }
Platform is the "platform" host module: it coheres the many OS/architecture spellings open-source projects use (aarch64 vs arm64, Darwin vs macOS vs mac) onto canonical Go GOOS/GOARCH values, and renders them back out in a chosen convention.
Matching is a deterministic, case-insensitive alias table rather than fuzzy matching: architecture identifiers are a small closed set where a near-miss (arm vs arm64, 386 vs amd64) must never be silently coerced to the wrong answer, so every accepted spelling is enumerated.
var Proc = Module{ Name: "proc", Doc: "Run other processes. proc.exec is the one verb that runs anything: it streams output live, captures it, honors the sandbox, and raises on failure instead of handing back a code to check. Needing a shell is not a second verb - proc.shell builds the {bin, args} to hand it, so which shell ran stays visible at the call site instead of hidden inside it. Distinct from Buzz's own os.execute, which returns an exit code and stays silent when you do not read it.", Methods: []Method{ { Name: "exec", Doc: "Run cmd directly (no shell; args are never shell-interpolated). Output streams live and is captured. Returns {stdout, stderr, code, ok}; raises on non-zero exit unless opts.allow_failure is true. Optional dir runs cmd there (relative to the target's cwd). opts.stdin is fed to the process as standard input - pipe by passing a prior call's stdout. opts.quiet captures the output without echoing it to the console. opts.tty runs cmd on a pseudo-terminal so it behaves as it would for a person: tools that check isatty keep their color and progress output instead of the plain form they emit to a pipe. A terminal is a single stream, so stderr arrives merged into stdout and the captured text carries ANSI escapes. Unix only.", Args: []Arg{ {Name: "cmd", Type: TypeString}, {Name: "args", Type: TypeStringSlice, Optional: true}, {Name: "dir", Type: TypeString, Optional: true}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "ExecResult"}}, Raises: true, Impl: OsExec, }, { Name: "shell", Doc: "Build the command line that runs `line` through the platform shell, WITHOUT running it: returns {bin, args} for proc.exec. Default shell is /bin/sh (cmd on Windows); pass shell (e.g. \"bash\") to override, resolved via PATH. This is a pure function, so the argv is inspectable before anything executes - print it, log it, or assert on it. A shell line is written in the platform shell's dialect, so sh and cmd lines are not portable across OSes; for cross-platform logic prefer proc.exec plus the fs/os helpers.", Args: []Arg{ {Name: "line", Type: TypeString}, {Name: "shell", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "ShellCommand"}}, Impl: OsShell, }, { Name: "which", Doc: "Resolve cmd against PATH and return its absolute path. RAISES when the command is not found - wrap it in try/catch to check a tool is installed and emit a clear hint instead of a cryptic exec failure.", Args: []Arg{{Name: "cmd", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: OsWhich, }, { Name: "with_slots", Doc: "Reserve n slots from magus's concurrency budget for the duration of callback. Use when callback runs a command with its own internal parallelism (make -j, a test runner) that magus can't see, so the global budget is not oversubscribed.", Args: []Arg{ {Name: "n", Type: TypeInt}, {Name: "callback", Type: TypeFunc}, }, Returns: nil, Raises: true, Impl: OsWithSlots, }, { Name: "stdin_is_terminal", Doc: "Report whether standard input is a terminal (TTY) rather than a pipe, file, or /dev/null. Use it to fail fast with a clear message instead of blocking on a read of stdin that will never receive piped input.", Args: nil, Returns: []Ret{{Type: TypeBool}}, Impl: OsStdinIsTerminal, }, }, }
Proc is running OTHER processes, and it is a separate module from os for a reason that is structural rather than cosmetic.
magus layers its host methods onto Buzz's stdlib, so one `import "os"` carries both surfaces. Buzz owns os.execute, which returns an exit code the caller must remember to check; magus's verb raises, captures, streams and enforces the sandbox. Two verbs that are synonyms in English, in one namespace, differing on whether a failure is SILENT, is a trap.
Renaming inside os would only be safe until upstream picked another name - Buzz is at 0.6.0-dev, and the day it ships proc.exec the collision is back. A module magus owns cannot collide with a language magus does not control.
What lives here answers "what process is about to run, and under what constraints". What stayed in os is the machine itself - platform, CPU count, hostname.
var Semver = Module{ Name: "semver", WASM: true, Doc: "Semantic version parsing and comparison (SemVer 2.0.0).", Methods: []Method{ { Name: "compare", Doc: "Order two semver strings: -1 when a sorts before b, 0 when they are equal, 1 when a sorts after. Use satisfies() to test a relation or a range.", Args: []Arg{ {Name: "a", Type: TypeString}, {Name: "b", Type: TypeString}, }, Returns: []Ret{{Type: TypeInt}}, Raises: true, Impl: SemverCompare, }, { Name: "isValid", Doc: "Whether v parses as a semantic version. Use it instead of calling parse purely to see whether it raises.", Args: []Arg{{Name: "v", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Impl: SemverIsValid, }, { Name: "canonical", Doc: `Canonical "vX.Y.Z" form of v, filling in missing components and discarding build metadata; errors on invalid input.`, Args: []Arg{{Name: "v", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: SemverCanonical, }, { Name: "major", Doc: `The major prefix of v as a string: major("1.2.3") is "v1". This is the cache token a spell's VersionKey{upTo = "major"} produces; parse().major is the same number as an int.`, Args: []Arg{{Name: "v", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: SemverMajor, }, { Name: "majorMinor", Doc: `The major.minor prefix of v as a string: majorMinor("1.2.3") is "v1.2". This is the cache token a spell's VersionKey{upTo = "minor"} produces.`, Args: []Arg{{Name: "v", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: SemverMajorMinor, }, { Name: "satisfies", Doc: `Whether v meets constraint, the full range syntax magus.yaml required_version uses: ">= 1.2, < 2.0", "^1.2.3", "~1.2". compare() tests one relation; this tests a range.`, Args: []Arg{ {Name: "v", Type: TypeString}, {Name: "constraint", Type: TypeString}, }, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: SemverSatisfies, }, { Name: "parse", Doc: "Parse a semver string into {major, minor, patch, prerelease, metadata, original}; errors on invalid input.", Args: []Arg{{Name: "v", Type: TypeString}}, Returns: []Ret{{Type: TypeAnyMap, Object: "SemverVersion"}}, Raises: true, Impl: SemverParse, }, { Name: "next", Doc: `Candidate next versions after v: {major, minor, patch}, each "vX.Y.Z" - the result of bumping the major, minor, or patch component. Errors on invalid input.`, Args: []Arg{{Name: "v", Type: TypeString}}, Returns: []Ret{{Type: TypeAnyMap, Object: "SemverNext"}}, Raises: true, Impl: SemverNext, }, }, }
Semver is the "semver" host module: semantic version parsing and comparison.
var Sort = Module{ Name: "sort", WASM: true, Doc: "Ordering for string lists: lexicographic, natural (digit-aware), and semver.", Methods: []Method{ { Name: "strings", Doc: "Return a new list ordered lexicographically by byte. The plain alphabetical sort, and the one to reach for when the goal is a stable order rather than a meaningful one - a listing that must not change between runs.", Args: []Arg{{Name: "items", Type: TypeStringSlice}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: SortStrings, }, { Name: "natural", Doc: "Return a new list ordered so embedded numbers compare as numbers: file2 before file10, where a lexicographic sort puts file10 first. Use it for anything a human will read - filenames, shard names, versioned directories - and note it is NOT a version sort; semver is.", Args: []Arg{{Name: "items", Type: TypeStringSlice}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: SortNatural, }, { Name: "semver", Doc: "Return a new list ordered by semantic version, oldest first, so v1.9.0 precedes v1.10.0 and a prerelease precedes its release. Accepts tags with or without a leading v. Anything that is not a valid version sorts AFTER every valid one, in lexicographic order among themselves - so a stray tag is visible at the end rather than silently reordering the releases.", Args: []Arg{{Name: "items", Type: TypeStringSlice}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: SortSemver, }, }, }
Sort is the "sort" host module: the orderings a build tool actually needs over a list of strings.
Buzz's list.sort takes a comparator, so ordering is not strictly missing - but the language has no `<` on str, which means even a plain alphabetical sort is a hand-written byte loop (docs/lib/text.buzz carries one, strLess). strings.compare gave that a primitive; this gives the three orderings worth not rewriting.
Each RETURNS A NEW LIST rather than sorting in place. list.sort mutates and requires a `mut` list, which means a caller holding a `final` list from vcs.changed_files or archive.list has to copy it first. Returning a fresh list makes the common case one call, and leaves the in-place form available for a caller that wants it.
var Strings = Module{ Name: "strings", WASM: true, Doc: "String helpers Buzz's builtins lack: case conversion, comparison, affix trimming, padding, and splitting into lines or fields.", Methods: []Method{ { Name: "camel_case", Doc: "Convert s to camelCase.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsCamelCase, }, { Name: "snake_case", Doc: "Convert s to snake_case.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsSnakeCase, }, { Name: "kebab_case", Doc: "Convert s to kebab-case.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsKebabCase, }, { Name: "pascal_case", Doc: "Convert s to PascalCase.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsPascalCase, }, { Name: "capitalize", Doc: "Uppercase the first rune of s and lowercase the rest.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsCapitalize, }, { Name: "words", Doc: "Split s into its constituent words (splitting on case changes, digits, and separators).", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: StringsWords, }, { Name: "ellipsis", Doc: "Trim s to at most length runes, appending \"...\" when truncated.", Args: []Arg{{Name: "s", Type: TypeString}, {Name: "length", Type: TypeInt}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsEllipsis, }, { Name: "upper_first", Doc: "Uppercase the first rune of s, leaving the rest untouched. Unlike capitalize, which lowercases the remainder, this preserves interior casing - the form a label or breadcrumb built from an existing string needs.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsUpperFirst, }, { Name: "compare", Doc: "Compare a and b lexicographically by byte, returning -1, 0, or 1. Buzz has no < operator on str, so this is what a list.sort comparator over strings is built from.", Args: []Arg{{Name: "a", Type: TypeString}, {Name: "b", Type: TypeString}}, Returns: []Ret{{Type: TypeInt}}, Impl: StringsCompare, }, { Name: "contains", Doc: "Report whether s contains substr.", Args: []Arg{{Name: "s", Type: TypeString}, {Name: "substr", Type: TypeString}}, Returns: []Ret{{Type: TypeBool}}, Impl: StringsContains, }, { Name: "trim_prefix", Doc: "Remove prefix from the start of s if present, otherwise return s unchanged. Buzz's str.trim only strips whitespace, so there is no built-in way to drop a known affix.", Args: []Arg{{Name: "s", Type: TypeString}, {Name: "prefix", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsTrimPrefix, }, { Name: "trim_suffix", Doc: "Remove suffix from the end of s if present, otherwise return s unchanged.", Args: []Arg{{Name: "s", Type: TypeString}, {Name: "suffix", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsTrimSuffix, }, { Name: "pad_left", Doc: "Left-pad s with pad until it is length runes wide; s is returned unchanged when already that wide or wider. pad defaults to a space. Zero-padding a number so it sorts lexically is the usual reason.", Args: []Arg{ {Name: "s", Type: TypeString}, {Name: "length", Type: TypeInt}, {Name: "pad", Type: TypeString, Optional: true, Default: " "}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: StringsPadLeft, }, { Name: "pad_right", Doc: "Right-pad s with pad until it is length runes wide; s is returned unchanged when already that wide or wider. pad defaults to a space. Aligning a column of output is the usual reason.", Args: []Arg{ {Name: "s", Type: TypeString}, {Name: "length", Type: TypeInt}, {Name: "pad", Type: TypeString, Optional: true, Default: " "}, }, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: StringsPadRight, }, { Name: "lines", Doc: "Split s into lines on \\n, tolerating \\r\\n endings and dropping the trailing empty element a final newline would produce. Reading a command's stdout line by line is what this is for; fs.read_lines is the same shape over a file.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: StringsLines, }, { Name: "fields", Doc: "Split s around runs of whitespace, discarding empties. Picking a column out of a tool's version banner is what this is for; str.split(\" \") cannot, because it yields an empty element per extra space.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: StringsFields, }, { Name: "split_n", Doc: "Split s on sep into at most n pieces, leaving any remaining separators in the final piece; n of -1 means no limit. Parsing a KEY=VALUE line whose value itself contains the separator needs this rather than str.split.", Args: []Arg{{Name: "s", Type: TypeString}, {Name: "sep", Type: TypeString}, {Name: "n", Type: TypeInt}}, Returns: []Ret{{Type: TypeStringSlice}}, Impl: StringsSplitN, }, { Name: "collapse_ws", Doc: "Fold every run of whitespace in s into a single space and trim the ends, so a multi-line value reads as one clean line.", Args: []Arg{{Name: "s", Type: TypeString}}, Returns: []Ret{{Type: TypeString}}, Impl: StringsCollapseWs, }, }, }
Strings is the "strings" host module: the string operations Buzz's builtins lack. Buzz strings already do upper/lower/trim/split/replace/sub, but two classes of work fall outside that. Codegen and naming tasks need to re-case an identifier (snake↔camel↔kebab↔Pascal) or split prose into words - operations with fiddly edge cases (acronyms, separators) that are easy to get subtly wrong in script; those delegate to samber/lo so the behavior matches a well-tested Go implementation. The rest are the primitives a magusfile reaches for while reading a command's output: comparing, trimming a known affix, splitting into lines or fields, padding a column.
`compare` in particular is not a convenience. Buzz has no `<` operator on str, so sorting strings at all means writing a byte-comparison loop by hand - docs/lib/text.buzz carries one (strLess) for exactly that reason. A comparator list.sort can be handed is what that loop was standing in for.
Pure string transforms: no filesystem or environment access.
var Template = Module{ Name: "template", WASM: true, Doc: "Logic-less Mustache templating (Mustache spec, via github.com/cbroglie/mustache).", Methods: []Method{ { Name: "render", Doc: "Render a Mustache template against a context value (usually a name->value map; lists drive sections, absent/false keys hide them). Returns the filled string; errors on a malformed template.", Args: []Arg{{Name: "template", Type: TypeString}, {Name: "data", Type: TypeAny}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: TemplateRender, }, { Name: "render_partials", Doc: "Render a Mustache template that includes partials via {{>name}}, resolving each name against the partials map (name->template string). Partials may reference other partials. Same context and escaping rules as render; errors on a malformed template.", Args: []Arg{{Name: "template", Type: TypeString}, {Name: "data", Type: TypeAny}, {Name: "partials", Type: TypeStringMap}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: TemplateRenderPartials, }, }, }
Template is the "template" host module: logic-less Mustache rendering via github.com/cbroglie/mustache, which tracks the mustache/spec. Chosen over Go's text/template: Mustache is a cross-language spec with a conformance suite, and being logic-less it keeps generated config files predictable.
var Term = Module{ Name: "term", Doc: "Terminal interaction: capability probes, an interactive picker, and styled output. Renders to stderr; pick raises rather than hanging when there is no terminal.", Methods: []Method{ { Name: "is_interactive", Doc: "Report whether this run can prompt at all: both standard input and standard error are terminals. Branch on it before calling pick - in CI, behind a pipe, or under a daemon this is false, and pick would raise. It is the one call that makes an interactive step safe to add to a target that also runs unattended.", Returns: []Ret{{Type: TypeBool}}, Impl: TermIsInteractive, }, { Name: "wants_color", Doc: "Report whether styled output should be emitted: standard error is a terminal and the environment does not ask for plain text (NO_COLOR, TERM=dumb). colorize already consults this, so a caller needs it only to make a wider rendering choice - a box-drawing table versus a plain one.", Returns: []Ret{{Type: TypeBool}}, Impl: TermWantsColor, }, { Name: "size", Doc: "Return the terminal's {width, height} in character cells. Both are 0 when there is no terminal to measure - piped output, no controlling terminal - so check width rather than expecting a raise. Use it to wrap or truncate output to the reader's actual window instead of assuming 80 columns.", Returns: []Ret{{Type: TypeAnyMap, Object: "TermSize"}}, Impl: TermSizeOf, }, { Name: "colorize", Doc: "Wrap s in the given style and close it again. Returns s UNCHANGED when the output is not a terminal or the environment asked for plain text, so a magusfile never has to guard the call and escape codes cannot leak into a CI log. A style of none is also pass-through, which lets a conditionally-computed style be passed without branching.", Args: []Arg{ {Name: "s", Type: TypeString}, {Name: "style", Type: TypeString, Enum: "TermStyle"}, }, Returns: []Ret{{Type: TypeString}}, Impl: TermColorize, }, { Name: "pick", Doc: "Prompt the reader to choose one of items and return its index. Type to filter (matching every whitespace-separated token), arrow keys or Ctrl-N/Ctrl-P to move, Enter to choose. RAISES when there is no terminal to prompt on - guard with is_interactive - and raises when the reader aborts with ESC, Ctrl-C or Ctrl-D, so a cancel ends the run rather than quietly returning a choice nobody made. Renders to stderr.", Args: []Arg{ {Name: "items", Type: TypeStringSlice}, {Name: "prompt", Type: TypeString, Optional: true}, {Name: "initial_filter", Type: TypeString, Optional: true}, {Name: "initial", Type: TypeInt, Optional: true}, {Name: "max_rows", Type: TypeInt, Optional: true}, }, Returns: []Ret{{Type: TypeIndex}}, Raises: true, Impl: TermPick, }, { Name: "notify", Doc: "Raise a notification into the band magus pins at the bottom of the terminal, where it shows for a few seconds and then disappears on its own. Unlike log.info it does not join the scrolling transcript: it is for something worth GLANCING at during a long run, not for the record. Returns immediately - the message expires on its own clock - and never raises: it is DROPPED when there is no terminal to show it on, or when the band has no room, so a piped or CI run is never given a repainted view it cannot use and no caller has to guard a notification. Log the same fact if it also needs recording. ttl_ms defaults to 5000; a negative ttl_ms pins the notification until newer ones push it out.", Args: []Arg{ {Name: "message", Type: TypeString}, {Name: "level", Type: TypeString, Enum: "LogLevel", Optional: true}, {Name: "ttl_ms", Type: TypeInt, Optional: true}, }, Returns: nil, Impl: TermNotify, }, { Name: "clear_screen", Doc: "Erase the screen and move the cursor home, the repaint a full-screen refresh loop issues before redrawing. Scrollback is preserved, so a reader who scrolls up after the loop ends still sees what came before. A no-op when there is no terminal, so a watch loop needs no guard.", Returns: nil, Raises: true, Impl: TermClearScreen, }, }, }
Term is the "term" host module: the terminal surface magus already renders its own output with, exposed so a magusfile can use it too.
None of this is new machinery. internal/interactive/tty carries the picker, the ANSI sequences and the terminal probe that `magus status --watch` and the project picker are built from; until now a magusfile could reach exactly one piece of it, os\stdinIsTerminal.
THE RULE THAT SHAPES THIS MODULE: magus does not ask. Its own doctrine is that the CLI reads context rather than prompting, because a prompt in a non-interactive run does not degrade - it HANGS, and a hung CI job reports as a timeout half an hour later with nothing to read. tty.Pick's own doc says the caller is expected to have already checked that stdin and stderr are terminals. "Expected to" is not a guarantee, so this module does not rely on it: pick verifies, and RAISES when it cannot prompt.
That makes the non-interactive path something an author has to answer for, which is the point. The shape is:
if (term\isInteractive()) { ... term\pick(...) ... } else { ... a declared default ... }
EVERYTHING RENDERS TO STDERR, matching the rest of magus: stdout carries the structured answer (-o json|yaml|template) alone, so a magusfile that paints a picker still pipes cleanly.
var Time = Module{ Name: "time", WASM: true, Doc: "Timestamp formatting/parsing and duration parsing (Go time, UTC).", Methods: []Method{ { Name: "format", Doc: "Render Unix-millis as a string using a Go reference layout (UTC).", Args: []Arg{{Name: "layout", Type: TypeString, Enum: "TimeLayout"}, {Name: "unix_millis", Type: TypeFloat}}, Returns: []Ret{{Type: TypeString}}, Impl: TimeFormat, }, { Name: "parse", Doc: "Parse a string with a Go reference layout into Unix-millis (UTC); errors on mismatch.", Args: []Arg{{Name: "layout", Type: TypeString, Enum: "TimeLayout"}, {Name: "value", Type: TypeString}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: TimeParse, }, { Name: "parse_duration", Doc: "Parse a Go duration string (e.g. \"168h\", \"1h30m\") into milliseconds; errors on mismatch.", Args: []Arg{{Name: "duration", Type: TypeString}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: TimeParseDuration, }, { Name: "now_iso", Doc: "Return the current UTC time as an RFC 3339 string. For the raw epoch-millis value use Buzz's os.time().", Args: nil, Returns: []Ret{{Type: TypeString}}, Impl: TimeNowISO, }, { Name: "add", Doc: "Add a Go duration string (e.g. \"24h\", \"-1h30m\") to a Unix-millis timestamp; returns the new Unix-millis timestamp.", Args: []Arg{{Name: "unix_millis", Type: TypeFloat}, {Name: "duration", Type: TypeString}}, Returns: []Ret{{Type: TypeFloat}}, Raises: true, Impl: TimeAdd, }, { Name: "diff", Doc: "Return a minus b in milliseconds (positive when a is later than b).", Args: []Arg{{Name: "a", Type: TypeFloat}, {Name: "b", Type: TypeFloat}}, Returns: []Ret{{Type: TypeFloat}}, Impl: TimeDiff, }, }, }
Time is the "time" host module: timestamp formatting/parsing and duration parsing, delegated straight to Go's time package so a spell doesn't reinvent calendar math. Times cross the VM boundary as Unix epoch milliseconds carried as float64 - the exact type (and unit) Buzz's os.time() already returns, so a timestamp flows through os.time() → extra.time without a conversion, and the 64-bit value never narrows to a 32-bit int. Layouts are Go reference-time strings (e.g. "2006-01-02T15:04:05Z07:00"). Formatting and parsing are anchored to UTC, so results are deterministic and location-free.
var UUID = Module{ Name: "uuid", WASM: true, Doc: "Unique identifiers and random tokens (v4 random, v7 time-ordered, plus raw random hex/tokens).", Methods: []Method{ { Name: "v4", Doc: "A random (version 4) UUID string, e.g. \"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d\".", Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: UUIDv4, }, { Name: "v7", Doc: "A time-ordered (version 7) UUID string; lexically sorts by creation time, which makes it a good ordered run/build id.", Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: UUIDv7, }, { Name: "randomHex", Doc: "A cryptographically random lowercase hex string of n bytes (2*n characters); errors when n is not positive.", Args: []Arg{{Name: "n", Type: TypeInt}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: UUIDRandomHex, }, { Name: "randomToken", Doc: "A cryptographically random URL-safe base64 token from n bytes of entropy (no padding); errors when n is not positive.", Args: []Arg{{Name: "n", Type: TypeInt}}, Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: UUIDRandomToken, }, }, }
UUID is the "uuid" host module: unique identifiers and random tokens for run ids, cache-busting suffixes, and artifact names. Native Buzz can only produce a bounded random int, so anything needing a collision-free id previously reached for os.time() hacks. Host-only (excluded from the browser playground) so its nondeterminism never leaks into the "planned, not run" dry-run surface.
var Vcs = Module{ Name: "vcs", Doc: "Version-control queries for the current working tree.", Methods: []Method{ { Name: "name", Doc: "VCS short name (e.g. \"git\"). Empty if unresolved, which is how a caller tests for a VCS without catching.", Returns: []Ret{{Type: TypeString}}, Impl: VcsName, }, { Name: "base", Doc: "Resolved base ref for diffs.", Returns: []Ret{{Type: TypeString}}, Impl: VcsBase, }, { Name: "root", Doc: "Absolute path of the repository root.", Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: VcsRoot, }, { Name: "changed_files", Doc: "The files changed against the given base (defaults to vcs.base), each a Path carrying the repository root as its base. Empty when no VCS is resolved. Named for what it returns: it answers WHICH files a branch touched, where vcs.dirtyDiff answers WHAT changed inside the working tree.", Args: []Arg{ {Name: "base", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeAny, Object: "[Path]"}}, Raises: true, Impl: VcsChangedFiles, }, { Name: "ref", Doc: "The movable name pointing at the current revision, or \"\" when there is none. Backend-specific by nature: a git branch, a Mercurial named branch, a Jujutsu bookmark. jj's working copy is usually an anonymous change, so \"\" is an ordinary answer there, not a failure. Raises when no VCS is resolved or its metadata cannot be read - use vcs.name() to test for a VCS first.", Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: VcsRef, }, { Name: "status", Doc: "The working tree's uncommitted state as {clean, files}: clean is true when nothing changed, files are the changed paths (empty when clean). Pass paths to scope it. Each file is a Path carrying the repository root as its base, because a VCS reports paths from the root while a target runs in its project directory. Paths only - a per-entry status code is not portable (jj reports none), so reach for vcs.cmd() when the codes matter.", Args: []Arg{ {Name: "paths", Type: TypeStringSlice, Optional: true}, }, Returns: []Ret{{Type: TypeAny, Object: "Status"}}, Raises: true, Impl: VcsStatus, }, { Name: "is_dirty", Doc: "True if the working tree has uncommitted changes. Pass paths to scope the check to those files/dirs (relative to the project), e.g. is_dirty([\"MAGUS.md\"]) - the right way to gate generated outputs without shelling out to git or parsing porcelain.", Args: []Arg{ {Name: "paths", Type: TypeStringSlice, Optional: true}, }, Returns: []Ret{{Type: TypeBool}}, Raises: true, Impl: VcsIsDirty, }, { Name: "dirty_diff", Doc: "The uncommitted changes to paths, as the active VCS's own unified diff; \"\" when nothing changed or no VCS is resolved. is_dirty answers whether an output moved, this answers how - which is what a drift gate needs when it fires in CI and nobody can look at the tree. Every backend implements it, so a magusfile no longer branches on vcs.name() to print a diff; the bytes are the backend's native format, not a normalized one.", Args: []Arg{ {Name: "paths", Type: TypeStringSlice, Optional: true}, }, Returns: []Ret{{Type: TypeString}}, Impl: VcsDirtyDiff, }, { Name: "commit", Doc: "Resolve a revision (a VCS-native rev expression; omit for the current revision) to its commit object: {id, short, author {name, email}, date, subject, body, parents}. id is the content/revision id (git SHA, hg node, jj commit_id); date is RFC3339, when the revision was recorded. Every field is meaningful for every VCS. Raises when no VCS is resolved or the revision cannot be looked up, so a caller never has to sniff a field to find out - use vcs.name() to test for a VCS, and try/catch for a revision that may not exist.", Args: []Arg{ {Name: "rev", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeAny, Object: "Commit"}}, Raises: true, Impl: VcsCommit, }, { Name: "history", Doc: "Up to limit recent commits, newest first; each is the same object vcs.commit returns. limit defaults to 10 when omitted. An empty list when no VCS is resolved.", Args: []Arg{ {Name: "limit", Type: TypeInt, Optional: true, Default: 10}, }, Returns: []Ret{{Type: TypeAny, Object: "[Commit]"}}, Raises: true, Impl: VcsHistory, }, { Name: "cmd", Doc: "Escape hatch: run the active VCS binary (git/hg/sl/jj) with args, for something no method covers. Same result and raise semantics as magus.cmd and proc.exec - returns {stdout, stderr, code, ok} and raises on a non-zero exit unless opts.allow_failure. opts.dir runs it elsewhere (relative to the target's cwd, unlike proc.exec's positional dir); opts.quiet captures the output without echoing it to the console. This is VCS-AGNOSTIC only in that magus picks the binary; the args are the backend's own, so branch on vcs.name() when they differ. Raises when no VCS is resolved, rather than running nothing and reporting success.", Args: []Arg{ {Name: "args", Type: TypeStringSlice}, {Name: "opts", Type: TypeAnyMap, Optional: true}, }, Returns: []Ret{{Type: TypeAnyMap, Object: "ExecResult"}}, Raises: true, Impl: VcsCmd, }, { Name: "tags", Doc: "Repository tags, newest first. Each is an object {name, date, id}: name as written (\"v0.3.0\", no refs/tags/ prefix), date RFC3339 (empty when the VCS reported none), id the revision it resolves to. pattern is a glob over the name (\"v*\"); wildcards stop at \"/\", so \"v*\" selects releases and skips a namespaced tag like backup/x. Omit it to list every tag. Empty when no VCS is resolved or the backend has no tags (jj); a failed query raises rather than reporting \"no tags\". Note a shallow or single-branch clone legitimately fetches no tags, so an empty list still means \"none present here\", not \"none exist\".", Args: []Arg{ {Name: "pattern", Type: TypeString, Optional: true}, }, Returns: []Ret{{Type: TypeAny, Object: "[Tag]"}}, Raises: true, Impl: VcsTags, }, { Name: "describe", Doc: "Human-readable version string from the nearest tag (git's `describe --tags --always --dirty`: tag, else short hash, with a -dirty suffix for a modified tree). \"\" when no VCS is resolved, or for a backend without a tag-describe concept (jj) - so a magusfile stamps a version without shelling out to git. Pair with vcs.commit().short as a fallback.", Returns: []Ret{{Type: TypeString}}, Raises: true, Impl: VcsDescribe, }, }, }
Vcs is the "vcs" host module: version-control queries for the current working tree.
Functions ¶
func ArchiveCompress ¶
func ArchiveList ¶ added in v0.4.0
func ArchiveList(ctx context.Context, src string, opts map[string]any) ([]types.ArchiveEntry, error)
ArchiveList reports an archive's entries without extracting it.
func ArchiveReadFile ¶ added in v0.4.0
ArchiveReadFile returns one entry's contents without extracting the archive.
func ArchiveUncompress ¶
func BuzzMethodName ¶ added in v0.4.0
BuzzMethodName is the identifier a Buzz caller types for m: the declared name converted to camelCase, or an explicit BuzzName override.
It exists because the two names differ and only this one is callable. A method declared "kebab_case" is reached as "kebabCase"; documenting the declared form sends a reader to a symbol that does not resolve.
func BuzzSignature ¶ added in v0.4.0
BuzzSignature renders a method's Buzz call form: the module imported under its bare name with the method camelCased, e.g. `env.lookup(name) → string, bool`. mod and m are the parent module and one of its methods. The separator is a backslash, the form upstream Buzz specifies for namespace member access. Docs show only that form so a reader never copies a notation magus is moving away from.
func BuzzStdlibEquiv ¶ added in v0.4.0
BuzzStdlibEquiv reports whether module.method has a Buzz stdlib call that covers the same need, returning that Buzz call. The magus method is still emitted; this is an informational pointer for authors who prefer Buzz's own stdlib. Both names are the snake_case descriptor names.
func CamelCase ¶ added in v0.4.0
CamelCase converts a snake_case descriptor name to Buzz's camelCase (a single-word name is unchanged). This is the single source of truth for the transform: magus-utils bindings uses it to emit the Buzz map keys, and BuzzSignature uses it to render those same keys, so the two cannot drift. (TestCamelCase in std/reflect_test.go keeps an independent table on purpose, to verify them.)
func CharmAfter ¶
func CharmAfter(_ context.Context, argv []string, anchor string, vals []string) (spells.Charm, error)
CharmAfter implements charm.after.
func CharmAfterFunc ¶
func CharmAfterFunc(ctx context.Context, argv []string, fn Callback, vals []string) (spells.Charm, error)
CharmAfterFunc implements charm.after_func.
func CharmAppend ¶
CharmAppend implements charm.append.
func CharmBefore ¶
func CharmBefore(_ context.Context, argv []string, anchor string, vals []string) (spells.Charm, error)
CharmBefore implements charm.before.
func CharmBeforeFunc ¶
func CharmBeforeFunc(ctx context.Context, argv []string, fn Callback, vals []string) (spells.Charm, error)
CharmBeforeFunc implements charm.before_func.
func CharmCopyFunc ¶
func CharmCopyFunc(ctx context.Context, argv []string, fn Callback, to string) (spells.Charm, error)
CharmCopyFunc implements charm.copy_func.
func CharmDropFunc ¶
CharmDropFunc implements charm.drop_func.
func CharmMoveFunc ¶
func CharmMoveFunc(ctx context.Context, argv []string, fn Callback, to string) (spells.Charm, error)
CharmMoveFunc implements charm.move_func.
func CharmPathFunc ¶
CharmPathFunc implements charm.path_func.
func CharmPrepend ¶
CharmPrepend implements charm.prepend.
func CharmSetFunc ¶
func CharmSetFunc(ctx context.Context, argv []string, fn Callback, val string) (spells.Charm, error)
CharmSetFunc implements charm.set_func.
func CharmTest ¶
CharmTest implements charm.test: a guard asserting the anchor is still present at its index when the patch applies.
func CharmTestFunc ¶
CharmTestFunc implements charm.test_func.
func CryptoBase64DecodeBytes ¶ added in v0.4.0
CryptoBase64DecodeBytes decodes standard padded base64 into raw bytes.
func CryptoBase64EncodeBytes ¶ added in v0.4.0
CryptoBase64EncodeBytes encodes raw bytes as standard padded base64.
func CryptoHmacSha256 ¶ added in v0.4.0
CryptoHmacSha256 returns the raw HMAC-SHA256 of data keyed by key.
Bytes in and bytes out, which is the whole point: the result keys the next call in a signing chain, and a str would not survive the round trip. It moved here from internal/interp/bindings/crypto_bytes.go once a byte-list type tag existed to declare it with - as an undeclared companion it worked but was invisible to `magus describe modules`, the knowledge graph and the docs, and untyped in the checker.
func CryptoHmacSha256Hex ¶ added in v0.4.0
CryptoHmacSha256Hex returns the hex HMAC-SHA256 of data keyed by key.
func CryptoMd5File ¶
CryptoMd5File returns the lowercase hex MD5 digest of the file at path (legacy interop).
func CryptoMd5Hex ¶
CryptoMd5Hex returns the lowercase hex MD5 digest of data (legacy interop).
func CryptoPublicKey ¶ added in v0.4.0
CryptoPublicKey returns the hex public half of the key named by keyEnv.
func CryptoSha1File ¶
CryptoSha1File returns the lowercase hex SHA-1 digest of the file at path (legacy interop).
func CryptoSha1Hex ¶
CryptoSha1Hex returns the lowercase hex SHA-1 digest of data (legacy interop).
func CryptoSha256File ¶
CryptoSha256File returns the lowercase hex SHA-256 digest of the file at path.
func CryptoSha256Hex ¶
CryptoSha256Hex returns the lowercase hex SHA-256 digest of data.
func CryptoSha512File ¶
CryptoSha512File returns the lowercase hex SHA-512 digest of the file at path.
func CryptoSha512Hex ¶
CryptoSha512Hex returns the lowercase hex SHA-512 digest of data.
func CryptoSign ¶ added in v0.4.0
CryptoSign signs data with the key named by keyEnv, returning hex.
func CryptoSignFile ¶ added in v0.4.0
CryptoSignFile signs the file at path and writes path + ".sig".
The signature is written as RAW bytes, not hex: that is the format every reader of a magus signature already expects, from the installer's openssl pkeyutl to internal/selfupdate. The hex return is for printing.
func CryptoVerify ¶ added in v0.4.0
CryptoVerify reports whether sigHex verifies data under pubHex.
func CwdFromContext ¶
CwdFromContext returns the working directory carried by WithCwd and whether one is set. Unlike EffectiveCwd it does not fall back to the process cwd, so a log handler can attach the directory only when a magusfile target actually established one - the process cwd is no longer meaningful for that correlation.
func DiffEqual ¶ added in v0.4.0
DiffEqual reports whether a and b match once line endings and a trailing newline are normalized.
func DiffUnified ¶ added in v0.4.0
func DiffUnified(_ context.Context, a, b, fromLabel, toLabel string, contextLines int) (string, error)
DiffUnified returns a unified diff of a and b, or "" when they are identical.
func EffectiveCwd ¶
EffectiveCwd reports the working directory a host module should treat as "." - the context cwd when set (the magusfile runner sets it per target), else the process working directory. Exported so the interp host bindings can resolve workspace-local paths against the same base the std modules use.
func EnvExpand ¶
EnvExpand replaces $VAR and ${VAR} references in s using the same sandbox-aware lookup as EnvGet: a name the policy strips (or that is unset) expands to "", so a spell cannot recover a hidden secret by interpolating it into a string.
func EnvGet ¶
EnvGet returns the value of the named variable, or "" if unset or stripped by the sandbox policy.
func EnvGetOr ¶
EnvGetOr returns the value of name, or def if the variable is unset or stripped by the sandbox. A variable that is set but empty returns its empty value; def is only the fallback for absence.
func EnvList ¶
EnvList returns all environment variables as a name-value map, omitting any the sandbox policy strips.
func EnvLoadDotenv ¶
EnvLoadDotenv reads a .env file and sets each variable into the process environment. Existing names win (the dotenv convention), sandbox-stripped names are skipped (matching EnvSet), and a recording/dry-run is a no-op.
func EnvLookup ¶
EnvLookup returns (value, found) for the named variable, distinguishing "set but empty" ("", true) from "unset" ("", false) as os.LookupEnv does and os.Getenv does not. A sandbox-stripped variable reports ("", false) so a stripped secret is indistinguishable from an absent one and cannot be probed for (mirrors EnvGet's information-hiding).
func EnvParseDotenv ¶
EnvParseDotenv parses .env-format content into a name->value map. It is pure (no process-environment access), so it is WASM-compatible.
func EnvReadDotenv ¶
EnvReadDotenv reads a .env file and parses it, subject to the sandbox read policy.
func EnvRequire ¶
EnvRequire returns the value of name, or an error when it is unset or stripped by the sandbox. It shares EnvLookup's sandbox-aware "found" semantics: a stripped variable is reported as absent (and so raises), and a set-but-empty variable is present (its empty value is returned).
func EnvSet ¶
EnvSet sets name to value in the current process environment, unless the sandbox policy strips name.
func EnvUnset ¶
EnvUnset removes name from the current process environment, unless the sandbox policy strips name (in which case it is already invisible and the call is a no-op, mirroring EnvSet's refusal to touch stripped names).
func FieldFuncName ¶ added in v0.4.0
FieldFuncName returns the bare function name of f.Resolver (e.g. "VcsName").
func FieldResolverPackage ¶ added in v0.4.0
FieldResolverPackage is MethodImplPackage for a Field's Resolver.
func FieldResolverTakesCtx ¶ added in v0.4.0
FieldResolverTakesCtx reports whether f.Resolver's signature is (context.Context) (T, error) rather than () (T, error).
func FmtSprintf ¶
FmtSprintf formats args into format using Go's fmt.Sprintf. The variadic boundary carries strings, so each arg satisfies %s/%q verbs.
func FsAppendFile ¶
FsAppendFile appends content to path (creating the file if absent, mode 0644), subject to the sandbox write policy.
func FsBasename ¶
FsBasename returns the final element of path.
func FsCopyDir ¶
FsCopyDir recursively copies the directory tree at src to dst, preserving permission bits. Each source entry is checked for read and each destination for write, so a sandbox-denied path stops the copy with a diag error.
func FsCopyFile ¶
FsCopyFile copies src to dst (overwriting), preserving src's permission bits. Both ends are subject to the sandbox policy: src must be readable, dst writable.
func FsExists ¶
FsExists reports whether path exists. A sandbox-denied path RAISES rather than being reported as absent (see the inline comment below for why).
func FsGlob ¶
FsGlob returns paths matching the doublestar pattern, filtered to those the sandbox policy permits reading.
func FsIsDir ¶
FsIsDir reports whether path exists and is a directory. A sandbox-denied path RAISES (see FsExists): "not allowed to look" is not the same answer as "not a directory".
func FsIsFile ¶
FsIsFile reports whether path exists and is a regular file. A sandbox-denied path RAISES (see FsExists).
func FsListDir ¶
FsListDir returns the entry names in path, or nil if it does not exist, subject to the sandbox read policy.
func FsMkdirAll ¶
FsMkdirAll creates path and any missing parents with the given mode, subject to the sandbox write policy.
func FsReadFile ¶
FsReadFile returns the contents of path as a string, subject to the sandbox read policy.
func FsReadLines ¶
FsReadLines reads path and returns its lines with terminators stripped, subject to the sandbox read policy. A single trailing newline is not reported as a final empty line (the common "lines of a text file" expectation), and an empty file yields an empty list rather than [""].
func FsReadlink ¶
FsReadlink returns the destination of the symbolic link at path, subject to the sandbox read policy.
func FsRemove ¶ added in v0.4.0
FsRemove removes a single file or empty directory, subject to the sandbox write policy. A missing path is not an error, matching remove_all.
func FsRemoveAll ¶
FsRemoveAll recursively removes path (no error if missing), subject to the sandbox write policy.
func FsRename ¶ added in v0.4.0
FsRename moves src to dst, subject to the sandbox write policy on both ends. dst's parent is created first so the common "rename into a fresh output dir" does not need a separate mkdirall.
func FsSize ¶ added in v0.4.0
FsSize returns path's size in bytes, subject to the sandbox read policy.
func FsStat ¶
FsStat returns metadata for path as {size, mtime, mode, is_dir}, subject to the sandbox read policy. Unlike the probe predicates a missing path is an error, since a caller asking for metadata expects the entry to exist.
func FsSymlink ¶
FsSymlink creates a symbolic link at link pointing to target, subject to the sandbox write policy on link.
func FsTempDir ¶
FsTempDir creates a new temporary directory in os.TempDir() with an optional name prefix and returns its path.
func FsTempFile ¶ added in v0.4.0
FsTempFile creates an empty temporary file and returns its path.
func FsWalk ¶
FsWalk walks the directory tree rooted at root, calling cb(path, isDir) for each entry in lexical order. If the callback returns true the walk stops early. Sandbox-denied entries are silently skipped (dirs skip their whole subtree; files are just omitted), matching the filtering policy of FsGlob.
func FsWatch ¶
FsWatch is BLOCKING: it watches paths (directories, recursively) and invokes cb with each debounced batch of changed paths (relative to the current directory) until cb returns true or the run is cancelled (Ctrl-C). Editor/VCS noise (.git, build caches, ...) is filtered by the built-in ignore set. It returns nil on a clean stop; a watcher setup error or an error raised by the callback propagates. Because it holds its session for its whole life, the idiomatic use is a reactive loop (rebuild on change) run as its own target, with parallelism coming from magus running other targets concurrently.
func FsWriteFile ¶
FsWriteFile writes content to path (mode 0644), subject to the sandbox write policy.
func FsWriteFileAtomic ¶ added in v0.4.0
FsWriteFileAtomic writes content to path via a same-directory temporary file renamed into place, subject to the sandbox write policy.
func FsWriteLines ¶
FsWriteLines writes lines to path (mode 0644), each terminated by a newline, subject to the sandbox write policy. An empty list writes an empty file, so write_lines(p, read_lines(p)) round-trips a newline-terminated file.
func HTTPDownload ¶ added in v0.4.0
func HTTPDownload(ctx context.Context, url, dest string, headers map[string]string, opts, retryPolicy map[string]any) (int, error)
HTTPDownload GETs url and streams the body to dest, returning the HTTP status.
It lives here rather than beside the other byte-level helpers in internal/interp/bindings so it is a declared method like every other: visible in `magus describe modules`, in the knowledge graph and the docs, and TYPED in the checker. As an undeclared companion it was reachable but invisible, and its signature was Unknown to every caller.
Moving it also gained it two things the companion version never had: the retrying client the rest of this module uses, and a sandbox write check on dest.
func HTTPGet ¶
func HTTPGet(ctx context.Context, url string, headers map[string]string, opts, retryPolicy map[string]any) (types.HTTPResponse, error)
HTTPGet sends a GET request to url with optional headers and curl-style opts, returning {status, body, headers}.
func HTTPPost ¶
func HTTPPost(ctx context.Context, url, body string, headers map[string]string, opts, retryPolicy map[string]any) (types.HTTPResponse, error)
HTTPPost sends a POST of body to url with optional headers and curl-style opts, returning {status, body, headers}.
func HTTPRequest ¶
func HTTPRequest(ctx context.Context, method, url, body string, headers map[string]string, opts, retryPolicy map[string]any) (types.HTTPResponse, error)
HTTPRequest sends a request with the given method to url and returns {status, body, headers}.
func HTTPServe ¶ added in v0.2.0
HTTPServe starts a static file server on localhost in a background goroutine from a validated options bag and returns the bound TCP port. opts is a curl-style map that carries either a single "dir" to serve one directory or a "mounts" prefix->dir map to serve several roots by URL prefix (longest prefix wins), plus an optional "port". The options object is the boundary: an unknown key, a missing or ambiguous dir/mounts choice, or a mistyped value fails loudly here rather than silently defaulting. The server runs until the process exits, so callers pair it with a blocking call such as fs.watch to keep serving.
func HostPlatform ¶
func HostPlatform() (osName, arch, variant string)
HostPlatform returns the Docker/OCI platform triple (GOOS, OCI arch, ARM variant). variant is "v6"/"v7"/"v8" for ARM, "" otherwise; arm reads /proc/cpuinfo on Linux.
func MagusAffected ¶ added in v0.4.0
MagusAffected computes the affected project set in-process. See MagusProjects for why the read-only verbs do not fork.
It deliberately does NOT swallow ErrAffectedFallback. When the VCS cannot produce a diff, magus selects every project as a safety net (MGS1010); a magusfile branching on this result needs to know that happened, because "nothing changed" and "we could not tell what changed" call for opposite decisions.
func MagusAffectedImpact ¶ added in v0.4.0
func MagusAffectedImpact(ctx context.Context, base string, opts map[string]any) (types.ImpactResult, error)
MagusAffectedImpact reports the affected set through a nested magus. It is the forking counterpart to MagusAffected, and answers the richer question: not just which projects, but which files seeded them.
func MagusAttention ¶ added in v0.4.0
func MagusAttention(ctx context.Context, args []string, opts map[string]any) (map[string]any, error)
MagusAttention lists the open attention requests through a nested magus. Listing only: disposal is deliberately absent from this surface, because a script that closes requests is the auto-disposition the doctrine's Manual-on-purpose table rules out.
func MagusBustCache ¶
MagusBustCache invalidates cached build entries. When projectPath is empty all manifests are cleared; otherwise only entries for that project are removed. A structured warning is always emitted: this is an escape hatch, not routine.
func MagusClearLedger ¶ added in v0.4.0
MagusClearLedger backs magus\ledger.clear, matching the magus_ledger MCP tool's "clear" op: it reports how many rows it dropped rather than nothing, since a destructive op should say what it destroyed.
func MagusCmd ¶
func MagusCmd(ctx context.Context, sub string, args []string, opts map[string]any) (types.ExecResult, error)
MagusCmd is the escape hatch: it runs `magus <sub> <args>` for a subcommand with no dedicated wrapper (status, affected, agent, ...). The subcommand is a parameter of its own rather than args[0], which is what lets the warning below be exact instead of a guess at a list's first element, and what keeps the invocation readable to anything reading the source. Like the typed methods it runs in the contextual project dir unless opts.dir says otherwise; see runMagus.
func MagusDescribe ¶
func MagusDescribe(ctx context.Context, args []string, opts map[string]any) (types.ExecResult, error)
MagusDescribe runs `magus describe <args>`; see runMagus.
func MagusDescribeFile ¶ added in v0.4.0
func MagusDescribeFile(ctx context.Context, paths []string, opts map[string]any) (types.FileReport, error)
MagusDescribeFile classifies paths as generated output, declared source, or unclaimed. See runMagusJSON for why it forks rather than reading the workspace on the context: this answer is wanted precisely where there is no workspace loaded - a `magus buzz` script deciding whether a changed file is worth a human's attention.
func MagusDiagnoseDrift ¶ added in v0.4.0
MagusDiagnoseDrift diagnoses a generate gate's drift into a coded diagnostic. Given the target's declared output globs and input globs (project-relative) and the fact that the tree drifted, it distinguishes the three causes the plan defines:
- outputs dirty AND a declared input is also dirty -> MGS4006 StaleGeneratedOutput: a source input changed, so regeneration is expected; commit it.
- outputs dirty, inputs byte-identical, running a DEV build -> MGS4005 EnvironmentalDrift: the committed form is produced by the pinned release (compat contract), so a dev build's differing output is version/tool skew - not the developer's change.
- outputs dirty, inputs byte-identical, running a RELEASE build -> MGS4003 NondeterministicOutput: same inputs and generator version, yet output differs - a reproducibility bug.
It RETURNS the classification rather than throwing, so the gate owns the response - fail on a clean-tree drift, warn on a mid-edit dirty one. The record is a plain map:
{ drifted: bool, code: str, message: str, url: str, files: []str }
drifted is false when the outputs are not actually dirty. files carries the backend's status lines, so a gate can say WHICH files moved without shelling out to the VCS.
It composes vcs.isDirty rather than replacing it: isDirty stays the general primitive, and this is the drift-specific reading on top plus the version signal.
func MagusDiff ¶ added in v0.4.0
MagusDiff implements magus\diff: the annotated changeset, in reading order.
It shells out to `magus diff` rather than reimplementing the join, which is the whole point of exposing it here - a Buzz advisor writing a pull-request comment and the console surface then rank files by the SAME definition (types.Diff.SortForReading), and a change to that order reaches both without either being edited.
--generated is passed so the caller receives every file and decides what to fold. A CI comment and a terminal reader want different things from the generated set, and a host module that pre-filtered would make the wider answer unreachable.
func MagusDoctor ¶
func MagusDoctor(ctx context.Context, args []string, opts map[string]any) (types.DoctorReport, error)
MagusDoctor validates the workspace and returns the typed report.
It is the shape every method here should have and most do not yet: the child already knows how to say this in JSON, so the answer crosses the boundary as the domain type rather than as console text a caller has to parse back out of a string.
func MagusGraph ¶ added in v0.4.0
MagusGraph returns the project dependency graph as a flat object. See MagusProjects for why the read-only verbs are served in-process.
func MagusHasCharm ¶
MagusHasCharm reports whether the execution charm name is active in ctx. It backs magus.has_charm, the read side of the charm system: a function target can publish conditionally on has_charm("rw") or branch on a custom charm.
func MagusInsight ¶
MagusInsight returns every insight lens as one typed report, read straight off the loaded workspace. Whole-report only: a single lens is the MCP tool's job, which is where an agent asks for one.
This is the one magus\* method that does NOT fork a nested magus, and the difference is the point: analytics are a pure read of a workspace magus has already loaded, so paying a process, a second workspace load, and a JSON round trip to ask it a question about itself was cost with nothing bought. See std/workspace.go for how it reaches the API across an import cycle that used to make forking the only option.
func MagusListLedger ¶ added in v0.4.0
func MagusListLedger(ctx context.Context) (types.LeaseReport, error)
MagusListLedger backs magus\ledger.list (hand-bound in internal/interp/bindings/ledger_ns.go, since a Namespace method has no Impl for codegen to reflect a trampoline from - see std.Namespace). It answers with one typed report; types.NewLeaseReport is the same constructor the magus_ledger MCP tool's "list" op and the console's /api/v1/ledger route call, so the three doors cannot disagree about the rows or the overlaps derived from them.
func MagusProjects ¶ added in v0.4.0
func MagusProjects(ctx context.Context) (types.ProjectsOutput, error)
MagusProjects lists the workspace's projects from the workspace already open on ctx.
The first of the read-only verbs served IN-PROCESS. The typed methods below it fork a full magus subprocess via runMagus - a spawn, a second workspace load, a JSON encode and a Buzz-side parse - to answer a question this process already holds.
The workspace reaches ctx via types.WithWorkspace in magus.Open's load, so it is present for every magusfile target. A bare Buzz script has none, hence the guard.
func MagusPutLedger ¶ added in v0.4.0
MagusPutLedger backs magus\ledger.put. The field merge is decoded by internal/ledger.Merge, the same decoder the magus_ledger MCP tool's "put" op calls, so a client typing either surface accepts the same fields and rejects the same mistakes.
func MagusRaise ¶ added in v0.4.0
MagusRaise fails with a caller-defined coded diagnostic.
magus already gives a Buzz `catch` the code, message and url of its OWN failures, so a magusfile can branch on MGS2001 without matching prose. Authoring one was the missing half: a magusfile could only `throw` a string, leaving its callers doing the substring matching that mechanism exists to avoid.
The MGS prefix is REFUSED, not discouraged: MGS codes are a closed catalog that `magus explain`, the knowledge graph and the docs URL map resolve against, so a workspace minting MGS9999 would render like magus's own and document nothing.
`raise` rather than `throw` because throw is a reserved Buzz keyword; error and fatal are taken by the logging members above.
cause and url live in an opts map: the generated trampoline binds by index, so a fourth positional url was unreachable without also passing a cause.
func MagusRegisterLedger ¶ added in v0.4.0
MagusRegisterLedger backs magus\ledger.register: a worker reports the base it actually landed on, and learns how that compares with the checkpoint its lease was handed. It returns the stored row and internal/ledger.RegistrationAdvice's reading of the verdict, the same pair the magus_ledger tool's "register" op answers with.
The verdict is a FACT, never a refusal - a diverged registration is recorded and reported like any other. See types.Lease.
func MagusTargets ¶ added in v0.4.0
MagusTargets returns every project's target graph. It is the typed counterpart to `magus describe graph`: a caller that wants the target inventory no longer has to shell out and parse the markdown that command renders. TargetGraph reads the magusfile statically, so this is side-effect free.
It serves the workspace on the context when there is one and forks a nested magus when there is not, because the caller asking for the target graph is asking the same question either way - which magusfile it came from is magus's problem, not a name the caller should have to choose between. These were two methods once; the split leaked an implementation detail into the surface, and calling the wrong one raised rather than answering.
func MagusWhere ¶ added in v0.4.0
MagusWhere returns the project path containing dir, or "" when dir is inside none. "" rather than an error: asking whether a path is inside a project is a question, and "no" is a valid answer a magusfile branches on.
func MarkdownFrontmatter ¶
MarkdownFrontmatter parses the leading YAML frontmatter block and returns it as a JSON object string (Buzz callers decode it with serialize.jsonDecode); "{}" when there is none. The YAML is parsed here rather than in to_html so a malformed header only errors for callers that read the metadata, never breaking a page render.
func MarkdownStripFrontmatter ¶
MarkdownStripFrontmatter returns the Markdown body with any leading YAML frontmatter block removed; the source is returned unchanged when none is present. It backs the search index, whose page text must not include the raw header.
func MarkdownToHTML ¶
MarkdownToHTML renders GFM source to semantic HTML, first stripping any leading YAML frontmatter so the header never renders as content.
func MathMedian ¶ added in v0.4.0
MathMedian returns the middle value of nums, averaging the middle pair when the count is even.
func MethodFuncName ¶ added in v0.4.0
MethodFuncName returns just the bare function name of m's Impl (e.g. "FsGlob") without the package qualifier. Useful for generating trampoline names.
func MethodImplPackage ¶ added in v0.4.0
MethodImplPackage returns the Go import path and local package identifier of m.Impl - e.g. ("github.com/egladman/magus/std", "std") for a method still implemented in std's own flat root, or ("github.com/egladman/magus/std/encoding/json", "json") for one implemented in a std/encoding leaf package. Both empty if Impl is nil or not a function.
The magus-utils bindings generator used to assume every Impl lived in package std and hardcoded the "std." call qualifier; that stopped holding once std/encoding split nine modules into their own packages, so the qualifier - and the import it needs - now has to come from wherever the Impl actually is.
func MethodSource ¶ added in v0.4.0
MethodSource returns the source file (relative to repoRoot) and line where a method's Impl is defined, for linking generated docs back to the code. Returns ("", 0) when Impl is nil/not a function, or its file resolves outside repoRoot (e.g. a -trimpath build, where there's no absolute path to relativize).
func NetFreePort ¶ added in v0.4.0
NetFreePort returns a TCP port the OS reports as unused.
Port 0 asks the kernel to assign one, which is the only answer that is not a guess: scanning upward from a base port races every other process doing the same thing, and magus already has one such scan in http.server.
func NetIsPortOpen ¶ added in v0.4.0
NetIsPortOpen reports whether host:port accepts a connection right now.
func NetWaitForPort ¶ added in v0.4.0
NetWaitForPort blocks until host:port accepts a connection or the timeout elapses.
func OsExec ¶
func OsExec(ctx context.Context, cmd string, args []string, dir string, opts map[string]any) (types.ExecResult, error)
OsExec runs cmd with args directly (no shell). Output streams live and is captured; it returns {stdout, stderr, code, ok}, raising on a non-zero exit unless opts.allow_failure is true. The optional dir runs cmd in that directory (relative to the context cwd); omitted, it inherits the context (or process) cwd.
func OsExecutable ¶ added in v0.3.0
OsExecutable returns the absolute path of the running binary. It resolves symlinks so two paths pointing at the same file compare equal, which matters for the intended use: a long-lived watcher stats this path each tick and treats a change as "I am no longer the current build, so anything I generate from here is stale".
func OsExit ¶
OsExit aborts the current run by returning a types.ExitError carrying code. It deliberately does not call os.Exit: a target may run inside a daemon serving other workspaces, where os.Exit would kill unrelated work. The error propagates to the CLI (and daemon), which translate it into the process exit status. It also records the code on ctx (types.CaptureExit) so it survives when the engine stringifies the error type away; the interpreter reads it back. See types.ExitError.
func OsHostname ¶
OsHostname returns the host machine's name.
func OsPlatform ¶
OsPlatform wraps HostPlatform for use as a Method Impl.
func OsRetry ¶
OsRetry calls fn up to max times, retrying on error with exponential backoff. On success it returns fn's first return value; on exhaustion it errors. opts keys: backoff_ms (initial delay, default 500), max_backoff_ms (cap, default 30000).
func OsShell ¶ added in v0.4.0
OsShell builds the argv that runs line through the platform shell and returns it, rather than running it. It replaced proc.shell, which had zero callers in this tree - where a shell was genuinely wanted, people wrote proc.exec("sh", ["-c", ...]) by hand, which says out loud what exec_sh hid.
Two verbs for "run a process" cost more than they bought. Every option worth having (stdin, quiet, allow_failure, tty) had to exist and be documented twice, and the wrapper's only real content was ten lines of platform selection. That selection IS worth having, so it survives here as a pure function: the shell choice becomes a value you can print before anything executes, instead of a decision taken inside a call you cannot see into.
func OsSleep ¶
OsSleep pauses for ms milliseconds (matching Buzz's os.sleep), honoring context cancellation: a one-shot timer races ctx.Done() so an interrupted run wakes immediately with ctx.Err() rather than blocking for the full duration. A non-positive duration is a no-op; an absurdly large one is clamped to 24h to avoid overflowing the nanosecond time.Duration.
func OsStdinIsTerminal ¶
OsStdinIsTerminal reports whether stdin is a TTY, reusing the shared terminal check (see internal/interactive/tty).
func OsWhich ¶
OsWhich resolves cmd against PATH, RAISING when it is not there.
It used to return "" so a magusfile could branch on `proc.which(cmd) == ""`. That is the same sentinel the vcs accessors carried, with the same two problems: the check is optional, so forgetting it hands the empty string on to an exec or a path join; and it is untyped, so nothing tells a reader the value needs testing at all. Raising makes the missing tool a case the author has to answer, in the same shape as every other failure in these modules:
try { final vhs = proc\which("vhs"); ... } catch (e) { magus\log.info("vhs not installed"); }
func OsWithEnv ¶
OsWithEnv injects extra env vars for subprocesses spawned during the callback without mutating the daemon's process-global environment. Overrides are propagated via ctx and merged at exec time in applySandboxPolicy.
func OsWithSlots ¶
OsWithSlots reserves n slots from magus's concurrency limiter for the duration of cb, so a callback running its own internally-parallel work (make -j, a test runner) does not oversubscribe the global budget. With no limiter on ctx (e.g. a standalone run) it just invokes cb. Mirrors archive.*: the build slot already held is handed back while the n are reserved, so peak in-flight stays within the cap rather than cap+n.
func PathAbs ¶
PathAbs returns the cleaned absolute form of path, resolved against the context working directory (see resolvePath), not the process cwd - a target running in another project's directory would otherwise get an answer for the wrong place (see vcsDir in vcs.go for the same bug class).
func PathExpandUser ¶
PathExpandUser expands a leading ~ to the current user's home directory. A bare "~" or "~/..." is expanded; "~other" (another user) is left untouched since resolving it needs a user lookup the host deliberately doesn't do.
func PathMatch ¶ added in v0.4.0
PathMatch reports whether path matches a doublestar glob.
Slashes are normalized first: doublestar matches on forward slashes, so a Windows-shaped path would never match a pattern written the way every magusfile writes one.
func PathMatchAny ¶ added in v0.4.0
PathMatchAny reports whether path matches any of the patterns.
func PlatformArch ¶
PlatformArch normalizes an architecture identifier to canonical Go GOARCH and renders it in the requested style. Returns "" for an unrecognized identifier.
func PlatformCPUs ¶ added in v0.4.0
PlatformCPUs returns GOMAXPROCS rather than NumCPU: inside a container with a CPU quota the two disagree, and the quota is what actually bounds the work. Go 1.25 derives GOMAXPROCS from the cgroup limit, so this follows the runtime instead of second-guessing it.
func PlatformMemory ¶ added in v0.4.0
PlatformMemory returns the memory this process may commit in bytes, or 0 when it cannot be determined.
Narrowed by a container's memory ceiling where there is one, for the reason PlatformCPUs below reports GOMAXPROCS rather than NumCPU: inside a container the machine's figure and the one that actually bounds the work disagree, and a magusfile sizing its parallelism wants the second.
Zero is UNKNOWN, not "none". Every caller has to branch on it, which is the honest shape: guessing a size here would make a magusfile's parallelism depend on a number magus invented.
The boundary carries a Go int, so a machine with more memory than an int can hold reports UNKNOWN rather than a truncated figure. That is only reachable on a 32-bit host with over 2GB, where a silently wrapped number would size a magusfile's parallelism off nonsense - see the deferred 32-bit plan.
func PlatformOS ¶
PlatformOS normalizes an OS identifier to canonical Go GOOS and renders it in the requested style. Returns "" for an unrecognized identifier.
func Register ¶
func Register(m Module)
Register adds m to the global module registry. Called from each module's init() so magus-utils bindings and the runtime registration paths can look up modules by name without an import loop.
std/encoding's nine leaf packages do NOT call this: std/encoding imports std for the Module vocabulary, so std registering them back here would import std/encoding in turn and cycle. They are collected explicitly instead (see std/encoding/register.go) and unioned with this registry one layer above std, in internal/hostmodules - the only place that imports both.
func RegisterSource ¶ added in v0.4.0
func RegisterSource(m SourceModule)
RegisterSource adds a Buzz-implemented module to the registry.
It PARSES the source and panics on a failure or on a name collision with a Go module, at init, for the same reason Register validates a Go Impl there: a stdlib module that does not load is a programmer error, and discovering it when a magusfile imports it makes it look like the magusfile's fault.
func SemverCanonical ¶ added in v0.4.0
SemverCanonical returns v in canonical "vX.Y.Z" form.
The leading "v" is what every function in this file that returns a version STRING emits, so a caller can compare two of them directly. Input stays lenient - "1.2", "v1.2.0", and "1.2.0+build" all canonicalize - which keeps it consistent with parse, the module's other entry point.
func SemverCompare ¶
SemverCompare orders a against b, returning -1, 0, or 1.
This is what `compare` means everywhere else - Go's cmp.Compare and strings.Compare, x/mod/semver.Compare, Masterminds' Compare, node-semver's compare - and returning a bool from a function by that name is a trap: an author arriving from any of them guesses three-way ordering, and the guess compiles.
It previously took (a, op, b) and answered whether the relation held. satisfies() covers that now, and covers ranges the operator form could not express, so nothing is lost by giving the name back its usual meaning.
func SemverIsValid ¶ added in v0.4.0
SemverIsValid reports whether v parses as a semantic version.
It exists because the module previously offered no way to ask: magusfile.buzz called `semver\parse(v)` purely for its side effect of raising, which is an exception used as a predicate. Both x/mod (IsValid) and node-semver (valid) carry one.
func SemverMajor ¶ added in v0.4.0
SemverMajor returns the major prefix of v, e.g. "v1" for "1.2.3".
It returns a STRING where parse().major returns an int, and the difference is the point: this is the projection a cache key holds, so two versions sharing a major produce one identical token. Named for golang.org/x/mod/semver.Major, which is the same function and the same spelling the engine calls when narrowing a probe.
func SemverMajorMinor ¶ added in v0.4.0
SemverMajorMinor returns the major.minor prefix of v, e.g. "v1.2" for "1.2.3". See SemverMajor for why it is a string.
func SemverNext ¶ added in v0.4.0
SemverNext returns the three candidate next versions after v: bumping major, minor, or patch. Delegates to Masterminds/semver's Inc* methods rather than hand-rolling "+1" arithmetic: per version.go's IncPatch and semver.org spec item 9, a version WITH a prerelease has IncPatch strip the prerelease and KEEP the patch (v1.2.3-rc1 -> v1.2.3, not v1.2.4) - a subtlety naive arithmetic gets wrong and that this delegation gets free.
func SemverParse ¶
SemverParse parses v into its constituent parts.
func SemverSatisfies ¶ added in v0.4.0
SemverSatisfies reports whether v meets a full constraint RANGE.
compare() takes a single operator and one version, so it cannot express ">= 1.2, < 2.0" - the form magus.yaml's required_version already accepts and the one a magusfile reaches for when gating on a toolchain window.
func SortNatural ¶ added in v0.4.0
SortNatural returns a new list with embedded digit runs compared numerically.
func SortSemver ¶ added in v0.4.0
SortSemver returns a new list ordered by semantic version, oldest first.
func SortStrings ¶ added in v0.4.0
SortStrings returns a new list ordered lexicographically.
func StringsCamelCase ¶
StringsCamelCase converts s to camelCase.
func StringsCapitalize ¶
StringsCapitalize uppercases the first rune of s and lowercases the rest.
func StringsCollapseWs ¶ added in v0.4.0
StringsCollapseWs folds every run of whitespace into a single space and trims.
func StringsCompare ¶ added in v0.4.0
StringsCompare compares a and b lexicographically by byte, returning -1, 0, or 1.
func StringsContains ¶ added in v0.4.0
StringsContains reports whether s contains substr.
func StringsEllipsis ¶
StringsEllipsis trims s to at most length runes, appending "..." when truncated.
func StringsFields ¶ added in v0.4.0
StringsFields splits s around runs of whitespace, discarding empties.
func StringsKebabCase ¶
StringsKebabCase converts s to kebab-case.
func StringsLines ¶ added in v0.4.0
StringsLines splits s into lines on \n, tolerating \r\n and dropping the trailing empty element a final newline produces.
func StringsPadLeft ¶ added in v0.4.0
StringsPadLeft left-pads s with pad until it is length runes wide.
func StringsPadRight ¶ added in v0.4.0
StringsPadRight right-pads s with pad until it is length runes wide.
func StringsPascalCase ¶
StringsPascalCase converts s to PascalCase.
func StringsSnakeCase ¶
StringsSnakeCase converts s to snake_case.
func StringsSplitN ¶ added in v0.4.0
StringsSplitN splits s on sep into at most n pieces.
func StringsTrimPrefix ¶ added in v0.4.0
StringsTrimPrefix removes prefix from the start of s if present.
func StringsTrimSuffix ¶ added in v0.4.0
StringsTrimSuffix removes suffix from the end of s if present.
func StringsUpperFirst ¶ added in v0.4.0
StringsUpperFirst uppercases the first rune of s, leaving the rest untouched.
func StringsWords ¶
StringsWords splits s into its constituent words.
func TemplateRender ¶
TemplateRender renders tmpl against data with Mustache semantics.
func TemplateRenderPartials ¶
func TemplateRenderPartials(_ context.Context, tmpl string, data any, partials map[string]string) (string, error)
TemplateRenderPartials renders tmpl against data with Mustache semantics, resolving {{>name}} includes against partials (a name->template-string map). A StaticProvider drives resolution, so partials may reference one another.
func TermClearScreen ¶ added in v0.4.0
TermClearScreen erases the screen, or does nothing when stderr is not a terminal.
func TermColorize ¶ added in v0.4.0
TermColorize wraps s in style, honoring the terminal's color capability.
func TermIsInteractive ¶ added in v0.4.0
TermIsInteractive reports whether both stdin and stderr are terminals.
BOTH, not either: the picker reads keys from stdin and paints to stderr, so one without the other is a half-usable prompt. `magus run x < /dev/null` on a terminal is the case that makes this concrete - stderr is a TTY, stdin is not, and a prompt would block forever on a read that can never arrive.
func TermNotify ¶ added in v0.4.0
TermNotify raises a notification into the process's terminal band.
It does NOT take a scope. An earlier shape wrapped the call in a term.withNotify(fn) that reserved the rows for the duration of a callback, which read well in a standalone script and was wrong for the case that matters: a magusfile target notifying in the middle of a run does not own the run, cannot wrap it, and would be nesting its scope inside magus's own. The band is owned by the process and released on the way out (tty.ReleaseStderr), so a caller just says the thing.
func TermPick ¶ added in v0.4.0
func TermPick(ctx context.Context, items []string, prompt, initialFilter string, initial, maxRows int) (int, error)
TermPick prompts for a choice among items and returns the chosen index.
func TermSizeOf ¶ added in v0.4.0
TermSizeOf returns the terminal's dimensions, or zeroes when unmeasurable.
func TermWantsColor ¶ added in v0.4.0
TermWantsColor reports whether styled output should be emitted to stderr.
func TimeAdd ¶
TimeAdd adds a Go duration string to a Unix-millis timestamp and returns the resulting Unix-millis value.
func TimeFormat ¶
TimeFormat renders unixMillis (interpreted as UTC) with a Go reference layout.
func TimeNowISO ¶
TimeNowISO returns the current UTC time formatted as RFC 3339. The raw epoch-millis clock value is already available as Buzz's os.time(); this is the formatted-string convenience the time module would otherwise force a caller to build by hand via os.time() + time.format.
func TimeParse ¶
TimeParse parses value with layout into Unix epoch milliseconds. A zoneless layout is read as UTC (Go's default); a zoned one (RFC 3339's Z07:00) is normalized to the UTC instant.
func TimeParseDuration ¶
TimeParseDuration parses a Go duration string into whole milliseconds.
func UUIDRandomHex ¶
UUIDRandomHex returns n random bytes as a lowercase hex string.
func UUIDRandomToken ¶
UUIDRandomToken returns n random bytes as an unpadded URL-safe base64 string.
func ValidateModule ¶ added in v0.4.0
ValidateModule checks m's Fields and Methods against their declared Impls, the same check Register runs before storing a module. std/encoding's leaf packages call it directly (see std/encoding/register.go) because they cannot call Register itself without cycling back through std - see Register's doc for why - but a malformed descriptor should still fail fast at the same place in the program's life as every other module's, not silently reach codegen as bad data.
func VcsChangedFiles ¶ added in v0.4.0
VcsChangedFiles lists files changed against base, defaulting to the resolved base ref.
Paths carry the repository root as their base: a VCS reports diff paths from the root while a target runs in its project directory.
The probe runs at EffectiveCwd, matching resolveVCS and vcs.status. An empty dir means the PROCESS cwd, which would resolve the driver from the target's directory and then run it somewhere else - identical only while both sit in the same repository.
func VcsCmd ¶ added in v0.4.0
VcsCmd runs the active VCS binary with args.
This replaced vcs.exe, which handed back a PATH and left every caller to write proc.exec(<the vcs binary>, [...]) - two calls, and a silent no-op when the path came back empty because no VCS was resolved. Returning an ExecResult also puts the escape hatch on the same typed footing as magus.cmd and proc.exec instead of a bare string. "exe" was the wrong word besides: it reads as a Windows file extension, and the value is a binary on every platform magus runs.
func VcsCommit ¶
VcsCommit resolves rev (empty = current revision) to its commit object. It RAISES when no VCS is resolved and RAISES when the revision can't be looked up - a caller uses vcs.name() to test for a VCS first, and try/catch for a revision that may not exist.
func VcsDescribe ¶
VcsDescribe returns a human-readable version string from the nearest tag (see the driver Describe methods), or "" when no VCS is resolved or the backend has no describe concept. It RAISES when the query fails - "" is reserved for the two no-op cases above, not for a probe that could not run.
func VcsDirtyDiff ¶ added in v0.4.0
VcsDirtyDiff returns the working tree's uncommitted diff for paths. Unlike VcsIsDirty this does NOT raise on a failed probe: it is a diagnostic printed beside a failure that has already been decided, so a backend that cannot produce a diff must not become the reason the build fails. "" reads as "no diff to show".
func VcsHistory ¶
VcsHistory returns up to limit recent commits (newest first) as objects, or an empty list when no VCS is resolved. It RAISES when the query fails - an empty list there would read as "no history" for "could not read history".
func VcsIsDirty ¶
VcsIsDirty reports whether the working tree has uncommitted changes.
func VcsName ¶
VcsName returns the active VCS short name (e.g. "git"), or "" if unresolved. Resolution is per call and honors the call's cancellation; resolveVCS caches on cwd, so repeated reads cost a mutex rather than a probe.
func VcsRef ¶ added in v0.4.0
VcsRef returns the movable name at the current revision (a git branch, an hg named branch, a jj bookmark); raises when no VCS or metadata is available.
func VcsStatus ¶ added in v0.4.0
VcsStatus reports the working tree's uncommitted state as a typed Status.
Handing a magusfile the backend's own status lines - git porcelain, hg status, jj diff --name-only - would make every caller reimplement the parsing and know which VCS it is on. DirtyFiles answers in paths, so there is nothing here to reimplement.
Paths carry the repository root as their base: a VCS reports from the root while a target's cwd is its PROJECT directory, so a bare string was ambiguous exactly when a project was not the root.
RAISES on a failed probe rather than reporting clean: a gate that cannot read the tree has no answer, and a quiet empty list would let it pass having checked nothing.
func VcsTags ¶ added in v0.4.0
VcsTags returns the repository's tags newest-first, filtered by pattern. An empty list when no VCS is resolved; a failed query is returned, not swallowed.
func WithCwd ¶
WithCwd returns ctx carrying dir as the default working directory for the os.* exec primitives. An empty dir is a no-op. It also propagates the cwd to Buzz's own stdlib (gopherbuzz io/fs/os) so a magusfile that uses the language built-ins - io.File, fs.list, os.execute - resolves relative paths against the project dir too, not just the magus host modules. magus extends Buzz's standard library; it does not replace it.
Types ¶
type Analyzer ¶ added in v0.4.0
type Analyzer interface {
Hotspots(ctx context.Context, opts types.InsightOptions) (types.HotspotOutput, error)
Affinity(ctx context.Context, opts types.InsightOptions) (types.AffinityOutput, error)
Ownership(ctx context.Context, opts types.InsightOptions) (types.OwnershipOutput, error)
Trend(ctx context.Context, opts types.InsightOptions) (types.TrendOutput, error)
Volatility(ctx context.Context) (types.VolatilityReport, error)
Unreferenced(ctx context.Context) (types.UnreferencedOutput, error)
}
Analyzer is the workspace's codebase-analytics surface: the VCS-history lenses plus the two that read the knowledge graph. Deliberately the narrow set the report needs rather than the whole of *Magus - a wide interface here would re-couple std to the shape of the package it cannot import.
func AnalyzerFromContext ¶ added in v0.4.0
AnalyzerFromContext recovers the analytics surface from the workspace on ctx.
Two distinct absences, and the caller wants to tell them apart: no workspace at all (a `magus buzz` script outside one), or a workspace whose implementation does not analyze - a test double, or a provider-supplied workspace that models projects without git history. Both report false here; the caller's message names the first, which is the one a reader can act on.
type Arg ¶
type Arg struct {
Name string
Type TypeTag
Optional bool
Variadic bool
// Default is used when Optional is true and the caller omits the arg.
// Must be of the Go type matching Type, or nil for "zero value".
Default any
// Object names the Buzz object this argument's map must match, for an arg whose
// Type is TypeAnyMap but whose shape is a declared boundary type. It changes only
// the DECLARED signature the checker reads: a Buzz object is a map at runtime, so
// the Impl still receives map[string]any and needs no decoder.
//
// Without it an argument like encoding.build_url's `parts` reads as {str: any} even
// though parse_url returns a URL - so the round trip was typed in one direction and
// untyped in the other, and a caller could pass any map at all.
Object string
// Enum names the Buzz enum this argument's string must be a case of, for an arg
// whose Type is TypeString but whose values are a closed set. Like Object it
// changes only the DECLARED signature: an enum case is a string at runtime, so
// the Impl still receives a string and needs no decoder.
//
// The name must be a boundaryEnums entry in cmd/magus-utils. Without it a
// closed-set parameter reads as `str`, and naming an algorithm magus does not
// implement is a runtime throw rather than something the checker catches.
Enum string
}
Arg is one positional parameter of a Method.
type Callback ¶
Callback is the host-side handle for a VM-side function value passed as an argument. The generated bindings layer wraps a buzz.Session + function value. Impls invoke the callback via Call; args are marshaled per VM convention.
type Field ¶
type Field struct {
Name string
Doc string
Type TypeTag
// Resolver is `func() (T, error)` or `func(context.Context) (T, error)`
// where T matches Type. Called once per Session registration.
Resolver any
}
Field is a static, table-level value on a Module: resolved once at registration and stored as a plain value on the module's Buzz map, so a caller reads it without invocation.
NO MODULE USES THIS, and TestNoModuleDeclaresFields keeps it that way. A Field generates no extern declaration - Buzz has `extern fun` and no extern value - so the checker cannot type it, and a caller who writes the parens gets a runtime "str is not callable". Declare a constant as a Method returning it.
The type stays because magus-docs, langservice-manifest and ModuleFieldEntry all render Fields, and dropping it would change a Buzz-visible introspection shape.
type Method ¶
type Method struct {
// Name is the canonical snake_case identifier (e.g. "read_file"); the Buzz
// surface exposes it as camelCase derived from this (readFile).
Name string
// BuzzName, when non-empty, is the verbatim Buzz-surface name, overriding the
// camelCase derivation from Name. The magus DSL keeps a few snake_case
// primitives (has_charm) that magusfiles and the static charm extractor match
// by literal name; those set BuzzName so codegen doesn't rewrite them.
BuzzName string
// Doc is a one-line description used in generated .d.ts comments.
Doc string
// Args lists positional parameters in declaration order. Variadic, if
// present, must be the last arg.
Args []Arg
// Returns lists return values. An error is always implicit on Impls
// and surfaces as a Buzz runtime error; do not list it here.
Returns []Ret
// Raises is authored, not derived: true when Impl can return a non-nil error
// to the Buzz caller under real inputs (not merely when its signature ends in
// `error` - many Impls declare one and never return it non-nil). moduledecls
// reads this to emit `!> any` on the generated extern declaration, which is
// what makes the checker's propagate-or-catch enforcement (BZZ1006) apply to
// host calls. Get this wrong and either a caller loses a real error path
// unchecked (false), or every call site needs a needless try/catch (true).
Raises bool
// Impl is the typed Go function bound by this Method. Codegen reflects
// over it to discover its package-qualified name and validates that its
// signature matches Args + Returns + (error). Nil only when Extern is set.
Impl any
// Extern marks a member DECLARED here but BOUND ELSEWHERE - today by
// internal/interp/bindings, which MapSets it onto the module's namespace value
// at run time. It carries no Impl and gets no generated trampoline; what it
// gets is a declaration.
//
// The name is upstream Buzz's `export extern fun`: a signature whose
// implementation the host binds.
//
// It exists because a member needing a dynamic binding - one closing over
// per-Open state, or binding differently per surface - otherwise had to be
// MapSet and was invisible to the checker. That is how `crypto\hash`, a
// function the crypto module has never had, type-checked inside a spell.
//
// Set it only when something really does bind the member at run time: an Extern
// with nothing behind it declares a member that does not exist.
Extern bool
}
Method declares one host function bound into the VM.
type Module ¶
type Module struct {
// Name is the identifier a magusfile calls the module by: `json` in
// `json\parse(...)`. It is also the key for everything derived from the
// module - the generated trampoline's Go function name, its file name, the
// Modules registry key - so it must stay a legal identifier.
Name string
// Path is the import spelling, when it differs from Name: `encoding/json` for
// the module bound as `json`. Empty means the two are the same, which is the
// case for every top-level module.
//
// The split exists because Buzz binds a slash-path import under its BASENAME
// (gopherbuzz/session.go's resolveImport). The path spells the import line; the
// name is what call sites write and what codegen puts in a Go identifier -
// conflating them would generate RegisterEncoding/json.
//
// Two modules may not share a Name even under different paths: Register panics,
// correctly, since a magusfile cannot import both unaliased anyway.
Path string
Doc string
Fields []Field
Methods []Method
// Namespaces are member groups the runtime assembles; see Namespace.
Namespaces []Namespace
// WASM marks a module the browser playground can install: pure compute, with no
// process, filesystem, or network access. It is what
// internal/interp/bindings/gen's WASM capability is generated FROM.
//
// Declared here because it is a fact about the MODULE. It previously lived as
// prose in doc comments and a hand-maintained table in bindings/gen carrying
// "keep this in sync" - a mirror that could drift with no test to check it.
//
// uuid is WASM despite generating randomness: the browser supplies
// getRandomValues. The test is whether the BROWSER can provide it, not whether
// the operation sounds pure.
WASM bool
}
Module is a named collection of Fields + Methods imported under the module's bare name: after `import "fs"`, fs.glob; after `import "os"`, proc.exec. magus layers these methods onto Buzz's own stdlib module of the same name.
func All ¶
func All() []Module
All returns a snapshot of every registered Module, in unspecified order.
func SourceModulesAsModules ¶ added in v0.4.0
func SourceModulesAsModules() []Module
SourceModulesAsModules projects every registered SourceModule into the Module shape, so a consumer that renders modules - the docs generator, the langservice manifest - handles both kinds through one code path instead of growing a second branch that drifts.
The Methods are derived from the Buzz source; Impl is nil on every one, which is the only observable difference and matters to nothing downstream: codegen reads std.All(), never this.
func (Module) ImportPath ¶ added in v0.4.0
ImportPath returns the path a magusfile imports this module by: Path when it declares one, otherwise Name.
type Namespace ¶ added in v0.4.0
type Namespace struct {
Name string
Doc string
// Methods are all Extern by construction: the whole point of a Namespace is that
// the runtime assembles it, so there is no Impl to generate a trampoline from.
// Validation enforces it rather than trusting the author.
Methods []Method
}
Namespace is a group of related host functions a module exposes behind one of its members: `magus\cache.remote(...)`, `magus\secret.read(...)`.
NOT a nested module: Buzz has no nested namespace, and the call site says so - `magus\cache.remote(x)` is a backslash (namespace access) then a DOT (member access on a value). The group is an OBJECT held by the module, upstream's own shape.
Declaring it buys checking a bare value cannot: an object reports an unknown member, so a typo is a load error instead of a null reaching the VM. The runtime binding is unchanged - a Buzz object IS a map at run time.
type Ret ¶
type Ret struct {
Name string
Type TypeTag
// Object names the Buzz object this return marshals to, for a method whose
// Impl returns a Go struct carrying BuzzObject (or a slice of them). Empty for a
// scalar return.
//
// Documentation for the CHECKER and the reader, not a marshaling instruction:
// the generator already recognizes an object by reflecting on the Impl. What
// was missing is the NAME - without it a method's return types as {str: any}
// outside the generator, so nothing checks the field names.
//
// The generator validates this against the reflected Impl and fails codegen on
// a mismatch, so it cannot drift from the struct it names.
Object string
// Enum names the Buzz enum this return's string is a case of. Same role as
// Arg.Enum, on the way out.
Enum string
}
Ret is one return value of a Method.
type SourceModule ¶ added in v0.4.0
type SourceModule struct {
// Name is the identifier the module binds as, exactly like Module.Name.
Name string
// Path is the import spelling when it differs from Name.
Path string
// Doc is the one-line module summary for `magus describe modules`.
Doc string
// Source is the Buzz implementation, normally a go:embed of a .buzz file.
Source string
}
SourceModule is a stdlib module written in BUZZ rather than in Go.
WHY THIS EXISTS. Every other module here is a Go implementation with a generated Buzz trampoline, which means extending the standard library requires writing Go, rebuilding the binary, and shipping a release. That is the right trade for anything on a hot path - a Go binding beats an equivalent Buzz implementation by 9-13x on string work and 2.2x even with no boundary to cross (internal/interp/bindings/hostvsbuzz_bench_test.go) - and the wrong one for everything else:
- POLICY that should be readable by the people it governs. tools/toolchain.buzz says so in its own header: "NOTHING HERE IS IN THE MAGUS BINARY, and that is the point ... it lives in the workspace so its trust decisions are yours to read and revise."
- CONTRIBUTIONS from someone who writes magusfiles and not Go.
- ITERATION without a rebuild.
HOW IT WORKS, and why it needs almost nothing. gopherbuzz already executes a declaration module that carries no native value - that is how `magus/spell` gives spells their Command/Target types, and how upstream's own assert/suite/ testing modules are shipped. Real Buzz source, executed on import, exported functions and object types, fully type-checked. The language side was already finished; what was missing was any way for magus to REGISTER one, which meant nothing downstream could see it.
THE DESCRIPTOR IS DERIVED, never hand-written. Parsing the source for its exported functions costs one parse at init and makes drift impossible; a Go descriptor sitting beside the .buzz would be a second source of truth that silently disagrees the first time someone edits one and not the other.
func AllSource ¶ added in v0.4.0
func AllSource() []SourceModule
AllSource returns a snapshot of every registered SourceModule.
func GetSource ¶ added in v0.4.0
func GetSource(name string) (SourceModule, bool)
GetSource returns the SourceModule registered under name.
func (SourceModule) ImportPath ¶ added in v0.4.0
func (m SourceModule) ImportPath() string
ImportPath returns the path a magusfile imports this module by.
type StatusError ¶ added in v0.4.0
type StatusError struct {
Method string
URL string
Status int
// Body is carried only under fail_with_body, matching curl --fail-with-body.
Body string
}
StatusError is what `fail`/`fail_with_body` raises when a response is >= 400.
It exists so a Buzz caller can BRANCH on the status. The thrown value used to be `{"message": "http.get https://...: server returned 404"}` and nothing else, so telling a 404 (this resource does not exist, which is often a normal answer) from a 500 (the server is broken, which is not) meant substring-matching English prose - and prose is the one part of an error nobody promises to keep stable.
Additive: `message` reads exactly as before, so anything matching on it still works.
func (*StatusError) BuzzError ¶ added in v0.4.0
func (e *StatusError) BuzzError() map[string]string
BuzzError renders the error as the map a Buzz `catch` receives.
func (*StatusError) Error ¶ added in v0.4.0
func (e *StatusError) Error() string
type TypeTag ¶
type TypeTag int
TypeTag classifies the shape of a value crossing the VM boundary. Each tag has a canonical Go type that Impls accept (for args) or return; codegen emits per-VM marshaling that produces or consumes that Go type.
const ( TypeInvalid TypeTag = iota TypeString TypeInt TypeFloat TypeBool TypeStringSlice // TypeFloatSlice is a list of numbers. Buzz has one numeric list, so an int // list and a double list are the same value crossing here; the Impl takes // []float64 and a caller that means integers rounds on the way out. TypeFloatSlice // TypeByteSlice is raw bytes, crossing as a Buzz list of ints rather than a // str. A Buzz string is rune-oriented, so arbitrary binary - a NUL, a 0xFF - // does not survive a round trip through one; anything that must move bytes // opaquely (an HMAC digest, a decoded base64 payload) uses this. TypeByteSlice // TypeStringSliceSlice is a list of string lists, e.g. csv.parse's rows of // fields. TypeStringSliceSlice TypeStringMap // TypeStringMapMap is a map of string maps, e.g. ini.parse's sections of // key-value pairs. TypeStringMapMap TypeAnyMap TypeFunc TypeAny // TypeIndex is an int that names a position in a list. Buzz lists are // 0-based, matching the Go Impl, so the index needs no offset on the way in // or out (-1 means "not found"). The distinct tag is kept so a VM with a // different convention can be translated in one place if one is ever added. TypeIndex )
The TypeTag constants enumerate the parameter and return types a binding field or method can declare; TypeInvalid is the zero value.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package encoding aggregates the nine text-codec host modules that live under std/encoding/*: base64, csv, hex, ini, json, toml, url, xml, yaml.
|
Package encoding aggregates the nine text-codec host modules that live under std/encoding/*: base64, csv, hex, ini, json, toml, url, xml, yaml. |
|
base64
Package base64 is the "encoding/base64" host module: base64 lives under std/encoding rather than in std's own flat root because it, like its eight siblings, uses none of std's shared sandbox/exec helpers (resolvePath, checkRead, checkWrite, optStringDefault, ...) - every helper a text codec needs is local to its own file, so splitting it into its own package hides nothing that std itself needs to reach back into.
|
Package base64 is the "encoding/base64" host module: base64 lives under std/encoding rather than in std's own flat root because it, like its eight siblings, uses none of std's shared sandbox/exec helpers (resolvePath, checkRead, checkWrite, optStringDefault, ...) - every helper a text codec needs is local to its own file, so splitting it into its own package hides nothing that std itself needs to reach back into. |
|
csv
Package csv is the "encoding/csv" host module.
|
Package csv is the "encoding/csv" host module. |
|
hex
Package hex is the "encoding/hex" host module.
|
Package hex is the "encoding/hex" host module. |
|
ini
Package ini is the "encoding/ini" host module.
|
Package ini is the "encoding/ini" host module. |
|
json
Package json is the "json" host module.
|
Package json is the "json" host module. |
|
toml
Package toml is the "toml" host module.
|
Package toml is the "toml" host module. |
|
url
Package url is the "encoding/url" host module.
|
Package url is the "encoding/url" host module. |
|
xml
Package xml is the "xml" host module.
|
Package xml is the "xml" host module. |
|
yaml
Package yaml is the "yaml" host module.
|
Package yaml is the "yaml" host module. |