setup

package
v0.22.132 Latest Latest
Warning

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

Go to latest
Published: May 2, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package setup owns clawtool's project-setup layer: the recipe framework, the wizard, and the .clawtool.toml repo-scoped config.

The user-facing CLI verb is `clawtool init`, but the internal package is named `setup` to avoid colliding with Go's reserved init() lifecycle function and to read clearly in imports.

Recipes are organized into 9 fixed categories. The category set is part of clawtool's public API — at v1.0 it freezes. Adding a category is a major bump; adding recipes within a category is always free.

Index

Constants

View Source
const ForceOption = "force"

ForceOption is the canonical key recipes consult to decide whether to overwrite a file that exists but is not clawtool- managed. The wizard surfaces this as an "overwrite anyway?" prompt; the CLI exposes it via `--force` (which parseKV translates to opts[force]=true).

View Source
const ManagedByMarker = "managed-by: clawtool"

ManagedByMarker is the canonical marker every clawtool-generated file embeds (typically as a YAML/TOML/HTML comment). Detect and Apply both check for it before refusing to touch unmanaged files.

View Source
const RepoConfigName = ".clawtool.toml"

RepoConfigName is the canonical filename. Lives in the repo root, committed to git. Keeps the user-global config (~/.config/clawtool) uncoupled from per-repo state.

Variables

View Source
var ErrSkippedByUser = errors.New("recipe skipped by user")

ErrSkippedByUser is returned when the prompter votes PromptSkip for a missing prereq. Carries through ApplyResult.SkipReason.

Functions

func AllSatisfied

func AllSatisfied(outcomes []PrereqOutcome) bool

AllSatisfied is the convenience predicate over PrereqCheck output.

func CategoryDescriptions

func CategoryDescriptions() map[Category]string

CategoryDescriptions surfaces the one-line description shown in the wizard category header and in `recipe_categories()` MCP output. Kept here, not on each recipe, because the category boundary is a contract, not a recipe-author concern.

func FileExists

func FileExists(path string) (bool, error)

FileExists is the boolean predicate. Returns (false, err) on fs errors that aren't IsNotExist so callers can surface them.

func GetOption

func GetOption[T any](o Options, key string) (T, bool)

Get is a typed helper that returns the option's value as T or the zero value if missing. Recipes use this instead of touching the map directly so the keys stay grep-able.

func HasMarker

func HasMarker(content []byte, marker string) bool

HasMarker reports whether content includes the given marker substring. Recipes use this to label generated files ("managed-by: clawtool") and refuse to overwrite anything without the marker.

func IsForced

func IsForced(o Options) bool

IsForced reports whether opts requests an overwrite of unmanaged files. Defaults to false. Recipes call this in their Apply to gate the "exists but no marker → refuse" check.

func ReadIfExists

func ReadIfExists(path string) ([]byte, error)

ReadIfExists returns nil bytes + nil error when the file is absent. Recipes use this in Detect to fingerprint existing content without juggling not-found errors.

func Register

func Register(r Recipe)

