Documentation
¶
Overview ¶
Package template implements clawk's resource file language.
A clawk.mod is a list of typed blocks — `sandbox [<name>] ( ... )`, `policy <name> ( ... )`, `namespace <name> ( ... )` — one filename for every shape. The sandbox block is a template describing a reusable sandbox (provider, repositories, network policy). It is NOT tied to a particular branch — the branch is supplied at `clawk work` time, so the same template can spawn many per-ticket sandboxes. A sandbox block with `includes` is a multi-repo workspace root; without it, the file configures its own repo.
Example workspace clawk.mod:
sandbox acme (
vm (
provider vz
)
includes (
~/code/k8s-deploy
~/code/monorepo
)
network (
use default corp-egress
allow github.com
allow *.github.com
allow ip 10.0.0.5
allow ip 192.168.10.0/24
)
)
policy corp-egress (
allow ip 10.20.0.0/16
deny telemetry.corp.com
)
Index ¶
- Constants
- Variables
- func ExpandPath(p string) (string, error)
- func FindGitRepo(dir string) (string, error)
- func GlobalModPath() (string, error)
- func IsResolvedVersion(v string) bool
- func IsTidyInputVersion(v string) bool
- func MigrateFlat(src string) (string, error)
- func RetiredWorkspaceFileError(path string) error
- type AgentDoc
- type File
- type FileSpec
- type Global
- type MCPSpec
- type NamespaceDef
- type PolicyDef
- type Repo
- type SerialSpec
- type ShareSpec
- type SkillKind
- type SkillRef
- type SkillResolver
- type SkillResolverFunc
- type SkillRewrite
- type Template
- type TidyResult
- type Token
- type TokenKind
- type Workspace
- func FindWorkspace(dir string) (*Workspace, error)
- func FindWorkspaceWithProfile(dir, profile string) (*Workspace, error)
- func LoadClawkfilePathWithProfile(path, profile string) (*Workspace, error)
- func LoadStandaloneClawkfile(dir string) (*Workspace, error)
- func LoadStandaloneClawkfileWithProfile(dir, profile string) (*Workspace, error)
- func LoadWorkspace(path string) (*Workspace, error)
- func LoadWorkspaceWithProfile(path, profile string) (*Workspace, error)
- func WorkspaceFromGitRepo(dir string) (*Workspace, error)
Constants ¶
const FormatVersion = 1
FormatVersion is the newest clawk.mod format version this parser understands. A file may declare the version it requires with a top-level `clawk <n>` directive; a file without one is version 1. Bump this only for changes an older parser would silently misread — purely additive directives don't need a bump, because older parsers already reject unknown directives by name with a clear error.
const GlobalModEnvVar = "CLAWK_GLOBAL_MOD"
GlobalModEnvVar names the environment variable that overrides the host-wide defaults file outright. Pointing it at a file makes a run reproducible regardless of what the host happens to have in ~/.config; pointing it at a path that does not exist is an error rather than a silent fall-through, so a typo in CI surfaces.
Named for the layer it overrides, so it pairs with --no-global and can't be misread as "the repo's clawk.mod", and prefixed like every other variable clawk reads (CLAWK_DEBUG, CLAWK_NET_MODE, CLAWK_MAX_VZ_DEVICES) so it doesn't collide with an unrelated ROOT_* in someone's shell.
const RepoFileName = "clawk.mod"
RepoFileName is the single clawk config filename. Whether it describes a multi-repo workspace or one repo is structural: a sandbox block with `includes ( ... )` is a workspace root. Profiles extend a clawk.mod with a "clawk.mod.<profile>" overlay file.
const RetiredWorkspaceFileName = "clawk.work"
RetiredWorkspaceFileName is the pre-cutover multi-repo filename. It is never loaded — the loaders recognise it only to emit a rename hint.
Variables ¶
var ErrAlreadyTyped = errors.New("file already uses the typed-block grammar")
ErrAlreadyTyped reports that a file already parses as the typed-block grammar and needs no migration.
var ErrGlobalMod = errors.New("host-wide clawk.mod")
ErrGlobalMod marks every OTHER host-wide-layer failure: unreadable, unparseable, out-of-scope directive, two candidate locations. Callers that try loaders in sequence (see the cli's resolveSource, which walks workspace → standalone → bare-git-repo and treats a failure as "not this shape") must test for it and surface it instead of moving on: a broken defaults file is not a hint to try somewhere else, and degrading to "no defaults" would hand back a sandbox quietly missing half its configuration.
var ErrNoGlobalMod = errors.New("no host-wide clawk.mod")
ErrNoGlobalMod reports that no host-wide defaults file exists. Not a failure — the overwhelmingly common case is a host that never wrote one.
var ErrNoWorkspace = errors.New(
"no workspace clawk.mod (sandbox block with 'includes') found in this directory or any parent")
ErrNoWorkspace is returned when FindWorkspace can't find a workspace clawk.mod (a sandbox block with `includes`) in dir or any ancestor.
var ErrSkipResolution = errors.New("skip resolution")
ErrSkipResolution is the sentinel a SkillResolver returns to leave a skill untouched. Local skills always skip; distributed skills with an already-pinned version may skip.
var GlobalDisabled bool
GlobalDisabled skips the host-wide layer entirely — wired to `--no-global`. A package var rather than a parameter threaded through nine loader signatures: it is written once from PersistentPreRunE before any load, and tests set and restore it around a call.
Functions ¶
func ExpandPath ¶
ExpandPath resolves leading ~ (home dir) and $HOME. It does NOT make the path absolute — callers decide the base directory to resolve against. In workspace mode that's the workspace root.
Other env vars are NOT expanded: we don't want to silently substitute host-side secrets into template-visible paths.
func FindGitRepo ¶
FindGitRepo returns the nearest ancestor of dir containing a .git entry, inclusive of dir itself. Returns an error if no .git is found.
func GlobalModPath ¶ added in v0.4.0
GlobalModPath resolves the host-wide defaults file, in order:
$CLAWK_GLOBAL_MOD explicit override (must exist) $XDG_CONFIG_HOME/clawk/clawk.mod default ~/.config/clawk/clawk.mod ~/.clawk/clawk.mod compatibility fallback
~/.config is the primary home because this file is the one thing in clawk's footprint a user hand-edits, symlinks out of a dotfiles repo and would be annoyed to lose: ~/.clawk is disposable machine state (VM disks, an image cache, per-sandbox records, a live OAuth token) that people exclude from backups and delete to start clean. Config must not be collateral.
Deliberately NOT os.UserConfigDir(): on darwin that is ~/Library/Application Support, which is the wrong place for a dotfile-managed text file. Honouring $XDG_CONFIG_HOME with a ~/.config fallback on every platform is what gh, git and nvim do on macOS, and what anyone writing this file expects.
Both non-env locations present is an error, never a silent precedence pick.
func IsResolvedVersion ¶
IsResolvedVersion reports whether v is in a form that tidy would leave alone — a semver tag or a pseudo-version. Anything else (a branch name, "latest", a short SHA) is a tidy input that needs to be rewritten before the file can be considered pinned.
func IsTidyInputVersion ¶
IsTidyInputVersion reports whether v is a valid version-shaped token the user might write before tidy runs: branch names, "latest", short SHAs. A return of false means the parser should reject the token as not a version at all.
func MigrateFlat ¶
MigrateFlat rewrites a pre-cutover flat clawk.mod (or clawk.work) body into the typed-block grammar: the top-level `name X` directive, if any, moves into the sandbox header and everything else is indented into `sandbox [X] ( … )`. The transform is text-level so comments, blank lines, and alignment survive verbatim; the result is validated with ParseFileString before being returned, so a file the migrator can't handle errors instead of being rewritten into something broken.
Input that already parses as the typed grammar returns ErrAlreadyTyped.
func RetiredWorkspaceFileError ¶
RetiredWorkspaceFileError is the loader-level rename hint produced when a clawk.work is encountered anywhere the old loader accepted one.
Types ¶
type AgentDoc ¶
AgentDoc is one entry in an `agent (...)` block — a unit of persistent instructions or memory seed. Exactly one field is set: Text is inline markdown from a quoted string (one line); Path is a markdown file, relative to the clawk.mod directory, whose content is read at compose. The file form is how multi-line markdown is carried — its backticks, fences and quotes never have to survive the DSL's string lexing.
type File ¶
type File struct {
Sandbox *Template // nil when the file defines no sandbox
Policies []PolicyDef
Namespaces []NamespaceDef
// FormatVersion is the value of the file's `clawk <n>` directive;
// zero when the directive is absent (which means version 1). Files
// requiring a version newer than FormatVersion fail at parse with an
// upgrade hint, so a future format change degrades into a readable
// error on old clawks instead of silent misparsing.
FormatVersion int
}
File is the parsed form of a typed-block clawk file: a sequence of `<kind> [<name>] ( ... )` blocks with kinds sandbox, policy and namespace. There is no implicit root document and no flat top-level directives — a clawk.mod IS its block list. See ParseFileString for the migration story away from the legacy flat grammar.
func ParseFileString ¶
ParseFileString parses the typed-block grammar. Exactly one sandbox block is allowed per file (a second is an error at its line). Legacy flat files (any known top-level directive like `name`, `vm`, `network` outside a typed block) produce an error that includes a migration hint: wrap the body in `sandbox ( ... )` and move `name <x>` into the header.
type FileSpec ¶
type FileSpec struct {
HostPath string
GuestPath string
Mode fs.FileMode
// Line/Col record where the entry appeared so duplicate-guest-path
// conflicts can be reported back to the right line.
Line, Col int
}
FileSpec is one entry in a `files (...)` block: a host file copied into the guest at sandbox create and re-pushed on every `clawk up`. Snapshot semantics — edits on the host propagate when the user next runs `up`, not live. Use ShareSpec for files that rotate underneath (AWS STS, etc.).
- HostPath: host-side path. Tilde and $VAR are expanded at compose time, not at parse time, because the parsing host (CI) and the run host (laptop) may differ.
- GuestPath: absolute guest path. Empty falls back to HostPath verbatim (with ~ resolved to the guest agent's $HOME).
- Mode: zero = preserve the host file's mode; non-zero overrides.
type Global ¶ added in v0.4.0
type Global struct {
// Path is the file it came from, for the note printed at create.
Path string
// Template is the file's sandbox block with every host-side path made
// absolute against Path's directory (see absolutiseHostPaths), so it can
// be merged under a repo template that resolves paths against its own
// root.
Template *Template
// Policies are `policy <name> ( … )` blocks declared beside it — a
// personal policy library, registered by the create paths exactly like a
// repo's own.
Policies []PolicyDef
// ProfileMatched reports whether a clawk.mod.<profile> overlay beside the
// global file existed and was applied, so a profile satisfied only by the
// host-wide layer is not reported as matching nothing.
ProfileMatched bool
}
Global is a loaded host-wide defaults layer.
func LoadGlobal ¶ added in v0.4.0
LoadGlobal is LoadGlobalWithProfile with no profile.
func LoadGlobalWithProfile ¶ added in v0.4.0
LoadGlobalWithProfile loads and validates the host-wide defaults layer, applying a clawk.mod.<profile> overlay beside it when profile is non-empty. Returns ErrNoGlobalMod when there is no such file (or --no-global was passed) — callers treat that as "no defaults", not as a failure.
type MCPSpec ¶ added in v0.4.0
type MCPSpec struct {
Name string
Transport string // config.MCPTransport* value
URL string
Command []string
Headers []string
Env []string
// Line/Col record where the entry appeared so a conflict between two
// repos declaring the same server name is reported at the right line.
Line, Col int
}
MCPSpec is one entry in an `mcp (...)` block: an MCP server the project wants available to the agent inside the sandbox. Written as
<name> <url> http transport (the default) <name> http|sse <url> explicit remote transport <name> stdio "<command> <args...>" a local server process
followed by any number of `header "Name: value"` and `env NAME` modifiers on the same line.
Credential values never appear here — a `${VAR}` inside a header (or the implied `NAME=${NAME}` of an env entry) is kept verbatim and expanded by the runner inside the guest. See config.MCPServer.
type NamespaceDef ¶
type NamespaceDef struct {
Name string // required
Template *Template // body parsed with the existing template grammar subset
}
NamespaceDef is one `namespace <name> ( ... )` block: a named overlay of the per-namespace template subset (network / files / shares / mcp / env / agent). VM shape, includes and lifecycle hooks are sandbox-level concerns and are rejected inside a namespace body.
type PolicyDef ¶
type PolicyDef struct {
Name string // required for policy blocks
// Allow/Deny lists use the same entry grammar as a `network ( ... )`
// block: bare domains and `ip <ADDR>` literals/CIDRs.
AllowDomains, AllowIPs, DenyDomains, DenyIPs []string
// Sources are URLs of external blocklists (`source "<url>"` entries),
// fetched and expanded into denied domains by the caller at apply time.
Sources []string
// Refresh is how often Sources are re-fetched, from `refresh <dur>`.
// Zero = none (fetch once).
Refresh time.Duration
// Line/Col record where the block appeared so cross-block conflicts
// (duplicate policy names, dangling `use` references) can be reported
// back to the right line by the caller.
Line, Col int
}
PolicyDef is one `policy <name> ( ... )` block: a named, reusable network egress policy referenced from network blocks via `use <name>`. Resolution of references happens at compose time, not parse time — a clawk file may use a policy defined elsewhere (e.g. registered by `clawk apply`).
type Repo ¶
type Repo struct {
Name string // human-facing name used by --only and display
Path string // absolute path on host (the `includes` entry, resolved)
RepoPath string // absolute path to the containing git repo
Clawkfile *Template // nil if the repo has no Clawkfile at its root
}
Repo is one git repository brought into a sandbox by a workspace. The Clawkfile (if present at the repo root) carries that repo's allow / forwards / setup.
type SerialSpec ¶ added in v0.4.0
type SerialSpec struct {
HostPath string
GuestName string
// Line/Col record where the entry appeared so duplicate-name conflicts
// can be reported back to the right line.
Line, Col int
}
SerialSpec is one `serial (...)` entry: a host serial port presented as a device inside the guest.
- HostPath: the device on this machine, e.g. /dev/cu.usbmodem1101. May be a glob, resolved when the port is opened rather than now.
- GuestName: the bare name the device appears under in the guest's /dev. Empty defaults to the host device's basename — except for a glob, which has no basename and must name one explicitly.
type ShareSpec ¶
type ShareSpec struct {
ShareSpec is one entry in a `shares (...)` block: a host directory live-mounted into the guest via virtio-fs. Edits on the host are visible inside the guest without clawk involvement — the host owns the file lifecycle.
Adding or removing a share requires `clawk down && clawk up` so the provider re-emits its virtio-fs device list. Disk state is preserved across that cycle; `clawk destroy` is not required.
type SkillKind ¶
type SkillKind int
SkillKind classifies the resolution path of a SkillRef.
func ClassifySkillPath ¶
ClassifySkillPath maps a raw entry path to its SkillKind. It does not touch the filesystem — the classification is purely lexical so the parser stays decoupled from disk state.
type SkillRef ¶
type SkillRef struct {
// Path is the raw entry as written in clawk.mod, before any
// expansion of ~ or $HOME. Tidy preserves it verbatim and only ever
// rewrites Version.
Path string
// Version is empty for local skills. For distributed skills it is
// either a tag ("v1.2.3"), a Go-style pseudo-version
// ("v0.0.0-yyyymmddhhmmss-12charSHA"), or — only as input to tidy —
// a branch name / "latest" / short SHA that tidy will resolve and
// rewrite.
Version string
// Kind classifies the path shape. Set by ClassifySkillPath; the
// parser populates it after each entry is read.
Kind SkillKind
// Line / Col anchor diagnostics back to the source. The rewriter
// also uses them to find the byte range to patch.
Line int
Col int
// VersionLine / VersionCol point at the version token, or zero
// when no version was written. The rewriter uses these to splice
// resolved versions in place.
VersionLine int
VersionCol int
}
SkillRef is a single entry in a `skills (...)` block.
Three shapes are valid:
- SkillKindLocalHome: ~/foo or $HOME/foo — a path under the user's home directory. Versions are forbidden.
- SkillKindLocalWorkspace: ./foo — a path relative to the workspace root. Versions are forbidden.
- SkillKindDistributed: <host.tld>/path — a remote skill addressed like a Go module. Versions are required after `clawk mod tidy`; before tidy, branch names and the literal "latest" are accepted as resolution inputs that will be rewritten.
type SkillResolver ¶
SkillResolver turns a SkillRef into a pinned version. The returned string must satisfy IsResolvedVersion or RewriteSkillVersions errors — half-resolved values would defeat the whole point of tidy.
Returning ErrSkipResolution leaves the entry untouched (used for local skills, which never carry a version).
var StubRemoteResolver SkillResolver = SkillResolverFunc(func(s SkillRef) (string, error) { if s.Kind != SkillKindDistributed { return "", ErrSkipResolution } if IsResolvedVersion(s.Version) { return "", ErrSkipResolution } return "", fmt.Errorf( "remote skill resolution is not yet implemented; pin %s manually with a vMAJOR.MINOR.PATCH tag", s.Path) })
StubRemoteResolver is the resolver used by the initial implementation: it pins nothing, just shapes the error so the user sees an actionable "implement me" rather than a silent miss. Replaced by a real git-fetch resolver in a follow-up.
type SkillResolverFunc ¶
SkillResolverFunc adapts a plain function into the SkillResolver interface. Useful for tests and the stub resolver below.
type SkillRewrite ¶
type SkillRewrite struct {
Path string
OldVersion string // empty when a version is being inserted for the first time
NewVersion string
Line int
InsertedNew bool // true when no version token existed before the rewrite
}
SkillRewrite is a single (path → new version) rewrite that happened.
type Template ¶
type Template struct {
Name string // repo name override (defaults to dir basename)
// SandboxName is the block-header name from the typed-block grammar
// (`sandbox <name> ( ... )`, see ParseFileString). Empty when the header
// omits it — the loaders fold a non-empty header into Name, so downstream
// naming is one field.
SandboxName string
Provider string // e.g., "vz", "firecracker"; empty = default
Includes []string // repo paths the workspace composes
Domains []string // domain allow list additions
IPs []string // literal IP / CIDR allow list
// Use lists named network policies referenced by `use <name> ...` inside
// a `network ( ... )` block, in declaration order. Resolution against
// `policy` blocks happens at compose time, not parse time.
Use []string
// DenyDomains and DenyIPs become guardrail denies in the sandbox's
// "mod" policy block: they refuse the destination outright (a denied
// domain covers its subdomains), override lower layers' allows, and
// suppress the interactive prompt. See composeNetworkPolicy.
DenyDomains []string
DenyIPs []string
// DenySources are URLs of external blocklists (hosts files / EasyList /
// plain domain lists, uBlock-style) fetched and parsed into denied domains
// by the caller. Written as `deny source "<url>"` inside a `network ( … )`
// block.
DenySources []string
Forwards []string // port forward specs (PORT or HOST:GUEST)
// ReverseForwards are the `reverse <spec>` entries of a forwards
// block: host loopback ports exposed on the guest's loopback. Same
// HOST:GUEST spelling as Forwards, opposite direction of travel.
ReverseForwards []string
Env []string // env entries to export in the VM (canonical envspec form; see parseEnvBlock)
// Lifecycle hooks. Each is a list of shell commands run inside the
// VM at the named moment.
//
// OnCreate runs once after first boot, before the runner attaches.
// Hard fails the up — see Sandbox.CreatePending.
// OnUp runs every clawk-up after the VM is healthy.
// OnDown runs every clawk-down before VM stop. (reserved; not yet wired)
// OnEnter runs every clawk-run before the runner spawns. (reserved; not yet wired)
OnCreate []string
OnUp []string
OnDown []string
OnEnter []string
// Skills is the require-style list of Claude skills the project
// assumes are available, identified by path (local or distributed).
Skills []SkillRef
// Files is the snapshot-on-up file list (`files (...)`).
// See FileSpec for semantics.
Files []FileSpec
// See ShareSpec for semantics.
Shares []ShareSpec
// Serials is the forwarded serial-port list (`serial (...)`).
// See SerialSpec for semantics.
Serials []SerialSpec
// MCP is the declared MCP server list (`mcp (...)`).
// See MCPSpec for semantics.
MCP []MCPSpec
// Instructions and Memory come from the `agent ( ... )` block: extra
// persistent CLAUDE.md guidance and a baseline auto-memory seed. Each is
// an ordered list of AgentDocs (inline text or a markdown file) resolved
// to content at compose, where the namespace's equivalents layer ahead.
Instructions []AgentDoc
Memory []AgentDoc
// Nested enables hardware nested virtualization for the sandbox.
// Bare directive: present and true, absent and false. There's no
// `nested false` form — profile overlays cannot un-set.
Nested bool
// CPU is the vCPU count exposed to the guest. Zero = provider default.
// VZ and KVM don't reserve host CPU time for idle guest vCPUs, so this is
// effectively a burst ceiling rather than a reservation.
CPU uint
// MemoryMiB is the baseline memory target in mebibytes. When set together
// with MemoryMaxMiB > MemoryMiB, the provider configures a virtio-balloon
// that reclaims (max - baseline) back to the host at boot and deflates on
// guest pressure (deflate_on_oom). Zero = follow MemoryMaxMiB (no balloon).
MemoryMiB uint64
// MemoryMaxMiB is the hard cap on guest memory in mebibytes — the amount
// the guest sees at boot. Zero = provider default.
MemoryMaxMiB uint64
// DiskMiB overrides the sparse ext4 root-disk ceiling, in mebibytes
// (vm ( disk <size> )). Zero = the built-in sandbox.DefaultDiskSizeGiB.
// The disk is sparse, so a larger ceiling mostly costs nothing until the
// guest writes into it — the exception is the inode table, ~1/64 of the
// ceiling, written at build time (see sandbox.DefaultDiskSizeGiB). Raise
// it for repos with big dependency trees now that toolchain caches live
// on the rootfs.
DiskMiB uint64
// SwapMiB is the guest swap device's capacity in mebibytes, declared as
// `swap <size|off>` inside the `vm ( ... )` block. Zero = unset (the
// built-in sandbox.DefaultSwapSizeMiB applies); negative = "off", no swap
// device. The device is sparse, so the size bounds how much the guest may
// swap rather than reserving anything up front.
SwapMiB int64
// IdleTimeoutSec is the sandbox's idle-stop timeout in seconds: how
// long the VM may sit with no attached session and a quiescent guest
// before the daemon stops it to reclaim host memory. Declared as
// `idle_timeout <dur|off>` inside the `vm ( ... )` block. Zero = unset
// (the provider default applies); negative = never stop ("off").
IdleTimeoutSec int64
// Image is an OCI image reference (e.g. "golang:1.25") the sandbox
// boots as its root filesystem. Declared as `image <ref>` inside the
// `vm ( ... )` block. Empty = the built-in clawk-dev default.
Image string
// Kernel overrides the guest kernel the vz provider direct-boots: a
// local vmlinux path or an http(s) URL. Declared as `kernel <path|url>`
// inside the `vm ( ... )` block. Empty = the default Kata kernel.
Kernel string
}
Template is the parsed form of a clawk.mod's `sandbox ( ... )` block.
Directives are grouped: scalar VM settings live in `vm ( ... )` and egress policy in `network ( ... )`; `forwards` / `files` / `shares` / `skills` / `env` / `on <event>` sit at the block's top level. A sandbox block with `includes ( ... )` is a workspace root composing member repos; without it the block configures the repo it sits in.
Callers (LoadWorkspace / LoadStandaloneClawkfile) decide which subset is valid for their context and reject out-of-place directives.
func ParseString ¶
ParseString parses a typed-block clawk file and returns its sandbox template. It is a thin wrapper over ParseFileString for callers that only care about the sandbox block; a file without one is an error. Legacy flat files fail inside ParseFileString with a wrap-in-sandbox migration hint.
func (*Template) Clone ¶ added in v0.4.0
Clone returns a deep-enough copy of t for Merge to write into without touching the original's backing arrays. Every field is either a scalar or a slice of values, so copying the slices is sufficient — used by the host-wide defaults layer, which is loaded once and merged under several templates.
func (*Template) Merge ¶
Merge folds the directives of `over` on top of `t`, mutating t in place. This is the overlay semantics used by profiles: the base file declares defaults, the profile file adds more. Scalars in `over` win only when they are non-empty; lists are unioned (appended, duplicates removed later by the sandbox-composition step).
We intentionally do NOT allow profiles to shrink the config: there's no way to say "remove this allowed domain". That keeps profiles analysable — a reviewer sees only additions relative to the base.
type TidyResult ¶
type TidyResult struct {
Rewrites []SkillRewrite
Source string // post-rewrite source. Equal to input when Rewrites is empty.
}
TidyResult describes what RewriteSkillVersions changed. Useful for `clawk mod tidy` to print a one-line "rewrote N versions" summary without the caller diff'ing strings.
func RewriteSkillVersions ¶
func RewriteSkillVersions(src string, tmpl *Template, resolver SkillResolver) (TidyResult, error)
RewriteSkillVersions applies a SkillResolver to every distributed skill in the parsed template and returns a new source with the resolved versions spliced in. Comments, alignment, blank lines, and entry order are preserved — only version tokens change.
Local skills are never rewritten; the resolver should return ErrSkipResolution for them, but RewriteSkillVersions also short-circuits before calling the resolver for safety.
The function does not parse — it works off `tmpl.Skills`, which already carries the source positions populated by the parser.
type Token ¶
type Token struct {
Kind TokenKind
Val string // only meaningful for TokIdent
Line int // 1-based
Col int // 1-based
}
Token is a single lexical unit with source position (for error messages).
func Lex ¶
Lex tokenises src into a flat slice. Errors are returned with line/col from the first malformed byte. Comments are stripped: "#" or "//" run to end of line, and "/* ... */" spans lines until its close. "//" and "/*" open a comment only at a token boundary, so a bare URL value such as https://host/path stays a single identifier; an inline comment needs whitespace before it.
Whitespace other than newlines is separator-only; newlines are significant because our grammar uses them as statement terminators (like go.mod).
type Workspace ¶
type Workspace struct {
Root string // absolute path to the directory containing the clawk.mod
File *Template // parsed workspace-level sandbox block (provider, allow, forwards, ...)
Repos []Repo // every repo included by the workspace, in declaration order
// Policies collects every `policy <name> ( ... )` block declared across
// the loaded files — the host-wide clawk.mod first, then the workspace
// file and its overlay (broader scope), then each repo's clawk.mod and
// overlay. The create paths register them into the host policy store, so
// later (nearer) blocks win a name collision.
Policies []PolicyDef
// GlobalPath is the host-wide clawk.mod folded in as the lowest layer,
// or "" when there is none. Reporting only — its directives have already
// been merged into File or a Repo's Clawkfile by the time a caller sees
// this. See global.go.
GlobalPath string
// GlobalProfileMatched records that a clawk.mod.<profile> overlay beside
// the host-wide file was applied, so a profile satisfied only by the
// host-wide layer isn't reported as matching nothing.
GlobalProfileMatched bool
}
Workspace describes a dev environment rooted at a clawk.mod. A multi-repo workspace is a clawk.mod whose sandbox block has `includes ( ... )`; a single-repo environment is the degenerate case with one synthesised repo.
func FindWorkspace ¶
FindWorkspace is FindWorkspaceWithProfile with no profile.
func FindWorkspaceWithProfile ¶
FindWorkspaceWithProfile walks up from dir looking for a workspace root: the nearest clawk.mod whose sandbox block has `includes`. An includeless clawk.mod along the way is a single-repo file — the walk continues past it, so a workspace root above still wins (exactly the old clawk.work walk). Parse failures along the walk surface as errors rather than being skipped: a leftover flat-grammar file must produce its migration hint, not a silent fall-back to defaults. A clawk.work at any level gets the rename hint.
func LoadClawkfilePathWithProfile ¶
LoadClawkfilePathWithProfile loads an explicitly-given clawk.mod path, picking workspace or single-repo semantics from the file's own shape: a sandbox block with `includes` is a workspace root, anything else is the standalone single-repo case rooted at the file's directory.
func LoadStandaloneClawkfile ¶
LoadStandaloneClawkfile is LoadStandaloneClawkfileWithProfile with no profile.
func LoadStandaloneClawkfileWithProfile ¶
LoadStandaloneClawkfileWithProfile reads a clawk.mod at the given directory and synthesises a single-repo workspace rooted at that repo, optionally overlaying a clawk.mod.<profile> file.
func LoadWorkspace ¶
LoadWorkspace is LoadWorkspaceWithProfile with no profile.
func LoadWorkspaceWithProfile ¶
LoadWorkspaceWithProfile parses a workspace clawk.mod, resolves every listed include, and applies an optional profile overlay to both the workspace file itself and each repo's Clawkfile.
If profile is non-empty:
- "<workspace-path>.<profile>" (e.g. clawk.mod.investigation) is loaded and merged onto the base workspace, if it exists. Missing overlay is tolerated — the profile may only affect per-repo policy.
- Each repo's "clawk.mod.<profile>" is loaded and merged onto its base clawk.mod, if either exists.
A profile name that matches NOTHING across the workspace is treated as an error, so typos surface loudly.
func WorkspaceFromGitRepo ¶
WorkspaceFromGitRepo synthesises a single-repo workspace using dir's containing git repo, with no Clawkfile. Used by `clawk work` as a last-resort fallback when no clawk.mod is present: the repo gets defaults only (no forwards, no setup, default allow list) and the user keeps a one-line invocation. Errors when dir is not inside a git repo.