Documentation
¶
Index ¶
- Constants
- func CommandHash(install, update string, installs, updates map[string]string) string
- func ComputeFileHash(content []byte) string
- func EnforceRetention(skillDir string, maxVersions int) error
- func HasConflictMarkers(content []byte) bool
- func HashInstallableFiles(files []tools.SkillFile) (string, error)
- func IsLocallyModified(skillDir string, installedHash string) bool
- func RunPackageCommand(ctx context.Context, exec CommandExecutor, pkgDir, cmd string, ...) (string, string, error)
- func ShortSHA(sha string) string
- func SnapshotVersion(skillDir string, revision int) error
- type BudgetWarningMsg
- type CommandExecutor
- type GitHubFetcher
- type Kind
- type LegacyFormatMsg
- type LockMismatchError
- type LockRefusal
- type MergeConflictMsg
- type MergeResult
- type ModifiedStrategy
- type NameConflict
- type NameConflictAction
- type NameConflictError
- type NameConflictResolution
- type NameConflictResolvedMsg
- type NoopFetcher
- func (n *NoopFetcher) FetchDirectory(_ context.Context, _, _, _, _ string) ([]SkillFile, error)
- func (n *NoopFetcher) FetchFile(_ context.Context, _, _, _, _ string) ([]byte, error)
- func (n *NoopFetcher) GetTree(_ context.Context, _, _, _ string) ([]provider.TreeEntry, error)
- func (n *NoopFetcher) LatestCommitSHA(_ context.Context, _, _, _ string) (string, error)
- type PackageApprovedMsg
- type PackageDeniedMsg
- type PackageDetectedMsg
- type PackageErrorMsg
- type PackageHashMismatchMsg
- type PackageInstallPlan
- type PackageInstallPromptMsg
- type PackageInstalledMsg
- type PackageInstallingMsg
- type PackageOutputMsg
- type PackageReclassifiedMsg
- type PackageSkippedMsg
- type PackageUpdateMsg
- type PackageUpdatedMsg
- type ProjectLockError
- type ReconcileCompleteMsg
- type ReconcileConflictMsg
- type ShellExecutor
- type SkillAdoptionNeededMsg
- type SkillDownloadingMsg
- type SkillErrorMsg
- type SkillFile
- type SkillInstalledMsg
- type SkillResolvedMsg
- type SkillSkippedByDenyListMsg
- type SkillSkippedMsg
- type SkillStatus
- type Status
- type StatusDisplay
- type SyncCompleteMsg
- type Syncer
- func (s *Syncer) Diff(ctx context.Context, teamRepo string, st *state.State) ([]SkillStatus, *manifest.Manifest, error)
- func (s *Syncer) DiffSource(ctx context.Context, registryKey string, spec source.SourceSpec, ...) ([]SkillStatus, *manifest.Manifest, error)
- func (s *Syncer) FetchLockfile(ctx context.Context, teamRepo string) (*lockfile.Lockfile, error)
- func (s *Syncer) FetchManifest(ctx context.Context, owner, repo string) (*manifest.Manifest, error)
- func (s *Syncer) FetchManifestSource(ctx context.Context, spec source.SourceSpec) (*manifest.Manifest, error)
- func (s *Syncer) ReclassifyLegacyPackages(st *state.State) error
- func (s *Syncer) Run(ctx context.Context, teamRepo string, st *state.State) error
- func (s *Syncer) RunProject(ctx context.Context, st *state.State, lf *lockfile.ProjectLockfile) error
- func (s *Syncer) RunSource(ctx context.Context, teamRepo string, spec source.SourceSpec, st *state.State) error
- func (s *Syncer) RunWithDiff(ctx context.Context, teamRepo string, statuses []SkillStatus, st *state.State) error
- func (s *Syncer) RunWithDiffSource(ctx context.Context, teamRepo string, spec source.SourceSpec, ...) error
- type TreeEntry
- type VersionInfo
Constants ¶
const DefaultMaxVersions = 10
DefaultMaxVersions is the default number of version snapshots to retain.
Variables ¶
This section is empty.
Functions ¶
func CommandHash ¶
CommandHash returns a deterministic hash of all install/update commands, including per-tool overrides. Used for TOFU: if the hash changes, the user must re-approve. Adding per-tool commands to a previously global-only entry will change the hash and trigger re-approval.
func ComputeFileHash ¶
ComputeFileHash returns SHA-256 hash (first 8 hex chars) of file content. Normalizes CRLF → LF for cross-platform determinism (must match discovery.skillFileHash).
func EnforceRetention ¶
EnforceRetention deletes oldest snapshots if count exceeds maxVersions. If maxVersions is 0, all snapshots are kept (unlimited).
func HasConflictMarkers ¶
func IsLocallyModified ¶
IsLocallyModified checks if SKILL.md has been modified since last sync. When .scribe-base.md is readable, it is the source of truth and is compared directly with SKILL.md. When .scribe-base.md is missing, installedHash is the legacy fallback for installs that do not have a sidecar yet. Other sidecar read errors are treated conservatively as locally modified.
func RunPackageCommand ¶
func RunPackageCommand(ctx context.Context, exec CommandExecutor, pkgDir, cmd string, timeout time.Duration) (string, string, error)
RunPackageCommand executes cmd inside pkgDir using the provided executor. Stdout/stderr are captured. Empty cmd is a no-op (returns nil). A non-nil err indicates the command failed or the context was cancelled; callers are expected to roll back on install errors.
func SnapshotVersion ¶
SnapshotVersion copies current SKILL.md to versions/rev-{N}.md before content changes. N is the current revision number from state. If the SKILL.md does not exist, this is a no-op (first install).
Types ¶
type BudgetWarningMsg ¶
BudgetWarningMsg is emitted when a post-change projected skill set enters an agent's warning band but remains below the hard refusal limit.
type CommandExecutor ¶
type CommandExecutor interface {
Execute(ctx context.Context, command string, timeout time.Duration) (stdout, stderr string, err error)
}
CommandExecutor runs shell commands and captures output.
type GitHubFetcher ¶
type GitHubFetcher interface {
FetchFile(ctx context.Context, owner, repo, path, ref string) ([]byte, error)
FetchDirectory(ctx context.Context, owner, repo, dirPath, ref string) ([]SkillFile, error)
LatestCommitSHA(ctx context.Context, owner, repo, branch string) (string, error)
GetTree(ctx context.Context, owner, repo, ref string) ([]provider.TreeEntry, error)
}
GitHubFetcher abstracts GitHub API operations needed by the sync engine.
func WrapGitHubClient ¶
func WrapGitHubClient(c *gh.Client) GitHubFetcher
WrapGitHubClient returns a GitHubFetcher backed by a real gh.Client. Delegates to provider.WrapGitHubClient — provider.GitHubClient is a superset of GitHubFetcher.
type Kind ¶
type Kind string
Kind classifies a fetched repo payload.
KindSkill: single-authorship skill; dir-symlinked into tool skill dirs. KindPackage: multi-skill bundle or self-installing toolkit; stored under ~/.scribe/packages/<name>/ and never projected into agent skill dirs.
func DetectKind ¶
DetectKind classifies a fetched file tree as either a skill or a package.
Rules (first match wins):
- Any SKILL.md at a non-root path (e.g. "browse/SKILL.md") → package.
- No SKILL.md at the root AND the tree has a recognised install script (setup, install.sh, bootstrap, Makefile, package.json) → package.
- Otherwise → skill.
This mirrors the spec at docs/superpowers/specs/2026-04-17-packages-store-design.md. The detection runs against in-memory SkillFile slices before the tree is written to disk, so nothing is persisted until routing is decided.
func DetectKindFromDir ¶
DetectKindFromDir classifies an existing on-disk directory. Used by the migration pass: first sync after upgrade walks ~/.scribe/skills/<name>/ for each installed skill and reclassifies any that look like packages.
type LegacyFormatMsg ¶
type LegacyFormatMsg struct{ Repo string }
LegacyFormatMsg is sent when a registry still uses scribe.toml (TOML format).
type LockMismatchError ¶
type LockMismatchError struct {
Registry string `json:"registry"`
Refused []LockRefusal `json:"refused"`
}
func (*LockMismatchError) Error ¶
func (e *LockMismatchError) Error() string
type LockRefusal ¶
type MergeConflictMsg ¶
type MergeConflictMsg struct {
Name string
}
MergeConflictMsg is sent when a 3-way merge produces conflict markers.
type MergeResult ¶
type MergeResult int
MergeResult describes the outcome of a 3-way merge.
const ( MergeClean MergeResult = iota // clean merge, auto-applied MergeConflict // has conflict markers MergeError // git merge-file failed )
func ThreeWayMerge ¶
func ThreeWayMerge(skillDir string, upstreamContent []byte) (MergeResult, error)
ThreeWayMerge performs a 3-way merge using git merge-file. base = .scribe-base.md (last synced pristine) ours = SKILL.md (locally modified) theirs = new upstream content
On clean merge: updates SKILL.md in place, advances .scribe-base.md to the new upstream content, and removes any stale .scribe-theirs.md sidecar. On conflict: writes conflict markers to SKILL.md, keeps .scribe-base.md unchanged (still the pre-merge base), and persists .scribe-theirs.md so `scribe resolve --theirs` can read the upstream version.
type ModifiedStrategy ¶
type ModifiedStrategy int
ModifiedStrategy controls how sync treats outdated skills with local edits.
const ( ModifiedStrategyMerge ModifiedStrategy = iota ModifiedStrategyPreferTheirs )
type NameConflict ¶
type NameConflictAction ¶
type NameConflictAction string
const ( NameConflictActionUnresolved NameConflictAction = "unresolved" NameConflictActionAdopt NameConflictAction = "adopt" NameConflictActionAlias NameConflictAction = "alias" NameConflictActionSkip NameConflictAction = "skip" )
type NameConflictError ¶
type NameConflictError struct {
Conflict NameConflict
Resolution NameConflictResolution
Err error
}
func (*NameConflictError) Error ¶
func (e *NameConflictError) Error() string
func (*NameConflictError) Unwrap ¶
func (e *NameConflictError) Unwrap() error
type NameConflictResolution ¶
type NameConflictResolution struct {
Action NameConflictAction `json:"action"`
Alias string `json:"alias,omitempty"`
}
type NameConflictResolvedMsg ¶
type NameConflictResolvedMsg struct {
Conflict NameConflict
Resolution NameConflictResolution
}
type NoopFetcher ¶
type NoopFetcher struct{}
NoopFetcher is a GitHubFetcher that returns errors for all operations. Used when Provider handles all fetching.
func (*NoopFetcher) FetchDirectory ¶
func (*NoopFetcher) LatestCommitSHA ¶
type PackageApprovedMsg ¶
type PackageApprovedMsg struct{ Name string }
PackageApprovedMsg is sent when a user approves a package install.
type PackageDeniedMsg ¶
type PackageDeniedMsg struct{ Name string }
PackageDeniedMsg is sent when a user denies a package install.
type PackageDetectedMsg ¶
type PackageDetectedMsg struct {
Name string
Dir string
Source string // where the install command came from (scribe.yaml, ./setup, …)
}
PackageDetectedMsg is emitted when a fetched payload was classified as a tree-package (nested SKILL.md or install script). Lets the UI show a "stored as package" hint before the install command runs.
type PackageErrorMsg ¶
PackageErrorMsg is sent when a package install or update fails.
type PackageHashMismatchMsg ¶
type PackageHashMismatchMsg struct {
Name string
OldCommand string
NewCommand string
Source string
}
PackageHashMismatchMsg is sent when a previously approved command has changed.
type PackageInstallPlan ¶
type PackageInstallPlan struct {
// Command is the shell command to run after writing the package dir.
// Executed from within the package directory.
Command string
// Uninstall, if set, is the command to run when `scribe remove <name>`
// is invoked for this package. Best-effort.
Uninstall string
// Source is a short description of where the command came from
// ("scribe.yaml", "./setup", "install.sh", "package.json", "Makefile").
Source string
}
PackageInstallPlan describes how a staged package should self-install. Resolution order (first match wins):
- scribe.yaml → install.command / install.uninstall
- executable setup at repo root
- install.sh at repo root
- package.json → run `bun install` when bun is available, else `npm install`
- Makefile target install (run via `make install`)
- No install command → empty Command (package is tracked but no-op)
func ResolvePackageInstall ¶
func ResolvePackageInstall(pkgDir string) (PackageInstallPlan, error)
ResolvePackageInstall walks the given package directory and returns a plan for how to execute its self-install. See PackageInstallPlan for the resolution order. An absent plan (empty Command) is not an error — the package is tracked but triggers no shell execution.
type PackageInstallPromptMsg ¶
PackageInstallPromptMsg is sent when a package requires user approval.
type PackageInstalledMsg ¶
type PackageInstalledMsg struct{ Name string }
PackageInstalledMsg is sent when a package install completes successfully.
type PackageInstallingMsg ¶
type PackageInstallingMsg struct{ Name string }
PackageInstallingMsg is sent when a package install command begins.
type PackageOutputMsg ¶
PackageOutputMsg streams a package install/uninstall command's combined output to the UI. Emitted once per command, after it finishes.
type PackageReclassifiedMsg ¶
PackageReclassifiedMsg is emitted by the migration pass when a legacy skills/ install is moved into packages/ because its tree shape identifies it as a package. InstallHint carries a note about whether setup should be re-run (we don't auto-run during migration).
type PackageSkippedMsg ¶
PackageSkippedMsg is sent when a package is skipped (e.g. already approved).
type PackageUpdateMsg ¶
type PackageUpdateMsg struct{ Name string }
PackageUpdateMsg is sent when a package update command begins.
type PackageUpdatedMsg ¶
type PackageUpdatedMsg struct{ Name string }
PackageUpdatedMsg is sent when a package update completes successfully.
type ProjectLockError ¶
type ProjectLockError struct {
Refused []LockRefusal `json:"refused"`
}
func (*ProjectLockError) Error ¶
func (e *ProjectLockError) Error() string
type ReconcileCompleteMsg ¶
type ReconcileConflictMsg ¶
type ReconcileConflictMsg struct {
Name string
Conflict state.ProjectionConflict
}
type ShellExecutor ¶
type ShellExecutor struct{}
ShellExecutor runs commands via a platform shell with process group management where supported.
type SkillAdoptionNeededMsg ¶
SkillAdoptionNeededMsg is sent when install refused to overwrite a real (non-Scribe) directory at a tool projection path. Run `scribe adopt <name>` to import the existing content, then re-sync.
type SkillDownloadingMsg ¶
type SkillDownloadingMsg struct{ Name string }
SkillDownloadingMsg is sent when a skill download begins.
type SkillErrorMsg ¶
SkillErrorMsg is sent when a skill fails to install. Sync continues.
type SkillInstalledMsg ¶
type SkillInstalledMsg struct {
Name string
Updated bool // true = update, false = fresh install
Merged bool // true if 3-way merge was used
Revision int // post-install revision number (1 for fresh install)
}
SkillInstalledMsg is sent when a skill is successfully installed or updated.
type SkillResolvedMsg ¶
type SkillResolvedMsg struct{ SkillStatus }
SkillResolvedMsg is sent once per skill after the diff is computed, before any downloads start. Powers the initial list render.
type SkillSkippedByDenyListMsg ¶
SkillSkippedByDenyListMsg is sent when a user removed a registry skill and sync is preserving that removal intent.
type SkillSkippedMsg ¶
type SkillSkippedMsg struct{ Name string }
SkillSkippedMsg is sent when a skill is already current — no action needed.
type SkillStatus ¶
type SkillStatus struct {
Name string
Status Status
Installed *state.InstalledSkill // nil if not installed
Entry *manifest.Entry // catalog entry, nil for StatusExtra
LoadoutRef string // the ref from the manifest (e.g. "v1.0.0", "main")
Maintainer string
IsPackage bool
LatestSHA string // resolved SHA for branch-pinned skills; empty if unavailable
BlobSHAs map[string]string
LockEntry *lockfile.Entry
SourceKey string
Source *source.SourceSpec
ResolvedRev string
}
SkillStatus is the result of comparing one skill against the loadout.
func (SkillStatus) DisplayAgents ¶
func (sk SkillStatus) DisplayAgents() string
DisplayAgents returns the comma-separated list of installed targets.
func (SkillStatus) DisplayAuthor ¶
func (sk SkillStatus) DisplayAuthor() string
DisplayAuthor returns the author or "—" if unknown.
func (SkillStatus) DisplayVersion ¶
func (sk SkillStatus) DisplayVersion() string
DisplayVersion returns the best human-readable version for this skill. Prefers semver tags as-is, else a short SHA when the ref is a branch/HEAD, else the installed revision counter.
type Status ¶
type Status int
Status describes how a skill compares against the team loadout.
const ( StatusMissing Status = iota // in loadout, not installed locally StatusCurrent // installed, matches loadout exactly StatusOutdated // installed, but loadout specifies a different version StatusExtra // installed locally, not in the team loadout StatusModified // installed, locally modified, upstream unchanged StatusConflicted // merge produced conflicts, needs resolution )
func (Status) Display ¶
func (s Status) Display() StatusDisplay
Display returns the icon and label for rendering this status.
type StatusDisplay ¶
StatusDisplay holds the icon and label for a status value.
type SyncCompleteMsg ¶
SyncCompleteMsg is sent when all skills have been processed.
type Syncer ¶
type Syncer struct {
Client GitHubFetcher
Provider provider.Provider // optional — if set, used for discovery and fetch
Tools []tools.Tool
Emit func(any) // receives events defined in events.go
Executor CommandExecutor
// ProjectRoot scopes tool projections to a project-local agent directory.
// Empty preserves legacy global projections.
ProjectRoot string
// ModifiedStrategy controls what to do when an outdated skill also has
// unsynced local edits on disk.
ModifiedStrategy ModifiedStrategy
// SkillFilter, if non-nil, restricts the sync to only the named skills.
// Skills not in the list are skipped entirely (not even resolved/emitted).
// Used by `scribe install` to install specific skills.
SkillFilter []string
// KitFilter, if non-nil, restricts symlink emission to skills resolved by
// the project's kit set. Empty/nil means no kit filtering (legacy behavior).
// Computed from the project file by StepResolveKitFilter so `scribe sync`
// matches `scribe show` output.
KitFilter []string
KitFilterEnabled bool
// SkipMissing prevents installing skills that are not yet locally installed.
// When true, StatusMissing skills are silently skipped — only updates and
// removals are processed. Set by `scribe sync` to implement the opt-in
// model: new skills from a registry are not auto-installed; the user must
// explicitly run `scribe add <skill>` or `scribe install` to install them.
SkipMissing bool
// TrustAll skips approval prompts for packages (--trust-all flag).
TrustAll bool
// ForceBudget allows projection even when an agent description-byte budget
// would otherwise be exceeded.
ForceBudget bool
// ApprovalFunc is called when a package needs interactive approval.
// Returns true if approved, false if denied.
// If nil and TrustAll is false, packages needing approval are skipped.
ApprovalFunc func(name, command, source string) bool
// AliasName installs an incoming skill under this alternate name when a
// real directory already exists at the original tool projection path.
AliasName string
// SkillAliases installs named incoming skills under fixed local names.
// Used by registry kits to avoid same-name collisions across sources.
SkillAliases map[string]string
// PinnedSkillSources overrides catalog source refs for named skills during
// kit dependency installs.
PinnedSkillSources map[string]string
// OnRegistryFetched runs after a registry manifest has been fetched and
// applied successfully. Callers use this for local metadata caches.
OnRegistryFetched func(repo string, m *manifest.Manifest) error
// NameConflictResolver is called when a real directory already exists at
// the incoming skill name. Nil means non-interactive conflict.
NameConflictResolver func(NameConflict) (NameConflictResolution, error)
}
Syncer wires manifest, github, tools, and state together. It emits events via the Emit callback — the caller decides whether to forward them to a Bubbletea program or log them to stdout.
func (*Syncer) Diff ¶
func (s *Syncer) Diff(ctx context.Context, teamRepo string, st *state.State) ([]SkillStatus, *manifest.Manifest, error)
Diff fetches the team loadout and computes status for every skill without making any changes. Used by `scribe list`. Returns the parsed manifest alongside statuses so callers can reuse it.
func (*Syncer) DiffSource ¶
func (s *Syncer) DiffSource(ctx context.Context, registryKey string, spec source.SourceSpec, st *state.State) ([]SkillStatus, *manifest.Manifest, error)
DiffSource fetches a SourceSpec-backed registry and computes status for each skill. registryKey is the legacy/display identity used in state and JSON during phase 2.
func (*Syncer) FetchLockfile ¶
func (*Syncer) FetchManifest ¶
FetchManifest tries Provider.Discover first (if set), then falls back to direct file fetch with scribe.yaml → scribe.toml fallback.
func (*Syncer) FetchManifestSource ¶
func (s *Syncer) FetchManifestSource(ctx context.Context, spec source.SourceSpec) (*manifest.Manifest, error)
FetchManifestSource fetches a manifest through SourceSpec-aware providers. It falls back to legacy GitHub file fetch only for unscoped GitHub sources.
func (*Syncer) ReclassifyLegacyPackages ¶
ReclassifyLegacyPackages scans state entries classified as skills, detects any whose on-disk shape is actually a package (nested SKILL.md / install script), and moves them to ~/.scribe/packages/<name>/. Tool projections are removed and state is updated. Each move emits a PackageReclassifiedMsg so the UI can surface a hint.
Safe to call repeatedly — the caller guards with state.HasMigration and marks it done after the first successful pass.
func (*Syncer) Run ¶
Run executes the full sync: diff, then install/update as needed. Emits events throughout. Updates state incrementally — a failed skill does not prevent successful skills from being recorded.
func (*Syncer) RunProject ¶
func (*Syncer) RunSource ¶
func (s *Syncer) RunSource(ctx context.Context, teamRepo string, spec source.SourceSpec, st *state.State) error
RunSource executes a sync against a SourceSpec-backed registry.
func (*Syncer) RunWithDiff ¶
func (s *Syncer) RunWithDiff(ctx context.Context, teamRepo string, statuses []SkillStatus, st *state.State) error
RunWithDiff applies a pre-computed diff (statuses) directly. Used by tests and callers that already have statuses from Diff().
func (*Syncer) RunWithDiffSource ¶
func (s *Syncer) RunWithDiffSource(ctx context.Context, teamRepo string, spec source.SourceSpec, statuses []SkillStatus, st *state.State) error
RunWithDiffSource applies a pre-computed diff using SourceSpec-aware fetch.
type TreeEntry ¶
Re-export so external callers can build their own fetchers without importing the provider package.
type VersionInfo ¶
VersionInfo describes a single version snapshot.
func ListVersions ¶
func ListVersions(skillDir string) ([]VersionInfo, error)
ListVersions returns available version snapshots for a skill, sorted by revision (ascending).