Register adds r to the global registry. Panics on:

  • empty/duplicate Name
  • unknown Category
  • empty Upstream (wrap-don't-reinvent enforcement)
  • empty Description

Always called from a recipe package's init(); panics fail the binary at boot, not at the user's first run.

func WriteAtomic

func WriteAtomic(path string, content []byte, mode os.FileMode) error

WriteAtomic writes content to path via temp+rename so a crash mid- write never leaves the user with a half-finished file. Recipes use this for every file mutation; mode is typically 0o644 for repo files, 0o755 for scripts. Thin wrapper over atomicfile.WriteFileMkdir so all 94 recipe callsites share the project-wide canonical helper — one place to tune crash-window invariants going forward.

Types

type AlwaysInstall

type AlwaysInstall struct{}

AlwaysInstall is the non-interactive Prompter. Used by `clawtool init --yes` and by the MCP recipe_apply tool when the caller passed `auto_install: true`.

func (AlwaysInstall) OnMissingPrereq

type AlwaysSkip

type AlwaysSkip struct{}

AlwaysSkip refuses installation. The MCP path uses it as a default safety guard when `auto_install` is false: a missing prereq surfaces as an error to Claude rather than silently shelling out `apt install`.

func (AlwaysSkip) OnMissingPrereq

type ApplyOptions

type ApplyOptions struct {
	// Repo is the absolute path to the target repository.
	Repo string
	// RecipeOptions is the per-recipe parameter bag (vault path,
	// license SPDX id, etc.).
	RecipeOptions Options
	// Prompter handles missing prereqs. Required.
	Prompter Prompter
	// Runner executes install commands. Required if any prereq
	// has an Install entry.
	Runner CommandRunner
}

ApplyOptions bundles everything Apply needs that isn't recipe- specific Options.

type ApplyResult

type ApplyResult struct {
	Recipe       string
	Category     Category
	Skipped      bool
	SkipReason   string
	Installed    []string // prereq names that were auto-installed
	ManualHints  []string // prereq names where the user picked manual
	UpstreamUsed string
	VerifyOK     bool
	VerifyErr    error
}

ApplyResult captures what a single recipe Apply call did. Used by the wizard's summary screen and by recipe_apply MCP output.

func Apply

func Apply(ctx context.Context, recipe Recipe, ao ApplyOptions) (ApplyResult, error)

Apply runs the full apply sequence for one recipe:

  • Detect (skip if already StatusApplied and ApplyOptions.Force is false; force is wired in v0.10).
  • Prereqs: check each, prompt on missing, install if accepted.
  • Recipe.Apply.
  • Recipe.Verify (post-condition; non-fatal warning if it fails).

Returns the ApplyResult either way; errors are returned alongside (Result.Skipped + non-nil err on user-skip; Result.VerifyErr + nil err on apply-ok-but-verify-failed).

type Category

type Category string

Category is the typed enum for recipe grouping. Defined as an exhaustive set so a recipe authored against a category that doesn't exist literally cannot compile.

const (
	// CategoryGovernance covers files & policies that govern
	// collaboration: LICENSE, CODEOWNERS, CONTRIBUTING.md,
	// SECURITY.md, issue/PR templates, code of conduct.
	CategoryGovernance Category = "governance"

	// CategoryCommits covers commit-time discipline: format
	// conventions, message linting, pre-commit hooks, secret
	// scanning at commit time.
	CategoryCommits Category = "commits"

	// CategoryRelease covers version cutting + publishing: release
	// automation, changelog generation, artifact distribution.
	CategoryRelease Category = "release"

	// CategoryCI covers PR/push pipeline scaffolding: test runners,
	// build matrix, coverage uploads.
	CategoryCI Category = "ci"

	// CategoryQuality covers code quality enforcement: linters,
	// formatters, type checkers, test scaffolds.
	CategoryQuality Category = "quality"

	// CategorySupplyChain covers dependencies & security:
	// dependency updates, SBOM generation, vulnerability scanning.
	CategorySupplyChain Category = "supply-chain"

	// CategoryKnowledge covers project memory & docs: brain,
	// ADR tooling, documentation sites, changelog tooling.
	CategoryKnowledge Category = "knowledge"

	// CategoryAgents covers AI agent integration: agent claims,
	// project-scoped MCP sources, skill bindings. clawtool's USP.
	CategoryAgents Category = "agents"

	// CategoryRuntime covers dev environment & containers:
	// devcontainers, Docker, Nix, direnv, mise.
	CategoryRuntime Category = "runtime"
)

func Categories

func Categories() []Category

Categories returns the categories in repo-maturity walk order (the order the wizard surfaces them). Frozen at v1.0.

func (Category) MustValid

func (c Category) MustValid()

MustValid panics if c isn't a frozen category. Recipe registries call this at registration so a bad category surfaces at boot, not at the user's first run.

func (Category) Valid

func (c Category) Valid() bool

Valid reports whether c is one of the 9 frozen categories. Used at registration time to refuse recipes that target an unknown category.

type ClawtoolMeta

type ClawtoolMeta struct {
	// Version is the clawtool semver that wrote this file.
	Version string `toml:"version"`
}

ClawtoolMeta is the toolchain stamp. Helps future migrations know which schema version they're reading.

type CommandRunner

type CommandRunner interface {
	Run(ctx context.Context, command []string) error
}

CommandRunner is the abstraction for executing prereq install commands. Real callers pass an exec.Command-backed implementation; tests pass a recording fake. Returning an error aborts the recipe.

type Options

type Options map[string]any

Options is the per-Apply parameter bag. Free-form because each recipe has its own knobs (vault path, license SPDX id, default branch). The wizard fills these via prompts; the MCP surface fills them via the recipe_apply call's arguments map.

type Platform

type Platform string

Platform is what we dispatch installer commands on. Kept narrow: linux/darwin/windows. ARM-vs-x86 is the recipe's problem if it matters.

const (
	PlatformLinux   Platform = "linux"
	PlatformDarwin  Platform = "darwin"
	PlatformWindows Platform = "windows"
)

func CurrentPlatform

func CurrentPlatform() Platform

CurrentPlatform returns the host's Platform. Recipes consult this when picking install commands; runtime/setup callers use it to route prereq install offers.

type Prereq

type Prereq struct {
	// Name is human-readable: "Node.js 18+", "GitHub CLI", "Obsidian".
	Name string

	// Check returns nil if the prereq is satisfied. Implementations
	// typically exec.LookPath the binary or shell out a version
	// probe.
	Check func(ctx context.Context) error

	// Install is the command (or commands) clawtool offers to run
	// if Check fails and the user consents. Empty means "manual
	// install only" — the wizard will print ManualHint instead.
	Install map[Platform][]string

	// ManualHint is shown when no Install entry matches the host
	// platform or when the user picks the manual route. One short
	// paragraph; usually a URL plus one-liner.
	ManualHint string
}

Prereq describes one external dependency the recipe needs before Apply can run. The wizard surfaces missing prereqs and offers to install them via the platform-canonical command.

type PrereqOutcome

type PrereqOutcome struct {
	Prereq    Prereq
	Satisfied bool
	Err       error
}

PrereqOutcome is the per-prereq state after a check.

func PrereqCheck

func PrereqCheck(ctx context.Context, recipe Recipe) []PrereqOutcome

PrereqCheck runs Check() against every prereq and returns the outcome list in declared order. Never returns an error itself — individual failures land in Outcome.Err.

type PromptDecision

type PromptDecision int

PromptDecision is what an interactive prompter (TTY wizard or MCP-driven Claude) returns for one prerequisite.

const (
	// PromptInstall: caller offered to install, user accepted.
	// Runner should run Prereq.Install for the current platform.
	PromptInstall PromptDecision = iota
	// PromptManual: user wants to install themselves; runner skips
	// the auto-install and emits ManualHint.
	PromptManual
	// PromptSkip: user said skip this recipe entirely.
	PromptSkip
)

type Prompter

type Prompter interface {
	OnMissingPrereq(ctx context.Context, recipe Recipe, p Prereq, checkErr error) (PromptDecision, error)
}

Prompter is the abstraction over wizard / Claude / non-interactive auto modes. The runner calls it once per missing prereq.

PromptDefault is what `clawtool init --yes` and the MCP code path use: returns PromptInstall on every prereq.

type Recipe

type Recipe interface {
	// Meta returns the static descriptor. Called at registration
	// (validated for required fields) and on every list call.
	Meta() RecipeMeta

	// Detect probes the repo at `repo` and returns the recipe's
	// current state plus an explanation string the wizard prints.
	// Returning StatusError surfaces the error verbatim.
	Detect(ctx context.Context, repo string) (Status, string, error)

	// Prereqs lists what must exist on the host before Apply is
	// safe to call. Empty slice = no prereqs.
	Prereqs() []Prereq

	// Apply executes the recipe against repo with opts. May shell
	// out, write files, fetch network resources. Atomic in the
	// success case; on partial-fail the error wraps what was done
	// so the user can recover.
	Apply(ctx context.Context, repo string, opts Options) error

	// Verify is the post-Apply sanity check, also re-runnable
	// later. Returns nil if the recipe's installed state is
	// healthy; otherwise an error describing what's missing.
	Verify(ctx context.Context, repo string) error
}

Recipe is the single interface every entry implements.

func All

func All() []Recipe

All returns every registered recipe, sorted by category then by name within category. Stable order so wizard output and tests don't flake.

func InCategory

func InCategory(cat Category) []Recipe

InCategory returns recipes for the given category in name order.

func Lookup

func Lookup(name string) Recipe

Lookup returns the recipe with the given name, or nil if absent. Names are unique across categories so the lookup is unambiguous.

type RecipeEntry

type RecipeEntry struct {
	Name            string         `toml:"name"`
	Category        Category       `toml:"category"`
	AppliedAt       time.Time      `toml:"applied_at"`
	UpstreamVersion string         `toml:"upstream_version,omitempty"`
	Options         map[string]any `toml:"options,omitempty"`
}

RecipeEntry is one applied-recipe row. Schema is forward-only — new optional fields are free to add; renames need a migration.

type RecipeMeta

type RecipeMeta struct {
	// Name is kebab-case, unique within the category. Surfaced as
	// the CLI selector and the MCP recipe_apply argument.
	Name string

	// Category is the typed enum. Registry refuses unknown values.
	Category Category

	// Description is one line shown in the wizard and recipe_list.
	Description string

	// Upstream is the canonical URL of the project this recipe
	// wraps. REQUIRED — registry refuses an empty Upstream so a
	// from-scratch reimplementation can't be smuggled in.
	//
	// Use the project's primary repo URL (e.g.
	// "https://github.com/googleapis/release-please") or — for
	// recipes that wrap a spec rather than a project — the spec
	// URL with a "spec:" prefix (e.g.
	// "spec:https://www.conventionalcommits.org").
	Upstream string

	// Stability defaults to StabilityStable if zero-valued.
	Stability Stability

	// Core marks a recipe as part of clawtool's curated default
	// install. When true, the setup wizard pre-checks the row and
	// `clawtool init --all` applies it without prompting (regardless
	// of Stability — Beta recipes can be Core too). Defaults to
	// false so unset / experimental recipes stay opt-in.
	Core bool
}

RecipeMeta is what every recipe declares about itself. Every field except OptionalArgs is required at compile time — a recipe missing Upstream literally cannot be valid, which makes the wrap-don't-reinvent rule code-level enforced.

type Registry

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

Registry is the in-process catalog of available recipes. Recipes register themselves at package init time via Register().

type RepoConfig

type RepoConfig struct {
	Clawtool ClawtoolMeta  `toml:"clawtool"`
	Recipes  []RecipeEntry `toml:"recipe,omitempty"`
}

RepoConfig is the on-disk record of which recipes were applied to this repo. Recipes are stored flat (queryable, easy to diff) but the wizard groups them by category for human reading.

func LoadRepoConfig

func LoadRepoConfig(repoRoot string) (*RepoConfig, error)

LoadRepoConfig reads .clawtool.toml from repoRoot. A missing file returns an empty config with no error — Apply paths can call AppendRecipe + Save without an explicit init step.

func (*RepoConfig) FindRecipe

func (c *RepoConfig) FindRecipe(name string) *RecipeEntry

FindRecipe returns the entry for name, or nil.

func (*RepoConfig) HasRecipe

func (c *RepoConfig) HasRecipe(name string) bool

HasRecipe reports whether a recipe with the given name has been recorded as applied. Wizard uses this to decide which checkboxes to pre-check.

func (*RepoConfig) RecipesByCategory

func (c *RepoConfig) RecipesByCategory() map[Category][]RecipeEntry

RecipesByCategory groups c.Recipes by Category in walk order. Empty categories are not present in the result. Used by recipe_status MCP tool and the wizard's "already applied" header.

func (*RepoConfig) RemoveRecipe

func (c *RepoConfig) RemoveRecipe(name string)

RemoveRecipe drops the entry with name from c. No-op if absent.

func (*RepoConfig) Save

func (c *RepoConfig) Save(repoRoot string) error

Save writes c to repoRoot/.clawtool.toml atomically (temp+rename). Mode 0644 — the file is meant to be committed.

func (*RepoConfig) UpsertRecipe

func (c *RepoConfig) UpsertRecipe(entry RecipeEntry)

UpsertRecipe records that recipe `name` was applied. If an entry already exists, AppliedAt is refreshed and Options/UpstreamVersion are replaced. If absent, a new entry is appended in alpha order.

type Stability

type Stability string

Stability marks how settled a recipe is. The wizard hides experimental recipes by default; recipe_list({stability: "stable"}) is the default filter.

const (
	StabilityStable       Stability = "stable"
	StabilityBeta         Stability = "beta"
	StabilityExperimental Stability = "experimental"
)

type Status

type Status string

Status is what Detect() returns: the recipe's current state in the target repo.

const (
	// StatusAbsent: no trace of the recipe in the repo.
	StatusAbsent Status = "absent"
	// StatusPartial: some artifacts exist but config is incomplete
	// or stale; Apply will reconcile.
	StatusPartial Status = "partial"
	// StatusApplied: recipe is fully configured and Verify passes.
	StatusApplied Status = "applied"
	// StatusError: detection itself failed (e.g. permission denied
	// reading a file). Treat as opaque; show the error to the user.
	StatusError Status = "error"
)

Directories

Path Synopsis
Package recipes is a thin import-aggregator: it pulls in every recipe subpackage so their init() functions register with the global setup.Registry.
Package recipes is a thin import-aggregator: it pulls in every recipe subpackage so their init() functions register with the global setup.Registry.
agentclaim
Package agentclaim hosts the AI-agent integration recipe — the `agents` category in clawtool's setup taxonomy.
Package agentclaim hosts the AI-agent integration recipe — the `agents` category in clawtool's setup taxonomy.
agents
Package agents holds setup recipes that integrate external AI- agent harnesses (Archon, future entrants) with clawtool's playbook layer.
Package agents holds setup recipes that integrate external AI- agent harnesses (Archon, future entrants) with clawtool's playbook layer.
bridges
Package bridges hosts the bridge recipes for the `agents` category — connectors from Claude Code to other coding-agent CLIs (Codex, OpenCode, Gemini).
Package bridges hosts the bridge recipes for the `agents` category — connectors from Claude Code to other coding-agent CLIs (Codex, OpenCode, Gemini).
ci
Package ci hosts recipes for the `ci` category — PR/push pipeline scaffolding.
Package ci hosts recipes for the `ci` category — PR/push pipeline scaffolding.
commits
Package commits hosts recipes for the `commits` category — commit- time discipline.
Package commits hosts recipes for the `commits` category — commit- time discipline.
governance
Package governance hosts recipes for the `governance` category — files & policies that govern collaboration.
Package governance hosts recipes for the `governance` category — files & policies that govern collaboration.
knowledge
Package knowledge hosts recipes for the `knowledge` category — project memory and documentation tooling.
Package knowledge hosts recipes for the `knowledge` category — project memory and documentation tooling.
productivity
Package productivity hosts recipes that ship curated, daily-use playbooks for AI-assisted development.
Package productivity hosts recipes that ship curated, daily-use playbooks for AI-assisted development.
quality
Package quality hosts recipes for the `quality` category — code quality enforcement (lint, format, test scaffolds).
Package quality hosts recipes for the `quality` category — code quality enforcement (lint, format, test scaffolds).
release
Package release hosts recipes for the `release` category — version cutting and publishing automation.
Package release hosts recipes for the `release` category — version cutting and publishing automation.
runtime
Package runtime hosts recipes for the `runtime` category — dev environment + container scaffolding.
Package runtime hosts recipes for the `runtime` category — dev environment + container scaffolding.
sources
Package sources — recipes for AI source / gateway integrations.
Package sources — recipes for AI source / gateway integrations.
supplychain
Package supplychain hosts recipes for the `supply-chain` category — dependency updates and security tooling.
Package supplychain hosts recipes for the `supply-chain` category — dependency updates and security tooling.

Jump to

Keyboard shortcuts

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