Documentation
¶
Overview ¶
Package skills implements the install-time integrity engine for Semantica SKILL.md files. The engine is the source of truth for the ownership model documented in the skills repo's `docs/AUTHORING.md`: every Semantica-managed file carries an `x-semantica-managed` marker and a whole-file content hash with the hash line itself replaced by a fixed placeholder before hashing, so install/uninstall can detect user edits before taking destructive action.
The engine is intentionally pure: it operates on byte slices and does no filesystem I/O. The install/uninstall commands wrap it.
Index ¶
Constants ¶
const ( ManagedKey = "x-semantica-managed" CLIVersionKey = "x-semantica-cli-version" ContentHashKey = "x-semantica-content-hash" )
Frontmatter keys the engine reads or writes.
const ( CLIVersionPlaceholder = "SEMANTICA_CLI_VERSION_PLACEHOLDER" ContentHashPlaceholder = "sha256:PLACEHOLDER" )
Author-time placeholder values. SKILL.md files in source carry these literal strings; the publish/install pipeline substitutes real values.
const ClaudeSkillsDirEnv = "SEMANTICA_CLAUDE_SKILLS_DIR"
ClaudeSkillsDirEnv lets tests override the Claude Code skills root without redirecting the entire user home. Set it to a temp path to exercise install / uninstall flows hermetically. A set-but-empty value explicitly disables this agent target (used by tests that want to exercise only the Cursor path).
const CodexSkillsDirEnv = "SEMANTICA_CODEX_SKILLS_DIR"
CodexSkillsDirEnv mirrors the same pattern for OpenAI Codex's user-global skills directory (`~/.codex/skills`). One target covers both the standalone CLI and the desktop app; their embedded runtime shares this directory and both surfaces discover installed skills via the same `skills/list` RPC.
Codex also reads `~/.agents/skills` per the agentskills.io standard. Semantica does not use that shared path here because the rest of its install surface is provider-scoped (uninstalling Codex must not affect skills another tool may have installed under the standard path).
const CopilotSkillsDirEnv = "SEMANTICA_COPILOT_SKILLS_DIR"
CopilotSkillsDirEnv mirrors the same pattern for GitHub Copilot CLI's user-global skills directory (`~/.copilot/skills`). See docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills.
const CursorSkillsDirEnv = "SEMANTICA_CURSOR_SKILLS_DIR"
CursorSkillsDirEnv mirrors ClaudeSkillsDirEnv for Cursor's user-global skills directory (`~/.cursor/skills`). Cursor's loader uses the same SKILL.md-with-frontmatter format Claude Code does, so the install / uninstall logic is shared.
const GeminiSkillsDirEnv = "SEMANTICA_GEMINI_SKILLS_DIR"
GeminiSkillsDirEnv mirrors the same pattern for Gemini CLI's user-global skills directory (`~/.gemini/skills`). See geminicli.com/docs/cli/skills.
const KiroSkillsDirEnv = "SEMANTICA_KIRO_SKILLS_DIR"
KiroSkillsDirEnv mirrors the same pattern for Kiro's user-global skills directory (`~/.kiro/skills`). The path is shared by Kiro IDE and Kiro CLI; both load skills from the same location. See kiro.dev/docs/cli/skills.
const SemanticaSkillNamePrefix = "semantica-"
SemanticaSkillNamePrefix is the prefix every Semantica-owned skill identifier carries. Both the source directory name and the destination directory name must start with it. The prefix is the scoping boundary for uninstall: directories without it are considered third-party or user-authored and are never touched, regardless of the --force flag.
const SkillFileName = "SKILL.md"
SkillFileName is the fixed name Anthropic Agent Skills loaders expect inside each skill subdirectory.
Variables ¶
var ErrCLIVersionEmpty = errors.New("cliVersion is empty")
ErrCLIVersionEmpty is returned when Stamp is called with an empty CLI version argument. Empty cliVersion would round-trip to a blank value in the installed file.
var ErrCLIVersionMissing = errors.New("x-semantica-cli-version line missing")
ErrCLIVersionMissing is returned when no `x-semantica-cli-version` line exists in the frontmatter. Stamp refuses to install an unversioned file because the version is part of the integrity-checked content.
var ErrCLIVersionNotPlaceholder = errors.New("x-semantica-cli-version is not the author-time placeholder")
ErrCLIVersionNotPlaceholder is returned when the version line is present but its value is not the author-time placeholder. Catches accidental input of an already-stamped file as well as hand-authored files that hard-coded a version string.
var ErrContentHashMissing = errors.New("x-semantica-content-hash line missing")
ErrContentHashMissing is returned when no `x-semantica-content-hash` line exists in the frontmatter. A managed file without a hash is malformed and the engine refuses to operate on it.
var ErrContentHashNotPlaceholder = errors.New("x-semantica-content-hash is not the author-time placeholder")
ErrContentHashNotPlaceholder is returned by Stamp when the hash line is present but its value is not the author-time placeholder. The input has been pre-stamped or hand-edited; refuse rather than silently re-hashing.
var ErrFileEdited = errors.New("installed SKILL.md has been edited since install")
ErrFileEdited indicates an installed SKILL.md is Semantica-managed but has been modified since install. Install and uninstall refuse to act on these files unless --force is set.
var ErrFileUnmanaged = errors.New("destination SKILL.md is not Semantica-managed")
ErrFileUnmanaged indicates a SKILL.md exists at the destination but is missing the Semantica ownership marker. Install refuses to overwrite it unless --force is set. Uninstall preserves it under all flags.
var ErrManagedMarkerMissing = errors.New("x-semantica-managed marker missing")
ErrManagedMarkerMissing is returned when the file is missing the `x-semantica-managed: true` marker. The install/uninstall commands surface this as "this file is not Semantica-managed."
var ErrNoAgentsDetected = errors.New("no supported agent skills directory found")
ErrNoAgentsDetected indicates the install command found no agent home directories (`~/.claude`, `~/.cursor`) and no env-override targets. The skill files have nowhere to land, so the command surfaces a clear error rather than creating directories under home dirs the user doesn't actually have.
var ErrSkillNameMismatch = errors.New("skill directory name does not match frontmatter name")
ErrSkillNameMismatch indicates a skill's source directory name does not match its frontmatter `name` field. AUTHORING.md requires they match.
var ErrSkillNameNotPrefixed = errors.New("skill name must start with " + SemanticaSkillNamePrefix)
ErrSkillNameNotPrefixed indicates a skill's identifier does not carry the SemanticaSkillNamePrefix. The CLI refuses to install such a file because the prefix is also the uninstall scoping boundary: a non-prefixed install would either leak past uninstall or risk colliding with an unrelated agent-side skill.
var ErrSkillsFetchFailed = errors.New("could not fetch skills from github.com; check your network or use --source <path> for offline install")
ErrSkillsFetchFailed identifies download and extraction failures.
var ErrSourceMissing = errors.New("source directory does not exist")
ErrSourceMissing indicates the install source directory does not exist or is unreadable.
var ErrSourceNoSkills = errors.New("source directory contains no skills")
ErrSourceNoSkills indicates the install source directory exists but contains no `<skill-name>/SKILL.md` entries.
var ErrUnsafeSkillName = errors.New("skill name is not safe for filesystem use")
ErrUnsafeSkillName indicates a skill's directory name or frontmatter name does not match the safe-name pattern. The install refuses rather than risking writes outside the skills directory tree.
Functions ¶
func Stamp ¶
Stamp prepares a source SKILL.md for installation. It validates that the input is an author-time SKILL.md (managed marker present, both placeholders present in the frontmatter), then substitutes the version placeholder with the supplied CLI version and computes the whole-file content hash with the hash line replaced by its fixed placeholder. Returns the final installable bytes and the computed hash value (e.g. "sha256:abcd...") for diagnostic logging.
Substitution is frontmatter-only and line-targeted: the engine rewrites just the value of the `x-semantica-cli-version` line, so any literal occurrences of the placeholder string in the markdown body (for example, in documentation explaining how authoring works) are left untouched. Inputs that have already been stamped, or that hand-author a concrete version string, are rejected so the install pipeline is never the source of an incorrectly-stamped file.
func Verify ¶
Verify reports whether the recomputed content hash of an installed SKILL.md matches the value stored in its frontmatter. A return of (true, nil) means the file is byte-identical to what Stamp produced; (false, nil) means the file has been edited and callers should refuse to remove or overwrite it without --force.
ErrManagedMarkerMissing fires when the file lacks the management marker entirely; ErrContentHashMissing fires when the marker is present but the hash line is missing.
Types ¶
type ActionKind ¶
type ActionKind string
ActionKind labels what Install or Uninstall did to a single skill file. Used by the report so the command layer can render a sensible per-skill summary.
const ( ActionInstalled ActionKind = "installed" ActionUpdated ActionKind = "updated" ActionRemoved ActionKind = "removed" ActionSkipped ActionKind = "skipped" ActionForced ActionKind = "forced" )
type InstallOptions ¶
InstallOptions controls Install. Source is the directory laid out as `<source>/<skill-name>/SKILL.md` (the layout the skills repo uses). CLIVersion is stamped into each installed file.
type Report ¶
type Report struct {
Actions []SkillAction
}
Report is returned from Install and Uninstall. The command layer renders the rows; this package does not print anything itself.
func Install ¶
func Install(ctx context.Context, opts InstallOptions) (*Report, error)
Install walks the source tree, stamps each SKILL.md, and writes the result to every detected agent target (Claude Code at `~/.claude/skills/`, Cursor at `~/.cursor/skills/`, etc.). The function is idempotent for files that pass Verify against the existing destination: re-running with the same CLI version is a no-op write of identical bytes; re-running after a CLI version bump rewrites with the new version stamped in. Files that have been edited since install, or that exist at the destination without the Semantica ownership marker, are refused unless Force is set. Each (skill, target) pair produces its own row in the returned report.
When opts.Source is empty, Install fetches the skills archive from the protected main branch of semanticash/skills. opts.Source overrides the network path entirely so developers and offline users can install from a local checkout.
func Uninstall ¶
Uninstall scans every detected agent's user-global skills directory and removes Semantica-installed SKILL.md files. Discovery is scoped to directories whose name starts with SemanticaSkillNamePrefix - third-party or user-authored skills (e.g. `~/.claude/skills/review/`) are out of scope and never touched, regardless of the force flag.
Within scope:
- hash matches stored value: removed (ActionRemoved).
- hash mismatch (we wrote it, user later edited it): preserved unless force is set, in which case removed (ActionForced).
- missing managed marker: preserved under all flags. The marker is the only positive signal that the file is ours; if it is gone we treat the file as user-authored content that happens to live under our prefix.
Skill subdirectories are removed best-effort once their SKILL.md is gone, but only when the directory ends up empty so user-added sibling files are preserved.
type SkillAction ¶
type SkillAction struct {
Skill string
Target string // "claude-code", "cursor"
Path string
Action ActionKind
Reason string // populated for ActionSkipped / ActionForced
}
SkillAction is one row in an Install or Uninstall report. The same skill can produce multiple rows when more than one agent target is detected (e.g., a user with both Claude Code and Cursor installed).