Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Sync AI coding skills across tools. Discover skills from Claude Code plugins, standalone directories, and custom locations — then distribute them to every AI coding tool that supports the SKILL.md format.

Why

AI coding tools (Claude Code, Codex, Antigravity) each use SKILL.md packages to provide context. But skills get siloed:

  • Plugin skills live in cache directories you never see
  • Standalone skills only exist for one tool
  • Switching tools means losing access to your skill library

tome consolidates all skills into a single library and distributes them everywhere.

Install

Homebrew (macOS/Linux):

brew install MartinP7r/tap/tome

Quick Start

# Interactive setup — discovers sources, configures targets
tome init

# Sync skills to all configured targets (with interactive triage)
tome sync

# Check what's configured
tome status

How It Works

Every directory you configure — package-manager caches, per-tool skill dirs, git-hosted skill repos — lives under a single [directories.*] map in tome.toml with a role that tells tome how it participates in the pipeline.

graph LR
    subgraph Sources["Sources (roles: Managed / Synced / Source)"]
        S1["<b>claude-plugins</b><br/>type: claude-plugins<br/>~/.claude/plugins"]
        S2["<b>claude-skills</b><br/>type: directory<br/>~/.claude/skills"]
        S3["<b>team-skills</b><br/>type: git<br/>github.com/org/skills"]
    end

    subgraph Library["Library — ~/.tome/skills"]
        L["Consolidated skill library<br/>(copies for local, symlinks for managed)"]
    end

    subgraph Targets["Targets (roles: Synced / Target)"]
        T1["<b>codex</b><br/>~/.codex/skills"]
        T2["<b>antigravity</b><br/>~/.gemini/antigravity/skills"]
        T3["<b>cursor</b><br/>~/.cursor/skills"]
    end

    S1 --> L
    S2 --> L
    S3 --> L
    L --> T1
    L --> T2
    L --> T3
  1. Discover — Scan every configured directory (types: claude-plugins, directory, git) for */SKILL.md subdirs
  2. Consolidate — Gather skills into ~/.tome/skills: local skills are copied, managed (package-manager) skills are symlinked back to their source; first-seen-wins on name conflicts
  3. Distribute — Create symlinks in each distribution directory (respects per-machine disabled/enabled filters)
  4. Cleanup — Remove stale entries and broken symlinks from both library and distribution dirs

v0.6+ unified directory model: A directory can be both a source and a target (role: Synced). Discovery and distribution are determined by role, not by separate config sections. See architecture for details.

License

MIT

Commands

CommandDescription
tome initInteractive wizard to configure directories
tome syncReconcile, discover, consolidate, distribute, and clean up skills
tome add <url|slug>Register a git skill repository in tome.toml
tome remove dir <name>Remove a directory entry (manifest entries transition to Unowned per LIB-04)
tome remove skill <name>Delete an Unowned skill from the library (manifest + library + distribution + lockfile + machine.toml cleanup)
tome reassign <skill> --to <directory>Reassign a skill to a different directory (accepts Owned + Unowned input per UNOWN-01)
tome fork <skill> --to <local-directory>Fork a managed skill to a local directory for customization
tome migrate-libraryConvert a v0.9-shape library (managed skills as symlinks) to v0.10 real-directory copies (idempotent on re-run)
tome statusShow library, directories, last-sync, and health summary
tome list (alias: ls)List all discovered skills with their directories (supports --json)
tome browseInteractively browse discovered skills with fuzzy search
tome doctorDiagnose and repair broken symlinks or config issues
tome lintValidate skill frontmatter and report issues
tome configShow current configuration
tome backupGit-backed backup and restore for the skill library
tome ejectRemove tome’s symlinks from all distribution directories (reversible via tome sync)
tome relocate <path>Move the skill library to a new location
tome completions <shell>Install shell completions (bash, zsh, fish, powershell)
tome versionPrint version information

Global Flags

FlagShortDescription
--config <path>Path to config file (default: ~/.tome/tome.toml)
--tome-home <path>Override tome home directory (default: ~/.tome/, or TOME_HOME env var)
--machine <path>Path to machine preferences file (default: ~/.config/tome/machine.toml)
--dry-runPreview changes without modifying filesystem
--no-inputDisable all interactive prompts (implies --no-triage for sync)
--verbose-vDetailed output
--quiet-qSuppress non-error output (conflicts with --verbose)

Command Details

tome sync

Runs the full pipeline: discover skills from configured directories, consolidate into the library, diff the lockfile to surface changes, distribute to targets, and clean up stale entries. When new or changed skills are detected, an interactive triage prompt lets you disable unwanted skills. Generates a tome.lock lockfile for reproducible snapshots.

FlagShortDescription
--force-fRecreate all symlinks even if they appear up-to-date
--no-triageSkip interactive triage of new/changed skills (for CI/scripts)

tome add

Register a git skill repository in tome.toml. Accepts either a full git URL (https://github.com/owner/repo, git@github.com:owner/repo.git) or a bare GitHub slug (owner/repo), which is expanded to https://github.com/owner/repo (v0.8.2+). The clone is shallow and lives in ~/.tome/repos/<sha256>/.

URL forms

tome add https://github.com/user/skills           # full HTTPS URL
tome add user/skills                              # bare slug → github.com
tome add git@github.com:user/skills.git           # SSH URL
tome add user/skills/tree/main/skills             # /tree/<ref>/<subdir> shortcut (v0.13+)
tome add user/skills --subdir skills              # explicit --subdir flag (v0.13+)

The /tree/<ref>/<subdir> URL form mimics how GitHub renders navigation into a subdirectory in your browser — copy-paste from github.com/owner/repo/tree/main/skills and it just works. Extracted <ref> becomes the default branch; <subdir> becomes the discovery subdirectory. Explicit --branch / --subdir flags override URL-embedded values (with a warning).

Auto-detection of common subdirs (v0.13+)

If tome sync finds zero skills at a directory’s root AND no subdir is configured, it probes common Claude Code plugin layouts (skills/, .claude-plugin/skills/) and emits a subdir = "..." hint if any candidate has skills inside. Catches the “I added a Claude plugin repo and got zero skills” case without forcing the user to know the convention up front.

Flags

FlagDescription
URLGit repository URL or owner/repo slug (optionally with /tree/<ref>/<subdir> suffix)
--name <name>Custom directory name (default: extracted from URL)
--branch <branch>Track a specific branch (overrides URL-embedded /tree/<ref>/...)
--tag <tag>Pin to a specific tag
--rev <sha>Pin to a specific commit SHA
--subdir <path>Restrict discovery to <clone>/<path>/*/SKILL.md (v0.13+, overrides URL-embedded subdir)
--role <role>Override the type-default role (v0.14+). Validated against valid_roles() for the chosen type.

--branch, --tag, --rev are mutually exclusive.

Choosing the right role (v0.14+)

The role field decides what tome does with a configured directory:

RoleBehavior
managedRead-only upstream (package manager owns content). Discovery only.
syncedBoth discovery AND distribution — skills found here are pulled into the library, and distribution symlinks are also written back into this dir.
sourceDiscovery only. tome reads but never writes here.
targetDistribution only. tome writes symlinks here but doesn’t scan for skills.

The defaults bite if you don’t know them. When you omit --role, the directory’s role falls back to its type default:

  • claude-pluginsmanaged
  • directorysynced
  • gitsource

The directory → synced default is the one that surprises people. If you tome add a local directory owned by a package manager (e.g. ~/.pfw/skills/), the synced default writes ~170 distribution symlinks INTO that source directory — polluting it with content tome propagated from other configured directories. Use --role source or --role managed for read-only package manager directories to keep them clean.

# WRONG (default role = synced; tome writes BACK into ~/.pfw/skills/)
tome add ~/.pfw/skills

# OK — discovery only, no write-back, but library entries get
# `managed: false` (treated as a generic local source).
tome add ~/.pfw/skills --role source

# BEST (v0.15+) — Managed semantic: library entries get `managed: true`,
# so reconcile + foreign-symlink protection recognize this as an
# external-package-manager-owned source.
tome add ~/.pfw/skills --role managed

source vs managed for flat-directory package managers (v0.15+)

Both refuse to write back into the source dir, so either keeps ~/.pfw/skills/ clean. The differences are subtle but material if you care about upstream-update semantics down the line:

Aspectsourcemanaged
tome writes to the source dirNeverNever
Manifest managed: bool flagfalse (local skill)true (package-manager-owned)
Future reconcile / MarketplaceAdapter integrationNot eligibleEligible (when adapters are added per upstream)
Foreign-symlink protection treats source path as legitimate-originNot yetNot yet (separate follow-up)
Semantic accuracy for pfw / npm / etc.“It’s just a local source”“It’s owned by a specific package manager”

For today’s needs (just propagating pfw skills to your other tools), both work equivalently. For longer-term integration with pfw’s own update lifecycle, prefer managed.

The success message now echoes the resolved role so you see what you got:

✓ Added directory 'pfw' (git: https://..., role: source)
  → Source (skills discovered here, not distributed here)

tome remove

Split into two subcommands since v0.10 (Phase 14, D-API-2):

tome remove dir <name>

Remove a configured directory entry from tome.toml. Manifest entries owned by that directory transition to Unowned (per LIB-04) — library content is preserved on disk; only the source_name linkage is cleared. Aggregates partial-cleanup failures and exits non-zero with a ⚠ N operations failed summary if any cleanup step fails. For git directories, the cached clone in ~/.tome/repos/<sha256>/ is removed.

FlagDescription
NAMEDirectory name to remove (as shown in tome status)
--yes / -ySkip confirmation prompt

tome remove skill <name>

Delete an Unowned skill from the library entirely — clears the manifest entry, removes the library directory, removes downstream distribution symlinks, removes the lockfile entry, and removes any machine.toml memberships. Refuses to operate on Owned skills with a hint to run tome remove dir first (per D-B2).

FlagDescription
NAMESkill name to delete
--yes / -ySkip confirmation prompt (default: no)

tome reassign

Reassign a skill to a different directory — useful when the same skill appears under multiple sources and you want to pin which directory owns it. Accepts both Owned skills (re-anchor between configured directories) and Unowned skills (re-anchor a previously-stranded skill back to a configured directory, per UNOWN-01 / D-API-1).

FlagDescription
SKILLSkill name to reassign
--to <directory>Target directory name (required)
--forceOverwrite if the target already has a different-content skill of the same name (per D-A1)

tome fork

Fork a managed (read-only) skill into a local directory so it can be edited. The local copy supersedes the managed one in the library.

FlagDescription
SKILLSkill name to fork
--to <local-directory>Target local directory name (required)
--yesSkip confirmation prompt

tome migrate-library

One-shot migration: convert a v0.9-shape library (where managed skills lived as symlinks pointing into the package manager’s cache) to the v0.10 library-canonical model (real-directory copies). Run once after upgrading from v0.9.x; idempotent on re-run.

Shows a plan summary (skill count + per-skill disk estimate via walkdir + metadata().len()) before any conversion, then prompts for confirmation. Broken symlinks are preserved in place per Phase 11 D-04.

FlagDescription
--yes / -ySkip the confirmation prompt (bypasses the UX-02 confirm gate)
--dry-runRender the plan; make no filesystem changes

tome list

FlagDescription
--jsonOutput as JSON

tome browse

Full-screen interactive skill browser using fuzzy search. Supports sorting, grouping by source, and per-skill actions (view source, copy path, disable/enable).

tome doctor

Diagnose library state. When run interactively (no --no-input, no --dry-run), surfaces issues and offers per-category repair prompts.

Orphan-directory repair (v0.14+)

When tome doctor finds a directory in the library that has no matching manifest entry (an “orphan”), it offers four choices per orphan:

  • claim — Register the orphan in the manifest as an Unowned skill (v0.14+). Hashes the directory, writes a SkillEntry::new_unowned, and the entry distributes to your target / synced directories on the next tome sync. This is the proper fix when the orphan represents a real skill you want to keep (e.g., a directory you copied in by hand, or one whose source was removed but you want to preserve it).
  • keep — Leave the directory on disk; tome sync will re-register it IF it discovers the orphan from a configured source. Useful when you know the orphan’s source got temporarily disconnected and will come back. Note: for library-canonical orphans with no upstream source, this option is a no-op until you claim it or add a source that covers it.
  • delete — Remove the directory from disk permanently.
  • skip — Leave the orphan as-is; doctor will surface it again on the next run.

Broken-frontmatter skills (v0.16+)

tome doctor walks every manifest-tracked skill and parses its SKILL.md frontmatter. Failures surface as Library Warnings — for example:

! 'my-skill' has unparsable SKILL.md frontmatter: invalid YAML frontmatter
! 'other-skill' has no SKILL.md file

These are not auto-repairable — the user must edit the SKILL.md file (or remove the skill) by hand. Without this check, the failure was only visible as a one-line stderr warning during tome sync; it now persists in the doctor surface so broken skills get triaged. Use tome lint <PATH> for a deeper per-field frontmatter audit.

Real-dir-in-target repair (v0.16+)

If a distribution directory contains a real directory (not a symlink) whose name matches a library skill, tome doctor hash-compares the two:

  • Identical content — Surfaces as an auto-fixable Warning (real directory in target matches library content (should be a symlink)). The auto-repair pass deletes the real directory and replaces it with a symlink into the library. Typical cause: skills copied into the target dir manually before adopting tome, or by hand after the fact.
  • Diverging content — Surfaces as a no-repair Warning (real directory in target diverges from library content — reconcile manually). The user must decide whether to overwrite the local edits, fold them back into the library, or remove the target copy.
  • No matching library skill — Left alone; tome does not own un-paired directories in target dirs.

tome lint

FlagDescription
PATHSpecific skill directory to lint (default: entire library)
--format text|jsonOutput format (default: text)

Validates SKILL.md frontmatter: missing/mismatched names, description length, non-standard fields, Unicode tag codepoints. Exits with code 1 on errors (CI-friendly).

tome config

FlagDescription
--pathPrint config file path only

tome backup

Git-backed backup and restore. Subcommands:

SubcommandDescription
tome backup initInitialize git repo in the library for backup tracking
tome backup snapshot [-m MSG]Create a snapshot of the current library state
tome backup list [-n COUNT]Show backup history (default: 10 entries)
tome backup restore [REF]Restore library to a previous snapshot (default: HEAD~1)
tome backup diff [REF]Show changes since last backup (default: HEAD)

tome eject

Removes all of tome’s symlinks from distribution directories. Reversible — run tome sync to recreate them.

tome relocate

Moves the skill library to a new path, updating symlinks in all distribution directories. Detects cross-filesystem moves and warns when target symlinks need to be re-anchored.

tome completions

FlagDescription
SHELLShell to install for: bash, zsh, fish, powershell
--printPrint completions to stdout instead of installing

Configuration

tome reads two TOML files:

  • ~/.tome/tome.toml — the portable config (intended to be shared via dotfiles across machines).
  • ~/.config/tome/machine.tomlmachine-local preferences and path overrides (do not share this).

The split is intentional: the portable config describes the abstract topology (which directories tome cares about, what role each plays), while machine.toml describes how that topology maps onto this machine’s filesystem.

tome.toml — Portable Config

library_dir = "~/.tome/skills"
exclude = ["deprecated-skill"]

[directories.claude-plugins]
path = "~/.claude/plugins/cache"
type = "claude-plugins"

[directories.local-skills]
path = "~/.claude/skills"
type = "directory"
role = "synced"

[directories.team-skills]
path = "https://github.com/myorg/team-skills"
type = "git"
branch = "main"

[directories.antigravity]
path = "~/.gemini/antigravity/skills"
type = "directory"
role = "target"

Migrating from v0.5 or earlier? The [[sources]] and [targets.*] sections were replaced with a single [directories.<name>] map in v0.6. tome will refuse to load old-format configs and print a migration hint. There is no automated migration tool — copy each [[sources]] entry to a [directories.<name>] entry with role = "source" (or "managed" for claude-plugins), and each [targets.<name>] entry to a [directories.<name>] entry with role = "target".

Top-level fields

FieldDescription
library_dirPath to the consolidated skill library. Supports ~ expansion.
excludeList of skill names to skip during discovery.

[directories.<name>] — entries

A <name> is a kebab-case identifier. Each entry combines a type (how skills are discovered) with a role (whether it’s a source, a target, or both).

FieldRequiredDescription
pathYesFilesystem path (or git URL when type = "git"). Tilde-expanded.
typeNo (defaults to "directory")One of claude-plugins, directory, git.
roleNo (each type has a default)One of managed, synced, source, target.
branch / tag / revNo (git only, mutually exclusive)Pin a git directory to a branch, tag, or commit SHA.
subdirNo (git only)If the repo nests skills under a subdirectory.

Directory type

TypeDescription
claude-pluginsReads installed_plugins.json from the Claude Code plugin cache. Supports v1 (flat array) and v2 (namespaced object) formats. Always role = "managed".
directoryFlat scan for */SKILL.md directories. Default.
gitShallow-clones a remote repo into ~/.tome/repos/<sha256>/ and treats the clone as a directory source. Always role = "source".

Directory role

RoleDiscoveryDistributionTypical use
managed✓ (read-only)Plugin cache (e.g. Claude Code)
syncedA directory that is both a skill source AND a tool that consumes them (e.g. ~/.claude/skills)
sourceA skill repo or local skill directory
targetA tool that only receives skills (e.g. Codex, Antigravity)

tome init picks a sensible default role from the type, but you can override it per directory.

The directory model is fully data-driven: any new tool can be supported by adding a [directories.<name>] entry — no code changes required. The tome init wizard auto-discovers common tool locations via the built-in KNOWN_DIRECTORIES registry.

machine.toml — Machine-Local Preferences

# Skip these skills entirely on this machine
disabled = ["noisy-skill", "work-only-skill"]

# Don't distribute to these directories on this machine
disabled_directories = ["openclaw"]

# Per-directory skill filtering (mutually exclusive: pick disabled OR enabled per directory)
[directory.antigravity]
disabled = ["claude-only-skill"]

[directory.work-laptop]
enabled = ["work-skill-a", "work-skill-b"]  # allowlist — ONLY these are distributed

# Per-machine path overrides for `tome.toml::directories.<name>.path` (PORT-01..05, v0.9)
[directory_overrides.local-skills]
path = "/Users/alice-corp/.claude/skills"

[directory_overrides.team-skills]
path = "/opt/shared/team-skills"
FieldDescription
disabledList of skill names to skip during distribution (no symlinks created in any target).
disabled_directoriesList of directory names to skip entirely on this machine.
[directory.<name>].disabledSkills to exclude from a single directory (blocklist).
[directory.<name>].enabledAllowlist — ONLY these skills are distributed to this directory. Mutually exclusive with disabled per directory (MACH-04).
[directory_overrides.<name>].pathReplaces directories.<name>.path on this machine. Useful when the same tome.toml is shared across machines with different home layouts. Unknown override names emit a typo-target stderr warning.

Override application happens at config load (after tilde expansion, before Config::validate), so all downstream code sees the canonical post-override paths. Any validation failure caused by an override is wrapped with an error attributing the problem to machine.toml rather than the portable tome.toml.

tome status and tome doctor annotate (override) next to any path that came from machine.toml, so you can tell at a glance which paths are portable and which are machine-local.

The --machine <path> global flag overrides the default machine preferences path.

Lockfile

tome sync generates a tome.lock file in the tome home directory (~/.tome/tome.lock). This lockfile captures a reproducible snapshot of all skills — their names, content hashes, sources, and provenance metadata. Each sync diffs the new lockfile against the previous one and surfaces changes interactively.

The lockfile is designed to be committed to version control alongside the library, enabling multi-machine workflows where tome sync on a new machine can detect what changed since the last sync.

Library .gitignore

tome sync automatically generates a .gitignore in the library directory:

  • Managed skills (symlinked from package managers) are gitignored — they are recreated by tome sync
  • Local skills (copied into the library) are tracked in version control
  • Temporary files (tome.lock.tmp) are always ignored

This allows the library directory to serve as a git repository for portable skill management while keeping transient entries out of version control.

Cross-machine sync

tome’s library is designed to be a portable, version-controlled artifact — you commit ~/.tome/ to your dotfiles and clone it onto every machine you work on. This page walks the workflow end-to-end: setting up the source-of-truth machine, bootstrapping a fresh machine, and what happens when things drift.

Why this matters. Pre-v0.10, tome’s library was a thin layer of symlinks pointing into machine-specific marketplace caches; cloning the library onto a fresh machine was meaningless because the symlink targets didn’t exist. v0.10 makes the library a real-directory copy of every skill, with tome.lock recording exactly what versions are installed. Now the library is portable. See Architecture — Library-canonical model for the underlying mechanic.

Walkthrough — Machine A (source of truth)

Machine A is the machine you use to curate your skill library. You run tome init here, install plugins, edit local skills, and commit the result.

# 1. Set up tome on Machine A.
tome init

# 2. Curate your library — install Claude plugins, add git directories,
#    enable/disable skills via `tome browse`.
claude plugin install foo@bar
tome add https://github.com/my-org/my-skills.git
tome sync

# 3. Commit ~/.tome/ to your dotfiles (or whatever portable storage you
#    use). The library content is real-directory copies; the manifest
#    and tome.lock pin exactly what's installed.
cd ~/.tome
git init
git add .
git commit -m "Initial tome library"
git remote add origin git@github.com:you/your-dotfiles.git
git push -u origin main

What’s in ~/.tome/ after this:

  • tome.toml — your portable directory configuration (sources, distribution targets, exclude lists)
  • library/ — real-directory copies of every skill (managed and local)
  • .tome-manifest.json — content hashes and provenance for every library entry
  • tome.lock — Cargo.lock-shaped: pinned versions and content hashes for managed skills

Machine-specific preferences (~/.config/tome/machine.toml) are deliberately NOT in ~/.tome/ — they’re per-machine by design. Things like disabled skills, auto_install_plugins consent, and [directory_overrides.<name>] belong there. See Configuration — machine.toml Machine-Local Preferences for the full schema.

Walkthrough — Machine B (fresh machine)

Machine B is a fresh machine that’s never seen your library. You install tome and Claude Code, clone your dotfiles, and run tome sync.

# 1. Install tome (Homebrew or cargo).
brew install martinp7r/tap/tome
# or: cargo install tome

# 2. Install Claude Code (required if your library has Claude plugins).
#    See https://claude.com/product/claude-code for instructions.

# 3. Clone your dotfiles into ~/.tome (or wherever the portable artifact lives).
git clone git@github.com:you/your-dotfiles.git ~/.tome

# 4. First sync. tome reads tome.lock and reconciles what's actually
#    installed against what should be installed.
tome sync

On the first sync, tome detects drift (your machine has zero plugins installed; the lockfile expects N) and prompts:

Tome detected N missing or out-of-date managed plugins. Install/update them now?
> Yes (always — install on every sync)
  Yes (ask me again next time)
  No (never ask again on this machine)

Your choice persists in machine.toml::auto_install_plugins as one of Always | Ask | Never. Pick Always on a personal machine (auto-install on every sync); pick Never on a locked-down workstation where you want to inspect drift before applying; pick Ask to skip this run only and be prompted again next time.

Once you confirm, tome shells out to claude plugin install <plugin>@<marketplace> for every missing plugin, re-discovers the skill content, verifies the resulting content_hash matches tome.lock, and then distributes the skills to your configured target directories (~/.claude/skills, ~/.codex/skills, etc.). You’re set up.

Reference — tome.lock semantics

tome.lock is the cross-machine state contract. It’s the equivalent of Cargo.lock for your skill library: a snapshot of every installed version plus content hash that tome can use to bring a fresh machine into the same state.

Each managed-skill entry records:

  • name — the skill name
  • version — the actual installed version (display-only; see drift basis below)
  • content_hash — SHA-256 of the skill directory contents
  • source_name — the directory in tome.toml that owns the skill (Option<DirectoryName>None for Unowned skills)
  • previous_source — the previous owner if the skill has been re-anchored (closes the Phase 13 fork-in-place gap)
  • registry_id, git_commit_sha — provenance metadata when applicable

Drift basis: content_hash, NOT version. When tome sync reconciles the lockfile against actually-installed plugins, drift is computed from content_hash(library/<skill>) != lockfile.content_hash. The version string is display-only in the diff output (e.g. plugin X: 5.0.5 → 5.0.7). Because Claude CLI doesn’t accept --version on claude plugin install, true version pinning is upstream future work — see Architecture — Lockfile-authoritative reconciliation.

~/.config/tome/machine.toml:

auto_install_plugins = "always"  # auto-install on every sync (CI / personal)
auto_install_plugins = "never"   # warn-only; never modify; require manual install
auto_install_plugins = "ask"     # re-prompt on every sync that detects drift

The unset case (field absent / None) is treated as a first-time prompt — tome asks once per tome sync invocation that detects drift and persists your choice. Pick always once and tome remembers; pick never and you’ll see drift warnings on every sync but tome won’t touch installed plugins.

--no-install is a global flag that overrides the persisted choice for the current invocation. Use this when you want to inspect drift on a machine where auto_install_plugins = "always" is set:

tome sync --no-install

Mirrors Cargo’s --frozen / --locked semantics — temporary, doesn’t change the persisted setting.

Reference — directory_overrides for path remapping

Different machines have different home layouts. macOS keeps Claude plugins at ~/Library/Application Support/Claude/plugins/cache; Linux keeps them at ~/.claude/plugins/cache. Your portable tome.toml can only express one path; machine.toml::[directory_overrides.<name>] provides the machine-specific remap (PORT-01..05).

~/.config/tome/machine.toml on Linux:

[directory_overrides.claude-plugins]
path = "~/.claude/plugins/cache"

~/.config/tome/machine.toml on macOS would override the same name to a different path.

Override application happens at config load (after tilde expansion, before validation), so all downstream code sees the canonical post-override path. Unknown override directory names emit a typo-target stderr warning. Override-induced validation failures are wrapped with a distinct error attributing them to machine.toml rather than tome.toml.

tome status and tome doctor annotate any directory whose path was rewritten by an override with (override) so you can tell at a glance which paths come from the portable config and which come from the machine-local layer.

See Configuration for the full schema.

Reference — what happens when claude is missing on Machine B

The ClaudeMarketplaceAdapter shells out to the claude binary. If claude isn’t on PATH and your tome.toml has any [directories.<name>] with type = "claude-plugins", tome sync produces a clear actionable error naming the binary:

error: claude CLI not found on PATH — install Claude Code, or remove
[directories.<name>] entries with type = "claude-plugins" from tome.toml

Install Claude Code from https://claude.com/product/claude-code and re-run tome sync. If your library only has git-source skills and no Claude plugins, missing claude won’t break sync — you’d only see this error if the lockfile contains entries the Claude adapter would need to install. A library that’s purely git-sourced and local-source is fully portable to a machine without Claude installed.

Vanished plugins (the marketplace removed a plugin you had installed) surface as a stderr warning (plugin X vanished from marketplace Y; using preserved library copy) and tome sync continues. Distribution still happens from the preserved library copy.

Adapter install/update failures aggregate into a ⚠ N install operations failed summary; library distribution still completes for skills whose adapter calls succeeded; sync exits non-zero on partial install failure.

Reference — migrating a v0.9 library on Machine B

If your dotfiles repo predates v0.10, the library was stored as machine-specific symlinks. v0.10’s first tome sync against such a library refuses with a Conflict / Why / Suggestion error pointing at the migration command:

tome migrate-library --dry-run    # preview the conversion
tome migrate-library              # run it (confirmation prompt; default no)

The dry-run shows a summary table — count of symlinks to convert, approximate additional disk usage, per-skill SKILL / SOURCE / SIZE / STATUS columns — and the live run prompts via dialoguer::Confirm defaulting to no. Pressing anything other than y aborts cleanly with no filesystem mutation. See the v0.10 release notes for the full migration walkthrough.

For CI or non-interactive automation, use --yes (mirrors tome remove skill --yes):

tome migrate-library --yes

Under --no-input without --yes, migration bails with a Conflict / Why / Suggestion error — destructive operations require explicit consent in non-interactive mode.

Broken managed symlinks (target gone) are SKIPPED and preserved in place so you can recover manually. Idempotent on re-run; subsequent syncs proceed normally.

The conversion is one-way — there is no --undo-migrate. Commit your library directory to git (or back it up some other way) BEFORE running.

Development Workflow

tome uses a lightweight layered workflow for substantial changes:

  • GitHub Issues track product intent, roadmap placement, and user-visible scope.
  • OpenSpec tracks the change proposal, requirements, design notes, and implementation checklist.
  • GSD (.planning/ + /gsd:* commands) tracks phase and plan execution state — what’s been researched, discussed, planned, executed, and verified.
  • Git commits / PRs are the implementation evidence.

This is meant to improve traceability, not create process theater. If the workflow becomes bureaucratic sludge, scale it back.

When to Use This Workflow

Use the full OpenSpec + GSD flow for:

  • new features
  • significant refactors
  • architecture-impacting changes
  • process or documentation changes that affect future development behavior
  • any change where requirements/design should be reviewed before implementation

You do not need the full workflow for:

  • typo fixes
  • tiny bug fixes with obvious scope
  • narrowly scoped internal cleanups
  • mechanical edits with no design impact

For small fixes, issue → code → PR is fine. /gsd:quick or /gsd:fast can be used for small-but-structured work without the full planning overhead.

Role of Each Layer

GitHub Issues

Use GitHub Issues for:

  • deciding what to work on
  • roadmap / milestone planning
  • discussion with humans
  • repository-visible backlog management

GitHub Issues answer: why does this work exist at all?

OpenSpec

Use OpenSpec for:

  • change proposals
  • requirement deltas
  • design decisions
  • implementation checklists for substantial changes

OpenSpec answers: what are we changing, and why does the shape of the change make sense?

Typical artifact layout:

openspec/changes/<change-id>/
├── proposal.md
├── design.md
├── tasks.md
└── specs/<capability>/spec.md

Core OpenSpec flow

# create a new change scaffold
openspec new change <change-id>

# inspect what exists
openspec list
openspec show <change-id>
openspec status --change <change-id>

# validate before implementation / archival
openspec validate <change-id>

# after implementation is complete
openspec archive <change-id>

GSD

Use GSD for:

  • turning a milestone into phases, and phases into executable plans
  • tracking which plans are ready, in progress, and done
  • recording verification outcomes tied to implementation
  • advancing STATE.md across phase transitions

GSD answers: what should be worked on next, who’s working on it, and what already landed?

Selected artifacts under .planning/ (list is non-exhaustive — GSD adds files as new workflows are used):

.planning/
├── PROJECT.md                          # core value, constraints, decisions, requirements
├── ROADMAP.md                          # milestones, phases, status
├── REQUIREMENTS.md                     # requirement IDs and traceability
├── STATE.md                            # current focus, current phase/plan
└── phases/<NN>-<name>/
    ├── <NN>-CONTEXT.md                 # context gathered before planning
    ├── <NN>-RESEARCH.md                # technical approach research (when created)
    ├── <NN>-DISCUSSION-LOG.md          # /gsd:discuss-phase transcript (when created)
    ├── <NN>-UI-SPEC.md                 # UI/UX design contract for frontend phases (when created)
    ├── <NN>-<MM>-<slug>-PLAN.md        # executable plan per wave/task
    ├── <NN>-<MM>-<slug>-SUMMARY.md     # created when each plan completes
    └── <NN>-VERIFICATION.md            # created by the verifier when the phase completes

Minimal command flow used in tome:

# see current state and next actions
/gsd:progress

# gather phase context, then plan, then execute
/gsd:discuss-phase <N>
/gsd:plan-phase <N>
/gsd:execute-phase <N>

# verify manually when needed
/gsd:verify-work <N>

# capture follow-up ideas without leaving flow
/gsd:add-backlog "<idea>"
/gsd:note "<short note>"

When creating a GSD phase that implements an OpenSpec change, reference the OpenSpec change id in the phase CONTEXT.md and in commit/PR footers.

Default Flow for Significant Changes

  1. Start from a GitHub issue or clear idea.
  2. Create an OpenSpec change for the substantial work.
  3. Write or refine:
    • proposal.md
    • design.md
    • tasks.md
    • any relevant spec delta files
  4. Bring the work into GSD by adding a phase to .planning/ROADMAP.md (or creating a new milestone with /gsd:new-milestone).
  5. Use /gsd:plan-phase and /gsd:execute-phase to drive implementation. Each plan’s SUMMARY.md becomes the per-plan closure note; the phase’s VERIFICATION.md is the phase-level sign-off.
  6. Land code in normal git commits / PRs.
  7. Archive the OpenSpec change when the work is complete.

Traceability Convention

For meaningful changes, link the layers explicitly. Don’t invent a new footer shape per PR — use one of the two forms used in recent merged PRs:

Small / incremental PRs — a one-liner in the commit body is enough:

Refs #123
OpenSpec: <change-id>

Phase-closing PRs — add a ## Traceability section in the PR body:

## Traceability

- Requirements: WHARD-04, WHARD-05, WHARD-06
- Phase artifacts: .planning/phases/05-wizard-test-coverage/
- OpenSpec: <change-id>   (if an OpenSpec change exists)

This gives a practical audit trail across backlog, planning, execution, and code history.

Practical Rule of Thumb

  • GitHub Issue = backlog / business reason
  • OpenSpec = requirements + design + checklist
  • GSD = execution state (phases, plans, verification)
  • git / PR = shipped evidence

Don’t stack ceremony for its own sake. Use the minimum structure needed to stop future-you from asking, “What the hell were we doing here?”

Architecture

System Diagram (Excalidraw) — interactive diagram showing the discovery → library → distribution flow. The diagram pre-dates v0.10 and does not depict the marketplace adapter dispatcher or unowned lifecycle; the broad three-tier shape is still accurate. Refresh deferred to a follow-up.

Rust workspace (edition 2024) with a single crate producing one binary.

crates/tome — CLI (tome)

The main binary. All domain logic lives here as a library (lib.rs re-exports all modules) with a thin main.rs that parses CLI args and calls tome::run().

Sync Pipeline

The core flow that tome sync and tome init both invoke (lib.rs::sync):

  1. Reconcile (reconcile.rs) — Lockfile-authoritative drift detection for managed skills, run first against the previously-saved manifest and tome.lock. Each managed skill is classified as Match / Drift / Vanished; Drift entries optionally apply via the marketplace adapter (subject to auto_install_plugins consent and the --no-install override). Edit-in-library detection raises a fork/revert/skip prompt when a managed skill’s library content_hash diverges from its lockfile entry (RECON-05). See Lockfile-authoritative reconciliation for details.
  2. Discover (discover.rs) — Walk every directory whose role is managed, synced, or source looking for */SKILL.md. Three directory types: ClaudePlugins (reads installed_plugins.json), Directory (flat walkdir scan), and Git (shallow-clones into ~/.tome/repos/<sha256>/ and then scans the clone). First directory wins on name conflicts; the exclude list is applied.
  3. Consolidate (library.rs) — Both managed AND local skills are stored as real-directory copies in the library — the library is the canonical home for every skill (LIB-01 / LIB-02). Managed skills (Claude plugins, git clones) get a recursive copy from the source on first sync; subsequent updates flow through the marketplace adapter (reconcile.rs) in step 1. Local skills (directory/synced sources) are copied as before. The managed: bool flag now means update channel — managed = upstream feeds updates into the library; local = library is canonical and edited in-place. It no longer means “stored as a symlink”. A manifest (.tome-manifest.json) tracks SHA-256 content hashes for idempotent updates: unchanged skills are skipped, changed skills are re-copied. Sync against a v0.9-shape library (managed = symlink) is refused with a hint pointing at tome migrate-library.
  4. Distribute (distribute.rs) — Push library entries (Owned and Unowned alike) to every directory whose role is synced or target via symlinks. The library is canonical, so distribution is a downhill operation; Unowned skills (manifest source_name = None) still get distributed because library content is preserved per LIB-04. Skills disabled in machine.toml (globally or per-directory) are skipped, as are directories on the disabled_directories list. distribute refuses to clobber pre-existing symlinks pointing outside the current library (HARD-09 foreign-symlink protection).
  5. Cleanup (cleanup.rs) — Stale-candidate skills are partitioned into three buckets per UX-01: removed-from-config (the source directory was removed from tome.toml — manifest entry transitions to Unowned, library content preserved per LIB-04), missing-from-disk (the source directory still exists but the file vanished — library copy removed), now-in-exclude-list (skill added to machine.toml::disabled or per-directory blocklist/allowlist — distribution symlinks removed, library copy preserved). Each bucket renders with a header and per-skill resolution hint to stderr. Broken symlinks in distribution directories are also cleaned, with HARD-09 origin verification before any removal.
  6. Lockfile (lockfile.rs) — Generate tome.lock capturing a reproducible snapshot of the library state for diffing on the next sync. Each entry now carries previous_source so cross-machine forks-in-place stay traceable to the directory that originally owned the skill.

Other Modules

Listed roughly in alphabetical order:

  • add.rstome add command (plan/render/execute pattern). Registers a directory in tome.toml.
  • backup.rs — Git-backed snapshot/restore/diff for the library. The pre-restore safety snapshot is the only recovery path if a restore was accidental, so restore aborts if the snapshot fails (#415).
  • browse/ — TUI browser (tome browse): app.rs (state + key handling), ui.rs (ratatui rendering), theme.rs (adaptive dark/light), fuzzy.rs (nucleo-matcher), markdown.rs (preview rendering). The status bar uses a StatusMessage { Success | Warning | Pending } enum (POLISH-02) so glyph + colorization stay consistent. Disable/Enable actions are wired to MachinePrefs via the smart-routing scope resolver (HARD-21 D-BROWSE-1: per-directory blocklist > per-directory allowlist > global).
  • cleanup.rs — Three-bucket cleanup output (UX-01). cleanup_library emits Buckets A (removed-from-config — Owned-to-Unowned transition per LIB-04) and B (missing-from-disk — library entry removed); lib.rs::cleanup_disabled_from_target emits Bucket C (now-in-exclude-list — distribution symlinks removed, library content preserved). Bucket C entries are collected into a sibling Vec<ExcludedSkill> and rendered alongside A+B by cleanup::render_cleanup_buckets (called from lib.rs::sync) for a single user-facing surface. All output goes to stderr (D-UX01-4). Cleanup no longer auto-deletes orphaned skills (LIB-04); orphan transitions are the unowned-lifecycle entry point.
  • config/ — TOML config at ~/.tome/tome.toml, split into mod.rs (load/save), types.rs (DirectoryName, DirectoryType = ClaudePlugins/Directory/Git, DirectoryRole = Managed/Synced/Source/Target, DirectoryConfig), overrides.rs (apply_machine_overrides merges [directory_overrides.<name>] from machine.toml after tilde expansion and before validation, PORT-01..04), and validate.rs. Config::save_checked round-trips ~/-shaped paths via paths::unexpand_tilde so dotfile-committed configs stay portable (HARD-22).
  • discover.rs — Skill discovery from all configured directories. ScanMode::{Local, ManagedNoProvenance, ManagedWith} replaces the v0.9 Option<Option<SkillProvenance>> (HARD-05).
  • distribute.rs — Distribution to synced / target directories via Unix symlinks. HARD-09 foreign-symlink detection uses a 2x2 canonicalize-vs-lexical-prefix matrix to handle macOS /var → /private/var-style middle symlinks without false positives.
  • doctor.rs — Diagnoses library issues (orphan directories, missing manifest entries, broken legacy symlinks, missing directory paths) and surfaces the unowned set in a NAME / LAST-KNOWN SOURCE / SYNCED tabled section. Per Phase 14 D-D3, the unowned set is informational and does NOT contribute to total_issues(). Annotates (override) for paths sourced from machine.toml (PORT-05). v0.11 adds issue categorization (IssueCategory = Library / Directory / Config / Foreign-symlink, OBS-06) with per-category counts in the text summary and summary.by_category + summary.auto_fixable_by_category maps in JSON output. Auto-repair dispatch uses typed RepairKind enum discrimination (POLISH-04 sentinel pattern); adding a new repair without a handler fails to compile. The pre-v0.11 “N auto-fixable issues / (no auto-repair available)” contradiction is closed (FIX-01 / #530).
  • eject.rs — Remove all of tome’s distribution symlinks (reversible via tome sync).
  • git.rs — Git clone / pull for type = "git" directories. Shallow clones to ~/.tome/repos/<sha256>/, with branch/tag/rev ref pinning and SHA captured in the lockfile.
  • install.rs — Shell completion installation. (The v0.9 reconcile-managed-plugins logic that used to live here moved to reconcile.rs in Phase 13.)
  • library.rsconsolidate() — copies both managed and local skills as real directories into the library (LIB-01 / LIB-02). consolidate_managed performs a recursive walkdir::WalkDir::follow_links(true) copy on first sync and on every reconcile-driven update; consolidate_local mirrors the same content_hash-flag-flip path. Refuses to operate on v0.9-shape (managed = symlink) entries.
  • lint.rs — Validates SKILL.md frontmatter; downcastable LintFailed error mapped to exit code 1 by main.rs (HARD-04).
  • lockfile.rs — Generates and loads tome.lock files. Each LockEntry carries name, content_hash, source_name: Option<DirectoryName> (None = Unowned), previous_source: Option<DirectoryName> (Phase 14 D-C1 cross-machine breadcrumb), version, registry_id, and git_commit_sha. Top-level fields are pub(crate) with read-accessors (HARD-06). The lockfile is now authoritative for managed-skill drift detection (RECON-01..05) — reconcile.rs reads it on every sync. Atomic temp+rename writes.
  • machine.rs — Per-machine preferences (~/.config/tome/machine.toml). Tracks disabled skill set, disabled_directories set, per-directory disabled/enabled skill filtering (DirectoryPrefs, MACH-04), [directory_overrides.<name>] path remapping (PORT-01), and auto_install_plugins: AutoInstall { Always, Ask, Never } (RECON-02). Hostile-input rejection in apply_machine_overrides covers .. traversal, NUL bytes, broken/looping symlinks, and duplicate target paths. Atomic temp+rename writes.
  • manifest.rs — Library manifest (.tome-manifest.json). Each SkillEntry records source_name: Option<DirectoryName> (None = Unowned per LIB-03) and previous_source: Option<DirectoryName> (the last directory that owned the entry — Phase 14 D-C1, also closes Phase 13 D-13 fork-in-place lossy-trace gap). Twin-constructor pattern: SkillEntry::new for owned entries, SkillEntry::new_unowned for entries materialised directly into the unowned state. Provides hash_directory() for deterministic SHA-256 of directory contents. v0.11 adds last_synced_at: Option<String> (RFC-3339) at the manifest level, stamped inside the !dry_run guard immediately before manifest::save so tome status can surface the last-sync timestamp (OBS-07 / D-LSYNC-3). Schema lift is additive — pre-v0.11 manifests deserialize cleanly with last_synced_at: None. Atomic temp+rename writes.
  • marketplace.rsMarketplaceAdapter trait (six methods: id, current_version, install, update, list_installed, available) plus ClaudeMarketplaceAdapter (subprocess to claude plugin install/update, parses claude plugin list --json, RefCell cache that auto-invalidates on Ok install/update) and GitAdapter (thin shim over git.rs). Failure aggregation via InstallFailure / InstallOp / InstallFailureKind mirrors the RemoveFailure pattern; InstallFailureKind::ALL plus a const-fn drift guard pin compile-time exhaustiveness (POLISH-04). Test mock MockMarketplaceAdapter lives in marketplace::testing behind the test-support feature.
  • migration_v010.rs — One-shot tome migrate-library command for converting v0.9-shape libraries (managed = symlink) to v0.10 shape (managed = real-directory copy). Idempotent; broken symlinks preserved per Phase 11 D-04. Confirm-or-abort gate via dialoguer::Confirm::default(false) with --yes/-y bypass (UX-02 / Phase 14 D-B3); --no-input without --yes bails with a Conflict/Why/Suggestion message. Migration plan summary uses tabled::Style::rounded() with NAME / SOURCE / SIZE / STATUS columns; per-skill disk size is computed via walkdir + metadata().len() (follow_links(false)). Slated for removal in v0.11+ once all known users have migrated.
  • paths.rsTomePaths struct bundling tome_home/library_dir/config_dir to prevent parameter swaps. expand_tilde / unexpand_tilde round-trip pair (HARD-22). Symlink path utilities: resolves relative symlink targets to absolute paths and checks whether a symlink points to a given destination. collapse_home for display.
  • reassign.rstome reassign <skill> --to <dir> command. Plan/render/execute. Phase 14 D-API-1: accepts Unowned input (re-anchors source_name: None to a configured directory). The --force flag bypasses D-A1 different-content collision detection; D-A2 refuses target-only directory roles. Re-anchor clears previous_source (Phase 14 D-C1). HARD-19 plan/execute filesystem snapshot eliminates drift between phases. The originally-proposed tome adopt verb was folded into this command (vocabulary supersession; see Unowned lifecycle).
  • reconcile.rstome sync reconciliation core. Classifies each managed skill in the lockfile as Match / Drift / Vanished (ReconcileClass); resolves auto_install_plugins consent; renders per-skill diff before applying installs/updates; verifies post-install content_hash; surfaces edit-in-library detection with the fork/revert/skip 3-way prompt (RECON-01..05). Drift detection is content_hash-based, not version-based (Phase 11 D-08).
  • relocate.rs — Move the skill library to a new path with full safety guarantees: detects cross-filesystem moves with a Phase 7 D-10 Conflict/Why/Suggestion recovery hint (HARD-18), re-anchors all distribution symlinks, calls warn_if_unreadable_symlink (intent-first naming per HARD-16) on unreadable managed-skill symlinks instead of silently dropping provenance.
  • remove.rstome remove dir <name> and tome remove skill <name> (Phase 14 D-API-2 subcommand split). tome remove dir transitions every owned manifest entry to Unowned and preserves library content (Phase 11 D-10). tome remove skill deletes an Unowned skill entry, its library directory, downstream distribution symlinks, lockfile entry, and machine.toml memberships (D-B1); refuses Owned skills with a hint to use tome remove dir first (D-B2). Confirms via interactive prompt unless --yes/-y (D-B3). Failure aggregation via Vec<RemoveFailure> + FailureKind::ALL POLISH-04 sentinel. The originally-proposed tome forget verb was folded into the skill subcommand (vocabulary supersession; see Unowned lifecycle).
  • status.rs — Read-only summary of library, directories (with type/role + override annotations), and health. Renders an Unowned skills section (NAME / LAST-KNOWN SOURCE / SYNCED) when any entries have source_name = None (UNOWN-03). v0.11 adds a top-line Last sync: <RFC-3339> (or never) line and a SKILLS column on the Directories table (OBS-07); JSON shape gains top-level last_sync: Option<String> and per-directory skill_count. Single-pass directory scan for efficiency.
  • summary.rsSkillSummary shared type (NAME / LAST-KNOWN SOURCE / SYNCED columns) consumed by status.rs and doctor.rs Unowned sections. JSON-stable (previous_source serialises explicit null rather than being skipped).
  • update.rs — Lockfile diffing and interactive triage logic, invoked by tome sync to surface added/changed/removed skills and offer to disable unwanted new skills. (Per-managed-skill version reconciliation moved to reconcile.rs in Phase 13; this module retains only the pre-cleanup user-presented diff.)
  • wizard.rs — Interactive tome init setup using dialoguer (MultiSelect, Input, Confirm, Select). Uses the merged KNOWN_DIRECTORIES registry (WIZ-01, hardened in v0.7) to auto-discover common tool locations (~/.claude/plugins/cache, ~/.claude/skills, ~/.codex/skills, ~/.gemini/antigravity/skills, etc.). Detects pre-v0.6 legacy configs and offers cleanup (WUX-03). All diagnostic chrome routes to stderr (HARD-15).

Key Patterns

  • Library-canonical model: Discovery directories →(consolidate)→ Library →(distribute)→ Distribution directories. The library is the source of truth and every skill (managed or local) lives there as a real directory copy. Managed skills use a marketplace adapter (reconcile.rs + marketplace.rs) to pull updates from upstream; local skills are edited in-place in the library. Distribution always uses Unix symlinks (std::os::unix::fs::symlink) pointing into the library. Unix-only. See Library-canonical model for the full mechanics.
  • Directories are data-driven: config::directories is a BTreeMap<DirectoryName, DirectoryConfig> — any tool can be added as a directory with a role without code changes. The wizard’s KNOWN_DIRECTORIES registry is used purely for auto-discovery convenience.
  • Roles, not “sources vs targets”: A directory can be managed (read-only source), source (discovery only), target (distribution only), or synced (both — same dir is read AND written, e.g. ~/.claude/skills). The pipeline asks each directory’s role what to do with it; there is no separate “sources” vs “targets” config.
  • dry_run threading: Most operations accept a dry_run: bool that skips filesystem writes but still counts what would change. Results report the same counts either way.
  • Atomic writes: manifest.json, tome.lock, and machine.toml are always written via temp file + rename. The temp file is in the same directory as the target so the rename is atomic on POSIX.
  • Plan/render/execute: add, remove, reassign, relocate, eject build an explicit plan, render it for the user, and only then execute. Dry-run is free; tests can assert plan structure without touching the filesystem.
  • Newtypes at boundaries: SkillName, DirectoryName, ContentHash, TomePaths validate at construction so downstream code doesn’t have to. The shared validate_identifier rejects empty names, path separators, ., and ...
  • Error handling: anyhow for the application; .with_context() adds path context to every fs error. Missing sources/paths produce stderr warnings rather than hard errors. Symlink operations always verify the link points into the library before deleting.
  • Per-machine portability: The portable tome.toml describes the abstract topology; machine.toml provides path overrides ([directory_overrides.<name>]) and machine-local opt-outs. Override application happens at config load, before validation, so all downstream code sees post-override paths.

Library-canonical model

The library at ~/.tome/library/ is the single source of truth for every skill on the machine — managed AND local. Both kinds are stored as real directory copies; neither is a symlink into a marketplace cache.

What changed in v0.10 (vs. v0.9): Pre-v0.10, managed skills (Claude plugins, git clones) lived in the library as symlinks pointing back at the package manager’s cache. Updating a plugin updated the library transparently via the symlink. The trade-off was that removing a source erased every skill it provided, and shipping the library across machines was impossible because the symlink targets weren’t portable. v0.10 inverts the relationship: consolidate_managed performs a recursive copy on first sync (and on every update afterward via the marketplace adapter), so the library on disk is fully self-contained.

Why this matters:

  • Cross-machine portability~/.tome/ can be committed to dotfiles and cloned onto a fresh machine. Pair with tome.lock for exact-version reproducibility (see Lockfile-authoritative reconciliation below). See Cross-machine sync for the end-to-end walkthrough (Machine A source-of-truth → Machine B fresh-machine bootstrap).
  • Source removal preserves content — removing a [directories.*] entry from tome.toml (or running tome remove dir <name>) no longer erases the skills it provided. Their manifest entries transition to Unowned (source_name: None); library content stays in place (LIB-04).
  • Vanished plugins stay usable — if a marketplace removes a plugin, tome sync warns but keeps using the preserved library copy.
  • Drift basis is content_hash, not version (Phase 11 D-08). Reconcile compares content_hash(library/<skill>) against the lockfile entry; the version string is display-only in diff output (e.g. plugin X: 5.0.5 → 5.0.7). Because the upstream claude plugin install command doesn’t accept --version, true version pinning is upstream future work.

Migration path: v0.9-shape libraries (managed = symlink) need to run tome migrate-library once. Detection: library_dir/<name>.is_symlink() && manifest[name].managed == true && manifest.contains_key(name) (Phase 11 D-03). The command shows a summary table (skill count + per-skill disk estimate via walkdir + metadata().len()), prompts for confirmation, then converts symlinks to real copies. Broken symlinks are preserved in place per Phase 11 D-04. Idempotent on re-run; tome sync refuses to operate on v0.9-shape libraries until migration runs. The migration is a one-shot CLI command, NOT auto-on-first-sync (Phase 11 D-01 supersedes the original UX-02 wording).

Lockfile-authoritative reconciliation

tome.lock is the cross-machine state contract. Cargo.lock-shaped: each managed skill records (name, version, content_hash, source_name, previous_source, registry_id, git_commit_sha). On every tome sync, reconcile.rs::reconcile_lockfile classifies each managed skill into one of four buckets:

  • Matchcontent_hash(library/<skill>) == lockfile.content_hash. Nothing to do.
  • Driftcontent_hash differs OR the lockfile expects a version the adapter no longer provides. Renders a per-skill diff (plugin X: 5.0.5 → 5.0.7); applies the install/update via the marketplace adapter; verifies the resulting library content_hash matches the lockfile entry.
  • Vanishedadapter.available() returns false (the marketplace no longer offers the plugin). Stderr warning, distribution continues from the preserved library copy (RECON-04).
  • Edited-in-librarymanaged: true and the library content_hash diverges from the lockfile in a way that looks like an in-place user edit rather than upstream drift. See “Edit-in-library detection” below.

Auto-install consent (RECON-02) — the first sync on a machine with a non-empty drift set prompts: Auto-install missing plugins on every sync? [Y/n/never]. The choice persists in machine.toml::auto_install_plugins as one of Always | Ask | Never. The --no-install global flag overrides the persisted choice for the current invocation (mirrors Cargo’s --frozen / --locked).

Edit-in-library detection (RECON-05) — when a managed skill’s library content_hash diverges from the lockfile, the user is prompted with three choices: fork (default — promote to local via the existing tome fork machinery), revert (overwrite from marketplace), skip (warn and don’t touch this entry this sync). In --no-input mode the default is skip with warning so edited content is never silently overwritten.

previous_source breadcrumb — when a managed skill forks in-place (Drift → fork), the manifest entry records the old source_name in previous_source before flipping to local. This closes the Phase 13 D-13 “lossy fork-in-place” gap; tome status and tome doctor show the last-known source so the user can re-anchor cleanly later via tome reassign <skill> --to <dir>.

Partial-failure surfacing (ADP-04) — adapter install/update errors aggregate into Vec<InstallFailure> and render as a grouped ⚠ N install operations failed summary (matches the v0.8 SAFE-01 RemoveFailure pattern). Library distribution still completes for skills whose adapter calls succeeded; sync exits non-zero on partial failure.

Marketplace adapter trait

marketplace.rs defines the MarketplaceAdapter trait that isolates install / update / availability logic per marketplace:

#![allow(unused)]
fn main() {
pub trait MarketplaceAdapter {
    fn id(&self) -> &str;
    fn current_version(&self, plugin_id: &str) -> Result<Option<String>>;
    fn install(&self, plugin_id: &str) -> Result<()>;
    fn update(&self, plugin_id: &str) -> Result<()>;
    fn list_installed(&self) -> Result<Vec<InstalledPlugin>>;
    fn available(&self, plugin_id: &str) -> Result<bool>;
}
}

v0.10 ships two production adapters and a feature-gated test mock:

  • ClaudeMarketplaceAdapter (ADP-02) — Shells out to claude plugin install, claude plugin update, claude plugin list --json. Caches the parsed list output in RefCell<Option<Vec<InstalledPlugin>>>; the cache auto-invalidates on Ok install/update calls. Missing claude on PATH surfaces as a clear actionable error message naming the binary. Upstream constraint: claude plugin install doesn’t accept --version, so the adapter installs “latest” only; the lockfile records the actual installed version and surfaces drift on subsequent syncs.
  • GitAdapter (ADP-03) — Thin shim over crates/tome/src/git.rs; behavior for existing git directories is byte-for-byte unchanged from v0.9.
  • MockMarketplaceAdapter — Lives in marketplace::testing behind the test-support feature. Used by integration tests to inject deterministic install/update/availability behavior without invoking real subprocesses.

Failure aggregation: InstallFailure + InstallOp + InstallFailureKind

  • a Kind::ALL exhaustive sentinel mirror the remove.rs::FailureKind pattern (POLISH-04). Adding a new failure kind without updating ALL is a compile error.

Unowned lifecycle

A skill’s manifest entry has source_name: Option<DirectoryName> (LIB-03). Some(<dir>) = Owned (the directory in tome.toml provides the source); None = Unowned (the library copy is canonical with no upstream source).

Transitions to Unowned:

  • Cleanup orphan — when a [directories.*] entry is removed from tome.toml (manually edited or via tome remove dir <name>), every manifest entry whose source_name pointed at the removed directory transitions to source_name: None on the next tome sync. Library content preserved (LIB-04). This is Bucket A in the cleanup output — see Sync Pipeline step 5.
  • tome remove dir <name> — explicitly transitions all manifest entries owned by <name> to Unowned and preserves library content (Phase 11 D-10).
  • Fork-in-place (managed → local during reconcile drift) — source_name stays the same value (it points at the now-local directory), but previous_source records the original managed source_name so the user can re-anchor.

Whenever the manifest transitions an entry to Unowned, previous_source captures the old source_name value (Phase 14 D-C1). tome status and tome doctor use this value to render the LAST-KNOWN SOURCE column.

CLI verbs (Phase 14 D-API-1 / D-API-2 vocabulary merge):

  • Re-anchortome reassign <skill> --to <dir>. Accepts Unowned input. The originally-proposed tome adopt was folded into existing tome reassign machinery; the work is identical (copy content into <dir>, update manifest source_name).
  • Deletetome remove skill <name>. Confirmation prompt defaults to no; --yes/-y skips. Cleans manifest entry + library directory + distribution symlinks + lockfile entry + machine.toml memberships (D-B1). Refuses on Owned skills with a hint to run tome remove dir first (D-B2).

Surfacingtome status and tome doctor show an Unowned skills (N): section with NAME / LAST-KNOWN SOURCE / SYNCED columns. JSON output includes the new unowned (status) / unowned_skills (doctor) field. Per Phase 14 D-D3, the unowned set is informational and does NOT contribute to tome doctor’s total_issues(); exit code is unaffected.

Observability (v0.11)

Sync/reconcile/consolidate/distribute/cleanup chatter routes through tracing::{info,warn,debug}! (OBS-01). Wizard prompts (dialoguer), TUI browse output, and user-facing summary tables (tome status/list/doctor tables, tome sync final summary) stay on direct stdout — tracing is for log-like output only.

The LogLevel enum (HARD-07) maps to a tracing_subscriber::EnvFilter (OBS-02):

  • Default level: info.
  • --verbose raises to debug.
  • --quiet lowers to warn.
  • TOME_LOG=tome::sync=debug,tome::reconcile=info (or any EnvFilter string) overrides the flag-derived level (D-ENV-1).

tome sync --verbose emits one span per pipeline step (discover, reconcile, consolidate, distribute, cleanup) with an elapsed_ms field on span close (OBS-03). Spans nest under a top-level sync span so a single run produces a hierarchical trace.

When consolidate / distribute re-emits a skill, the cause field on the info! event names why — one of hash changed, previously failed, newly added, or directory now allowed (OBS-04). The final sync summary block includes a reconcile classification line (reconcile: N match · M drift · K vanished · L missing-from-machine, OBS-05) above the cleanup buckets.

PreviouslyFailed and DirectoryNowAllowed causes are documented but deferred to a future schema bump — the substrate is in place, the emit sites need a per-skill failure-history field on SkillEntry.

Testing

Unit tests are co-located with each module (#[cfg(test)] mod tests). Integration tests live in crates/tome/tests/ and exercise the binary via assert_cmd — post-HARD-13 (v0.10) the original cli.rs was split into per-domain files (cli_sync.rs, cli_doctor.rs, cli_status.rs, cli_init.rs, cli_make_release.rs, etc.) with shared helpers under tests/common/. Snapshot tests use insta (filtered for tmpdir paths). Tests use tempfile::TempDir and assert_fs::TempDir for filesystem isolation — no cleanup needed.

CI

GitHub Actions runs on both ubuntu-latest and macos-latest: fmt check, clippy with -D warnings, tests, and release build.

AI Coding Tool Landscape

Research into agent file formats (skills, rules, memory, hooks, agents, plugins), invocation methods, context loading strategies, and format differences across AI coding tools — informing tome’s connector architecture.

Last updated: March 2026


1. The Full Taxonomy

AI coding tools have up to seven layers of configuration. Most discussions focus on the first three, but the extended structures (hooks, agents, plugins, MCP) are where the real divergence happens.

LayerPurposePortable?Who Has It
SkillsReusable instructions activated on demandYes (SKILL.md standard, 30+ tools)Claude, Codex, Copilot, Antigravity, Gemini CLI, Cursor, OpenCode, Amp, Goose
RulesAlways-on project/global conventionsPartially (markdown, but different filenames/formats)All tools
MemoryLearned context persisted across sessionsNo (completely tool-specific)Claude, Codex, Cursor, Windsurf, Copilot, OpenClaw
HooksLifecycle event handlersNo (tool-specific JSON/config)Claude (12 events), Codex, Windsurf, Amp
AgentsIsolated subagents with custom tools/modelNo (tool-specific markdown/YAML)Claude, Codex, Copilot, Cursor, Antigravity
PluginsBundles of skills + agents + hooks + MCPNo (tool-specific manifests)Claude, Cursor, Amp
MCP ServersExternal tool integrations via protocolYes (MCP is an open standard)All major tools

Key insight: Skills and MCP are the two truly portable layers. Everything else is tool-specific.


2. Tool-by-Tool Breakdown

Claude Code

Vendor: Anthropic | Type: CLI agent | SKILL.md: Full standard + extensions

AspectDetails
Instruction fileCLAUDE.md (project root, ~/.claude/CLAUDE.md global)
SkillsSKILL.md with extended frontmatter (disable-model-invocation, context: fork, agent, hooks, argument-hint)
Rules.claude/rules/ directory (markdown files)
Memory.claude/memory/MEMORY.md (auto-loaded), additional topic files
Other filesPlugins (plugin.json), Agents (.claude/agents/*.md), Hooks (hooks.json)
Skill discoveryPersonal (~/.claude/skills/), Project (.claude/skills/), Plugin, Enterprise, nested .claude/skills/ in subdirectories
Invocation/skill-name (slash command), Skill(skill: "name") (tool call), implicit model invocation
Context loadingDescription always in context (2% of window budget); full content on invocation; context: fork runs in subagent

Unique skill extensions beyond the standard:

  • disable-model-invocation: true — user-only invocation
  • user-invocable: false — model-only (background knowledge)
  • context: fork + agent: Explore — run skill in isolated subagent
  • `!command` syntax — inject shell output into skill content before sending to model
  • $ARGUMENTS, $0, $1 — argument substitution
  • hooks — lifecycle hooks scoped to the skill

Extended structures:

  • Hooks — 12 lifecycle events (SessionStart, PreToolUse, PostToolUse, Stop, etc.) with 3 hook types: command, prompt, agent. Configured in settings.json at global/project/local levels.
  • Agents.claude/agents/*.md with YAML frontmatter (11+ fields: tools, model, maxTurns, hooks, mcpServers, permissionMode, etc.). Isolated subagents with own context/tools/model.
  • Pluginsplugin.json manifest bundling skills + agents + hooks + MCP + LSP servers + output styles. Marketplace discovery via /plugin. Three install scopes: user/project/local.
  • Commands.claude/commands/*.md (legacy, superseded by skills but still supported). Simple markdown, no frontmatter.
  • Settings — Three-level hierarchy: ~/.claude/settings.json.claude/settings.json.claude/settings.local.json. Permissions, hooks, MCP servers. JSON schema available.
  • MCP.mcp.json at project root or in settings. Supports stdio/http/sse. Env var expansion: ${VAR}, ${VAR:-default}.

Codex CLI

Vendor: OpenAI | Type: CLI agent | SKILL.md: Standard + agents/openai.yaml

AspectDetails
Instruction fileAGENTS.md (project root), AGENTS.override.md takes priority
SkillsSKILL.md (standard) + optional agents/openai.yaml for UI metadata and invocation policy
RulesVia AGENTS.md content
MemorySession transcripts in ~/.codex/history.jsonl. Resume subcommand. TUI: /m_update, /m_drop. Initial plumbing in v0.97.0 (Feb 2026).
Hooks“Notify” system — external programs on lifecycle events (e.g., agent-turn-complete). Simpler than Claude’s 12 events.
SecurityDual-layer: OS-level sandbox (what’s possible) + approval policy (when to ask). Modes: suggest, auto-edit, full-auto.
Skill discovery6 levels: CWD .agents/skills/ → parent → repo root → $HOME/.agents/skills//etc/codex/skills/ → built-in
Invocation/skills menu, $skill-name inline mention, implicit matching
Context loadingMetadata (name, description, file path) at start; full SKILL.md only when activated
TelemetryOpenTelemetry full lifecycle tracking with event metadata

agents/openai.yaml adds Codex-specific configuration:

interface:
  display_name: "User-facing name"
  icon_small: "./assets/logo.svg"
  brand_color: "#3B82F6"
policy:
  allow_implicit_invocation: false   # Disable auto-matching
dependencies:
  tools:
    - type: "mcp"
      value: "toolName"
      url: "https://example.com"

Antigravity

Vendor: Google | Type: IDE agent | SKILL.md: Standard

AspectDetails
Instruction fileGEMINI.md (shared with Gemini CLI)
SkillsSKILL.md (standard). Skills directory-based with scripts/, references/, assets/
RulesVia GEMINI.md content
Memory.gemini/antigravity/brain/ directory for knowledge base
AgentsAgent Manager dispatches up to 5 agents simultaneously. Multi-model: Gemini 3 Pro, Claude Sonnet 4.5, GPT-OSS.
MCPMCP Hub with 1,500+ pre-configured servers. UI-driven setup.
Skill discoverySkills directory; semantic matching against descriptions
InvocationImplicit via semantic engine matching prompts to skill descriptions
Context loadingProgressive disclosure — registers name + description at start, hydrates full instructions on match
Context window1M tokens (Gemini 3 Pro backend)

Key pattern: Antigravity emphasizes narrow, precise descriptions with explicit “do not use” clauses to reduce false activation.


Cursor

Vendor: Anysphere | Type: IDE | SKILL.md: Adopted via agentskills.io (2026)

AspectDetails
Instruction fileNone (rules serve this purpose)
SkillsAdopted SKILL.md standard (2026)
Rules.cursor/rules/*.mdc — Markdown Component files with frontmatter
Memory.cursor/rules/learned-memories.mdc for project-specific knowledge. Prompt-level persistence.
Agents.cursor/agents/ — up to 8 parallel subagents via Git worktree isolation
NotepadsPersistent context notes referenced with @notepad-name, survive across sessions (beta)
PluginsMarketplace with 10,000+ tools (Agnxi.com). Packages skills, agents, MCP, hooks, rules.
MCP.cursor/mcp.json — separate from other settings
Rule formatYAML frontmatter: description, globs (file patterns), alwaysApply (boolean)
Limits6000 chars per rule file; 12000 chars combined
Legacy.cursorrules single file (deprecated, still supported)

.mdc rule example:

---
description: Python API conventions
globs: ["*.py", "src/**/*.py"]
alwaysApply: false
---
Use type hints on all function signatures...

Evolution: .cursorrules (2023) → .cursor/ folder with index.mdc (2024) → Multi-file .cursor/rules/*.mdc (2025) → Context-aware rules with MCP integration (2026)


Windsurf

Vendor: Codeium | Type: IDE | SKILL.md: Not documented

AspectDetails
Instruction fileglobal_rules.md (global), .windsurf/rules/ (workspace)
SkillsNo native SKILL.md support documented
Rules.windsurf/rules/ directory with 4 activation modes
MemoryCascade Memories: dual system (auto-generated + user-created). Auto-memories don’t consume credits. Storage: ~/.codeium/windsurf/memories/
HooksCascade Hooks: shell commands at workflow lifecycle points. JSON stdin context. Enterprise distribution via cloud dashboard + MDM deployment (Feb 2026).
Legacy.windsurfrules.windsurf/rules/rules.md
Limits6000 chars per rule; 12000 chars combined (global + workspace)

Activation modes:

ModeBehavior
Always OnApplied to every interaction
ManualActivated via @-mention
Model DecisionAI decides based on natural language description
AutoApplied based on file context

OpenCode

Vendor: SST (open source) | Type: CLI agent | SKILL.md: Via standard

AspectDetails
Instruction fileAGENTS.md (project), ~/.config/opencode/AGENTS.md (global)
SkillsSKILL.md via Agent Skills standard
RulesVia AGENTS.md
Legacy compatReads CLAUDE.md as fallback (disable via OPENCODE_DISABLE_CLAUDE_CODE=1)
External refsopencode.json instructions array supports globs: ["docs/*.md", "packages/*/AGENTS.md"]
Custom commandsMarkdown files in designated directories; filename becomes command ID

OpenClaw

Vendor: Open source | Type: Autonomous agent | SKILL.md: Supported

AspectDetails
Instruction fileAGENTS.md (primary instructions)
SkillsSKILL.md (compatible with Claude Code / Cursor conventions)
IdentitySOUL.md (personality, values, behavior), IDENTITY.md (presentation)
User contextUSER.md (info about the user)
ToolsTOOLS.md (capability declarations)
MemoryMEMORY.md + memory/YYYY-MM-DD.md dated files
LifecycleHEARTBEAT.md (periodic task checklist), BOOTSTRAP.md (startup)
Config formatJSON5 (openclaw.json) allowing comments

OpenClaw has the richest file taxonomy — 8 separate config files loaded at session start into the system prompt. This gives fine-grained control but means more files for tome to discover and potentially sync.


Nanobot (HKUDS)

Vendor: HKUDS (open source) | Type: Lightweight CLI agent | SKILL.md: Via OpenClaw compat

AspectDetails
Instruction fileAGENTS.md
IdentitySOUL.md, USER.md, TOOLS.md, IDENTITY.md
LifecycleHEARTBEAT.md (checked every 30 min)
Memorymemory/MEMORY.md + memory/HISTORY.md
Size~4000 lines of core code (99% smaller than OpenClaw)

Nanobot is essentially an OpenClaw-compatible agent in a fraction of the code. Same file conventions, same workspace structure.


PicoClaw

Vendor: Open source | Type: Ultra-lightweight agent | SKILL.md: Not documented

AspectDetails
RuntimeSingle Go binary, <10MB RAM, runs on $10 RISC-V hardware
Interfacepicoclaw agent -m "prompt" one-shot or interactive mode
ConfigMinimal — focuses on LLM backend configuration
MCPSupported for tool integration

PicoClaw prioritizes extreme minimalism. For tome, it would likely be an MCP target rather than a skill directory target.


Gemini CLI

Vendor: Google | Type: CLI agent | SKILL.md: Standard

AspectDetails
Instruction fileGEMINI.md (plain markdown, no frontmatter required)
DiscoveryGlobal (~/.gemini/GEMINI.md) → project root → parent dirs up to .git → subdirectories
Imports@file.md syntax for referencing external content
IgnoreRespects .gitignore and .geminiignore
Custom namingsettings.jsoncontext.fileName allows alternative file names
Memory/memory show, /memory refresh, /memory add commands

Extensions (2026): gemini-extension.json packaging prompts, MCP servers, and commands. Extension settings allow user-prompted configuration on install with env var mapping.

Shell execution: !{command} syntax for shell injection with auto-escaping of {{args}}. Argument substitution via {{args}} (not $ARGUMENTS).

Gemini CLI is the simplest format for instructions — plain markdown files concatenated into context. No frontmatter, no structured fields. The @file.md import syntax and !{command} execution are unique.

Skills (2026): Gemini CLI now supports SKILL.md directory scanning. Personal skills at ~/.gemini/skills/ and the portable ~/.agents/skills/ path. For tome, Gemini CLI is a Symlink target via ~/.gemini/skills/.


VS Code Copilot

Vendor: GitHub (Microsoft) | Type: IDE agent | SKILL.md: Full standard (since Jan 2026)

AspectDetails
Instruction file.github/copilot-instructions.md (project), %HOME%/copilot-instructions.md (personal)
SkillsSKILL.md (full standard since v1.108+). Primary location: .github/skills/, also reads .claude/skills/. Personal: ~/.copilot/skills/.
Rules.github/instructions/*.instructions.md — YAML frontmatter with description (1-500 chars) and applyTo (glob). excludeAgent for targeting.
MemoryCopilot Memories (early access, Pro/Pro+ only). Repository-level, auto-deleted after 28 days unless renewed. Includes citations to code locations.
Agents.github/agents/ with tools, prompts, MCP. Two built-in types: coding-agent, code-review.
ExtensionsTwo flavors: Skillsets (lightweight: tools + prompts) and Full agents (autonomous: multi-step, GitHub App). Built via Copilot API.
Chat participants@workspace, @terminal, custom participants via VS Code Extension API.
Invocation/init generates instruction file. Skills matched semantically.
Context loadingConditional: glob match on applyTo, semantic match on descriptions.

Copilot Workspace (GitHub Next): Running coding agents via GitHub Actions — agent workflows as CI/CD.


Amp

Vendor: Sourcegraph | Type: CLI/IDE agent | SKILL.md: Standard (migrating to)

AspectDetails
Instruction fileUnknown
SkillsSKILL.md standard. Replacing deprecated custom commands and toolboxes (Jan 2026).
Hooks"amp.hooks" settings with events like "tool:post-execute"
Migration.agents/commands/*.md.agents/skills/*/SKILL.md
Key featureOn-demand skill loading — zero tokens until needed (vs toolbox overhead)

Amp is a useful case study — their migration from custom commands to Agent Skills demonstrates the industry consolidation trend.


Goose

Vendor: Block (Square) | Type: Autonomous agent | SKILL.md: Standard

AspectDetails
SkillsSKILL.md standard. Scans 6 directories: ~/.config/goose/skills/, ~/.config/agents/skills/, ~/.claude/skills/, and project-level equivalents.
ExtensionsSix types: Stdio (MCP via pipes), HTTP (MCP via SSE), Builtin (Rust).
MCPCore mechanism — 100+ servers in toolkit catalog. Auto-OAuth on HTTP 401.
ConfigTOML at ~/.config/goose/

Goose now supports SKILL.md directory scanning alongside its MCP-centric extension model. For tome, Goose is a Symlink target via ~/.config/goose/skills/.


Aider

Vendor: Open source | Type: CLI pair programmer | SKILL.md: Not documented

AspectDetails
Config.aider.conf.yml in home/repo root/current dir (loaded in order, last wins)
RulesVia config file content
Git--no-verify flag, commit behavior. Known issue: pre-commit hooks not respected.

Aider is minimal — no skills, no hooks, no agents. Pure configuration-driven.


3. Instruction Files (Rules)

Each tool reads project-level instructions from a differently-named markdown file. The content is plain markdown (no frontmatter) and is always loaded into context.

ToolFile NameGlobal LocationProject LocationDiscovery
Claude CodeCLAUDE.md~/.claude/CLAUDE.mdProject rootRoot → parent dirs → global
Codex CLIAGENTS.md~/.agents/AGENTS.mdProject rootRoot → parent dirs → ~/.agents//etc/codex/
VS Code Copilotcopilot-instructions.md%HOME%/copilot-instructions.md.github/copilot-instructions.mdWorkspace root only
AntigravityGEMINI.md~/.gemini/GEMINI.mdProject rootRoot → parent dirs → .git boundary
Gemini CLIGEMINI.md~/.gemini/GEMINI.mdProject rootRoot → parent dirs → subdirs
OpenCodeAGENTS.md~/.config/opencode/AGENTS.mdProject rootFalls back to CLAUDE.md
OpenClawAGENTS.mdWorkspace dir+ SOUL.md, IDENTITY.md, TOOLS.md, etc.
Cursor(rules only).cursor/rules/*.mdcGlob + activation mode
Windsurfglobal_rules.md~/.windsurf/.windsurf/rules/Activation mode per rule

What this means for tome

A “rule” that should apply everywhere needs to exist as up to 5 different files:

  • CLAUDE.md (Claude Code)
  • AGENTS.md (Codex, OpenCode, OpenClaw)
  • GEMINI.md (Antigravity, Gemini CLI)
  • .github/copilot-instructions.md (VS Code Copilot)
  • .cursor/rules/*.mdc or .windsurf/rules/*.md (IDE-specific)

Symlinks can unify the markdown-based ones, but Cursor/Windsurf require format transforms.

AGENTS.md as open standard

AGENTS.md has emerged as an open instruction-file standard under the Linux Foundation / Agentic AI Foundation. It is adopted by Codex, OpenCode, OpenClaw, and configurable in Gemini CLI — the instruction-file equivalent of the SKILL.md skills standard. The portable path ~/.agents/ (and its skills/ subdirectory) is scanned by Codex, Goose, Gemini CLI, OpenCode, Amp, and Cursor.


4. SKILL.md Standard (agentskills.io)

The Agent Skills format originated at Anthropic in late 2025 and was released as an open standard. It defines a portable way to package reusable instructions, scripts, and resources for AI coding agents. As of February 2026, 27+ tools have adopted it.

Who supports it

ToolSKILL.md SupportExtensions Beyond Standard
Claude CodeFull + extensionsdisable-model-invocation, context: fork, agent, hooks, !command, $ARGUMENTS
Codex CLIStandard + openai.yamlagents/openai.yaml (UI metadata, invocation policy, tool deps)
VS Code CopilotFull (since Jan 2026)excludeAgent field for targeting coding-agent vs code-review
AntigravityStandardSemantic matching emphasis
CursorAdopted (2026)
OpenCodeStandard
OpenClawCompatible
Gemini CLIStandardScans ~/.gemini/skills/ and ~/.agents/skills/
GooseStandardScans 6 dirs including ~/.config/goose/skills/, ~/.config/agents/skills/, ~/.claude/skills/
AmpStandard (migrating to)Scans ~/.config/amp/skills/, ~/.config/agents/skills/, .claude/skills/
WindsurfNot documentedUses native rules format

Invocation methods

ToolInvocationImplicitExplicitExtra Config
Claude CodeSlash + ToolYes (description matching)/name, Skill(skill: "name")Plugins, hooks, agents
Codex CLIMenu + MentionYes (unless disabled)/skills, $nameagents/openai.yaml
VS Code CopilotSemanticYes (description matching)Via chat.github/agents/, Extensions
AntigravitySemanticYes (semantic matching)Not documentedAgent Manager
CursorUnknownUnknownUnknownNotepads, plugins
WindsurfCascade hooks
OpenCodeUnknownUnknownUnknownopencode.json

Required frontmatter

---
name: my-skill          # 1-64 chars, lowercase + hyphens, must match directory name
description: |          # 1-1024 chars. When to use this skill.
  What it does and when the agent should activate it.
---

Optional frontmatter

license: Apache-2.0
metadata:
  author: example-org
  version: "1.0"
compatibility: Requires poppler-utils
allowed-tools: Read Grep Bash(git:*)

Skill directory structure

skill-name/
├── SKILL.md           # Required — instructions + frontmatter
├── scripts/           # Optional — executable code
├── references/        # Optional — detailed docs loaded on demand
└── assets/            # Optional — templates, data files

Discovery paths

ToolPersonal SkillsProject Skills
Claude Code~/.claude/skills/.claude/skills/ + nested subdirs
Codex CLI$HOME/.agents/skills/.agents/skills/ → parent → repo root
VS Code Copilot~/.copilot/skills/.github/skills/ + .claude/skills/
Antigravity~/.gemini/antigravity/skills/.gemini/skills/, .agents/skills/
Gemini CLI~/.gemini/skills/~/.agents/skills/
Goose~/.config/goose/skills/~/.config/agents/skills/, ~/.claude/skills/
Amp~/.config/amp/skills/~/.config/agents/skills/, .claude/skills/
OpenCode~/.config/opencode/skills/~/.claude/skills/, ~/.agents/skills/

5. Parsing Differences (The Details That Break Things)

5.1 YAML Frontmatter

Delimiters: All tools require --- (three hyphens) at start and end.

Unknown fields: Silently ignored by all tools following the Agent Skills standard. Claude Code has occasionally thrown errors on unexpected keys in non-standard positions.

Case sensitivity: All field names are case-sensitive and must be lowercase. SKILL.md filename must be exact uppercase — skill.md won’t be discovered.

Multiline descriptions — MAJOR GOTCHA:

Claude Code’s YAML parser breaks on Prettier-formatted multiline descriptions:

# BROKEN — Claude Code can't parse this (Prettier-wrapped):
---
description: This is a long description that Prettier
  wraps across multiple lines without a block scalar
---

# WORKS — single line:
---
description: This is a long description kept on one line  # prettier-ignore
---

# WORKS — explicit block scalar:
---
description: |
  This is a long description using
  a YAML block scalar indicator.
---

Other tools (Codex, Cursor, Windsurf, Gemini CLI) handle standard YAML multiline correctly.

Workaround: Keep descriptions single-line, or use # prettier-ignore comment, or disable Prettier’s proseWrap for SKILL.md files.

5.2 Markdown Body

Code blocks: All tools prefer triple backticks with language tags. Indented code blocks (4 spaces) work but lack syntax highlighting.

XML tags: Claude is fine-tuned to recognize XML tags (<example>, <system-reminder>, etc.) as structural elements. No other tool does this. Skills that rely on XML structure for Claude won’t parse the same way in Codex/Copilot.

Shell execution in content:

SyntaxToolBehavior
`!command`Claude CodeExecutes bash, injects output into skill content
!{command}Gemini CLIExecutes shell with auto-escaping of {{args}}
All othersNo shell execution in skill content

Argument substitution:

SyntaxTool
$ARGUMENTS, $0, $1Claude Code
{{args}}Gemini CLI
All others (no substitution)

Claude Code parser bug: ! inside inline code backticks can be misinterpreted as a bash command. Avoid ! in code spans within SKILL.md files meant for Claude Code.

5.3 Rules Formats (Cursor & Windsurf)

Cursor .mdc files:

---
description: Python API conventions
globs: ["*.py", "src/**/*.py"]
alwaysApply: false
---
Use type hints on all function signatures...

Four activation modes:

ModeFrontmatterWhen Applied
AlwaysalwaysApply: trueEvery interaction
Auto-Attachglobs definedWhen active file matches glob
Agent Requesteddescription onlyAI decides based on description
ManualNoneMust be @-mentioned

Windsurf rules:

Four activation modes: Manual (@-mention), Model Decision (AI decides), Glob (file pattern), Always On.

Limits: Both Cursor and Windsurf enforce 6,000 chars per rule file, 12,000 chars combined. Content beyond the limit is silently truncated.

5.4 VS Code Copilot .instructions.md

---
description: 'Purpose of these instructions (1-500 chars)'
applyTo: '**/*.py'
---
Natural language instructions here...
  • description and applyTo are the main frontmatter fields
  • excludeAgent: "code-review" or "coding-agent" for agent targeting
  • No char limit documented, but recommended to stay under 2 pages
  • Glob patterns in applyTo use standard glob syntax

5.5 File Encoding

UTF-8: All tools assume UTF-8. Claude Code has documented bugs with UTF-8 corruption (especially CJK characters via MultiEdit tool). Non-UTF-8 files (Windows-1252) get silently corrupted.

BOM: Use UTF-8 without BOM. VS Code can handle BOM but other tools may not.

Line endings: Use LF (\n). Cursor has a known bug converting CRLF → LF. Enforce via .gitattributes.

Skill names: Must be [a-z0-9-]+ (lowercase alphanumeric + hyphens). No emoji, no unicode, no uppercase.


6. Memory & Persistence

Memory is the least portable layer — every tool has its own mechanism with zero interoperability.

Claude Code

AspectDetails
Location~/.claude/projects/<project-hash>/memory/
Auto-loadedFirst 200 lines of MEMORY.md injected into system prompt every session
Topic filesAdditional *.md files in memory dir, loaded on demand
Who writesBoth the agent (auto-memory) and user (manual edits)
Opt-inSet CLAUDE_CODE_DISABLE_AUTO_MEMORY=0 to enable

Codex CLI

AspectDetails
Location~/.codex/history.jsonl
MechanismSession transcripts, resume subcommand
TUI commands/m_update, /m_drop for memory management
StatusInitial memory plumbing in v0.97.0 (Feb 2026) — still evolving

Cursor

AspectDetails
Location.cursor/rules/learned-memories.mdc
MechanismRules-based — .mdc files with YAML frontmatter
PersistencePrompt-level, not true cross-session memory
Known issueDifferent LLMs interpret .mdc rules inconsistently

Windsurf

AspectDetails
Location~/.codeium/windsurf/memories/
MechanismDual: auto-generated (Cascade AI) + user-created
Auto-memoriesDon’t consume credits — major advantage
RetrievalCascade AI decides what’s relevant per conversation

Antigravity

AspectDetails
Location.gemini/antigravity/brain/
MechanismKnowledge base directory, multi-agent sharing
Context window1M tokens (Gemini 3 Pro backend)

VS Code Copilot

AspectDetails
FeatureCopilot Memories (early access, Pro/Pro+ only)
ScopeRepository-level (not user-specific)
LifecycleAuto-deleted after 28 days unless renewed by use
CitationsEach memory references specific code locations

OpenClaw / Nanobot

AspectDetails
Locationmemory/MEMORY.md + memory/YYYY-MM-DD.md dated files
HEARTBEAT.mdPeriodic task checklist (safe to run every 30 min)
DesignPrevents cross-agent collection clobbering with dated files

Summary Table

ToolStorageAuto-LoadWho WritesCross-Session
Claude Codememory/MEMORY.mdFirst 200 linesAgent + userYes
Codex CLIhistory.jsonlN/ASystemPartial (resume)
Cursor.mdc rulesBy activation modeUserPrompt-level
WindsurfCascade MemoriesAI-selectedAI + userYes
Antigravitybrain/ directoryKnowledge baseUnknownUnknown
VS Code CopilotCopilot MemoriesAI-selectedAIYes (28-day expiry)
OpenClawmemory/*.mdOptionalAgent + userYes

7. Context Loading Strategies

How each tool manages token budget:

ToolStrategySession Start CostOn-DemandToken Budget
Claude CodeProgressive disclosure~100 tokens/skill (name + description only)Full SKILL.md on invocation2% of window for skill metadata
Codex CLIProgressive disclosure~50-100 tokens/skill metadataFull SKILL.md on activationUnknown
VS Code CopilotConditional loadingMatching instructions onlySkills on semantic matchUnknown
AntigravityRegister → hydrateName + descriptionFull SKILL.md on semantic match~100 tokens/skill
CursorGlob-based loadingAll matching rules fully loaded12k chars total
WindsurfMode-based loading“Always On” rules fully loadedManual/Model rules on trigger12k chars total
Gemini CLIAll-at-onceAll GEMINI.md files concatenatedVariable
OpenClawAll-at-onceAll 8 config files loadedSkills on demandVariable

The key difference: SKILL.md-based tools use progressive disclosure (metadata → body → resources), while rules-based tools (Cursor, Windsurf) front-load everything up to their char limits.

Progressive Disclosure Flow

Skills use a three-tier loading strategy to minimize context window consumption:

TierWhat loadsWhenToken cost
Metadataname + description from frontmatterSession start, for all skills~100 tokens per skill
InstructionsFull SKILL.md bodyWhen skill is activated<5000 tokens recommended
ResourcesFiles in scripts/, references/, assets/Only when referenced during executionVariable
graph LR
    subgraph "Session Start (~100 tokens/skill)"
        A["Scan skill directories"]
        B["Read frontmatter only<br/>(name + description)"]
    end

    subgraph "Task Matching"
        C{"User prompt matches<br/>skill description?"}
        D["Skill stays dormant"]
    end

    subgraph "Activation (<5000 tokens)"
        E["Load full SKILL.md body"]
        F["Execute instructions"]
    end

    subgraph "On Demand (variable)"
        G["Load references/<br/>scripts/ assets/"]
    end

    A --> B --> C
    C -- No --> D
    C -- Yes --> E --> F
    F -. "if needed" .-> G

8. MCP vs Skills: Efficiency

The Token Problem

MCP servers add tool definitions to context at session start. Each tool includes its name, description, parameter schema, and usage hints — all in natural language so the model understands when and how to use them.

MetricMCP ServersSkills
Session start overhead~55,000 tokens (typical 2-3 servers)~100 tokens per skill (metadata only)
Per-tool costFull schema always in contextFull instructions only when activated
ScalingDegrades at 2-3 servers (accuracy drops)Hundreds of skills with minimal overhead
Cost multiplierBaseline~3x cheaper for equivalent functionality

When to Use Which

Use CaseBest ApproachWhy
External API callsMCPSkills can’t make HTTP requests; MCP servers can
Database queriesMCPRequires authenticated connections
Coding conventionsSkillsPure instructions, no external calls needed
Deployment workflowsSkillsStep-by-step instructions + shell scripts
File format knowledgeSkillsReference material loaded on demand
Real-time dataMCPNeeds live connections

They’re complementary, not competitive. MCP provides capabilities (tools the agent can call). Skills provide knowledge (instructions the agent follows). A skill can even declare MCP tool dependencies in its frontmatter.

Progressive Disclosure is the Key

The fundamental difference: skills let agents load context incrementally based on what’s actually needed, while MCP front-loads everything. For a library of 50 skills, progressive disclosure means ~5000 tokens at session start vs. potentially hundreds of thousands for equivalent MCP tool definitions.


9. Hooks & Lifecycle Events

Hooks let tools run shell commands or logic at specific points in the agent lifecycle. This is the fastest-growing area of divergence.

Claude Code — 12 Events, 3 Hook Types

Location: ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json

Events:

EventWhenCommon Use
SessionStartSession beginsLoad environment, log start
PreToolUseBefore any tool executesValidate, block dangerous commands
PostToolUseAfter tool completesLint, format, log
PostToolUseFailureTool execution failsError reporting
PermissionRequestPermission dialog appearsAuto-approve/deny patterns
UserPromptSubmitUser submits promptPre-process, inject context
NotificationAgent needs attentionDesktop notifications
StopAgent finishes respondingSummarize, commit
SubagentStartSubagent spawnsTrack agent tree
SubagentStopSubagent finishesCollect results
PreCompactBefore context compactionSave state
SessionEndSession endsCleanup, log

Hook types:

TypeMechanismUse Case
commandRun shell command, receive JSON on stdinLinting, formatting, git hooks
promptSend prompt to Claude for evaluationPolicy enforcement, content review
agentSpawn subagent with tools (Read, Grep, Glob)Complex validation, code analysis

Matchers target specific tools:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash(npm run *)",
      "type": "command",
      "command": "echo 'npm command detected'"
    }]
  }
}

Patterns support: exact ("Bash"), specifier ("Bash(npm run lint)"), wildcards ("Bash(curl *)"), regex-like ("mcp__.*__write.*").

Exit codes: 0 = success, 2 = block action (stderr becomes error message).

Codex CLI — Notify Hooks

Location: Configuration or CLI flags

Codex has a simpler hook system called “notify”:

  • Executes external programs on lifecycle events (e.g., agent-turn-complete)
  • Less granular than Claude’s 12 events
  • Focused on notification rather than validation/blocking

Windsurf — Cascade Hooks

Location: Settings/configuration

  • Shell commands at workflow lifecycle points
  • Receives JSON context via stdin (similar to Claude)
  • Supports user prompt events and policy violation blocking
  • Enterprise distribution via cloud dashboard + MDM deployment (Feb 2026)

Amp — Tool Execution Hooks

Location: Settings ("amp.hooks")

{
  "amp.hooks": {
    "tool:post-execute": "npm run lint"
  }
}
  • Event format: "tool:post-execute"
  • Simpler than Claude’s matcher system

Other Tools

ToolHooks?Notes
VS Code CopilotNoExtensions serve a similar role
CursorNoPlugin system handles automation
AntigravityNoAgent Manager handles orchestration
Gemini CLINoExtension settings provide some lifecycle config
OpenClawNoHEARTBEAT.md is the closest analogue (periodic, not event-driven)
AiderPartialGit pre-commit hooks, but not agent lifecycle hooks
GooseNoExtension system handles tool integration

Cross-Tool Hook Comparison

FeatureClaude CodeCodex CLIWindsurfAmp
Events12Few (notify)Severaltool:post-execute
Can block actionsYes (exit 2)NoYes (policy)No
JSON stdinYesUnknownYesUnknown
Tool matchersWildcards + regexNoUnknownNo
Hook typescommand, prompt, agentcommandcommandcommand
ScopeGlobal + project + localUnknownGlobal + enterpriseSettings

10. Agents & Subagents

Agents are isolated execution contexts with their own tools, model, and permissions. This is distinct from skills (which provide instructions within the main context).

Claude Code — Most Mature Agent System

Location: ~/.claude/agents/*.md (global), .claude/agents/*.md (project)

Format: YAML frontmatter + markdown body

---
name: code-reviewer
description: Reviews code for quality and best practices
tools: [Read, Glob, Grep]
model: sonnet
maxTurns: 10
---
You are a code reviewer focused on...

Frontmatter fields (11+):

FieldPurpose
nameAgent identifier (required)
descriptionWhat the agent does (required)
toolsAllowlist of tools
disallowedToolsDenylist of tools
modelsonnet, opus, haiku, or inherit
permissionModePermission behavior for this agent
mcpServersMCP servers to enable
hooksHooks active only while agent runs
maxTurnsMaximum conversation turns
skillsSkills available to the agent
memoryMemory scope: user, project, or local

Key difference from skills: Agents run in isolated context with their own model and tool restrictions. Skills inject instructions into the current context.

VS Code Copilot — Custom Agents

Location: .github/agents/

GitHub Copilot supports custom agents with:

  • Custom tools and prompts
  • MCP server access
  • Agent targeting via excludeAgent field in instructions
  • Two built-in agent types: coding-agent and code-review

Cursor — Parallel Subagents

Location: .cursor/agents/

  • Up to 8 parallel agents via Git worktree isolation
  • Agents get their own worktree branch for conflict-free parallel work
  • Configured similarly to Claude agents

Antigravity — Agent Manager

  • Dispatches up to 5 agents simultaneously on separate features
  • Multi-model support (Gemini 3 Pro, Claude Sonnet 4.5, GPT-OSS)
  • Agent Manager UI for orchestration (not file-based configuration)
  • Claims 5-10x productivity from parallel agent execution

Codex CLI — Sandbox Agents

Codex takes a security-first approach:

  • Dual-layer security: OS-level sandbox (what’s possible) + approval policy (when to ask)
  • Three approval modes: suggest (read-only), auto-edit (edits approved, commands need approval), full-auto
  • Agents run within sandbox constraints

OpenClaw/Nanobot — Workspace Files as “Agent Personality”

Rather than defining subagents, OpenClaw defines the main agent’s personality via 8 workspace files:

FilePurpose
AGENTS.mdPrimary instructions (equivalent to CLAUDE.md)
SOUL.mdBehavioral core — personality, ethics, communication style
IDENTITY.mdStructured profile — name, role, goals
TOOLS.mdEnvironment quirks, path conventions, risky commands
USER.mdInfo about the human user
MEMORY.mdPersistent learned context
HEARTBEAT.mdPeriodic maintenance rituals (configurable cadence, e.g., every 30 min)
BOOTSTRAP.mdFirst-run interview script

This is philosophically different — OpenClaw treats the AI as an entity with personality and rituals, rather than a tool with configurations.

Cross-Tool Agent Comparison

FeatureClaude CodeCopilotCursorAntigravityCodex
Agent config formatYAML + MDYAML + MDYAML + MDUI-basedSandbox policy
Parallel executionYes (Task tool)Yes8 agents (worktrees)5 agentsUnknown
Model selectionPer-agentNoUnknownMulti-modelNo (GPT only)
Tool restrictionsPer-agentPer-agentUnknownUnknownSandbox-level
Isolated contextYesYesYes (worktree)YesYes (sandbox)
File location.claude/agents/.github/agents/.cursor/agents/UIN/A

11. Plugins & Extensions

Plugins bundle multiple configuration objects (skills, agents, hooks, MCP servers) into a single installable package.

Claude Code — Plugin Ecosystem

Location: .claude-plugin/plugin.json at plugin root

Manifest format:

{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "Plugin description",
  "author": { "name": "Author", "email": "dev@example.com" },
  "commands": "./commands/",
  "agents": "./agents/",
  "skills": ["skill1", "skill2"],
  "hooks": "./hooks.json",
  "mcpServers": "./.mcp.json",
  "lspServers": "./.lsp.json",
  "outputStyles": "./styles/"
}

What plugins can contain: Skills, agents, hooks, commands (legacy), MCP servers, LSP servers, output styles.

Discovery: /plugin command → Discover tab. Official + community marketplaces. Three installation scopes: user (default), project, local.

Note: Manifest is optional — Claude Code auto-discovers components if absent.

Cursor — Plugin Marketplace

  • Packages skills, subagents, MCP servers, hooks, rules into single install
  • 10,000+ tools on Agnxi.com marketplace
  • 1,800+ MCP servers available

VS Code Copilot — Extensions

Two flavors:

  1. Skillsets — lightweight: tools, prompts, knowledge
  2. Full agents — autonomous: multi-step workflows, GitHub App integration

Built as GitHub Apps using the Copilot API. Chat participants (@workspace, @terminal) are built-in extensions.

Gemini CLI — Extensions

Location: Extension directory with gemini-extension.json

{
  "name": "my-extension",
  "prompts": ["./prompts/"],
  "mcpServers": { ... },
  "commands": ["./commands/"]
}

Extension settings (2026): User-prompted configuration on install with environment variable mapping.

Goose — Six Extension Types

TypeMechanismLifecycle
StdioMCP via stdio pipesGoose manages
HTTPMCP via SSEExternal process
BuiltinRust compiled into binaryAlways available

100+ MCP servers in toolkit catalog. Auto-OAuth on 401 for HTTP extensions.

Amp — Skills Migration

Amp (Sourcegraph) is actively consolidating:

  • Deprecated (Jan 2026): Custom commands, toolboxes
  • Replacing with: Agent Skills (SKILL.md standard)
  • Migration path: .agents/commands/*.md.agents/skills/*/SKILL.md
  • Key motivation: on-demand loading (zero tokens until needed) vs toolbox overhead

Cross-Tool Plugin Comparison

FeatureClaude CodeCursorCopilotGemini CLIGoose
Formatplugin.jsonUnknownGitHub Appgemini-extension.jsonTOML config
MarketplaceYesYes (10k+)GitHub MarketplaceNoToolkit catalog
Contains skillsYesYesYes (skillsets)Yes (prompts)No
Contains agentsYesYesYesNoNo
Contains hooksYesUnknownNoNoNo
Contains MCPYesYesNo (separate)YesCore mechanism
Install scopesuser/project/localUnknownOrg/repoUserUser

12. MCP Server Configuration

MCP (Model Context Protocol) is the most universally supported integration mechanism — every major tool supports it.

Configuration Formats

ToolConfig FileLocationTransport
Claude Code.mcp.jsonProject root or ~/.claude/ settingsstdio, http, sse
Codex CLIConfig / CLI flagsUnknownstdio, http
VS Code CopilotSettings.vscode/settings.json or mcp.jsonstdio, http
Cursor.cursor/mcp.jsonProject .cursor/ dirstdio, sse
WindsurfSettingsWindsurf settingsstdio
AntigravityMCP HubSettings panelstdio, http
Gemini CLIsettings.json~/.gemini/settings.jsonstdio, http
OpenClawopenclaw.jsonJSON5 configstdio
GooseConfig~/.config/goose/stdio, http (SSE)

Claude Code MCP Config

{
  "mcpServers": {
    "server-name": {
      "command": "node",
      "args": ["path/to/server.js"],
      "env": {
        "API_KEY": "${API_KEY}"
      }
    }
  }
}

Environment variable expansion: ${VAR} (required), ${VAR:-default} (optional), ${CLAUDE_PLUGIN_ROOT} (plugin-relative).

Key Differences

  • Antigravity: MCP Hub with 1,500+ pre-configured servers — UI-driven setup
  • Goose: MCP is the primary extension mechanism (not just an add-on)
  • Claude Code: Most flexible — supports project, global, and plugin-scoped MCP configs
  • Cursor: Stored in .cursor/mcp.json separate from other settings

13. Settings & Permission Systems

Claude Code — Three-Level Settings

Hierarchy (last wins): ~/.claude/settings.json.claude/settings.json.claude/settings.local.json

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": ["Bash(npm run lint)", "Read(~/.zshrc)"],
    "ask": ["Bash(curl *)"],
    "deny": ["Read(./.env)", "Edit(.env*)"]
  },
  "hooks": { ... },
  "mcpServers": { ... },
  "model": "sonnet",
  "maxTurns": 25
}

Permission modes: ask (default), acceptEdits, plan, bypassPermissions (“YOLO”).

Automatic timestamped backups (5 most recent retained).

Codex CLI — Dual-Layer Security

LayerControlsOptions
SandboxWhat’s possibleOS-level isolation, network restrictions
ApprovalWhen to asksuggest (read-only), auto-edit, full-auto

Cursor — Settings in .cursor/

  • .cursor/rules/*.mdc for rules (covered in section 5.3)
  • .cursor/mcp.json for MCP servers
  • .cursor/agents/ for agent definitions
  • Notepads: persistent context notes referenced with @, survive across sessions (beta)

Windsurf — Enterprise Distribution

  • Cloud dashboard for enterprise hook/rule distribution
  • MDM deployment support (Feb 2026)
  • Policy enforcement via Cascade hooks

Gemini CLI — Extension Settings

~/.gemini/settings.json:

{
  "context": { "fileName": "GEMINI.md" },
  "mcpServers": { ... }
}

Supports custom naming for context files. Extension settings (2026) allow user-prompted configuration on install.


14. Unique Structures by Tool

Some tools have first-class configuration objects that exist nowhere else.

OpenClaw’s Behavioral Workspace

OpenClaw’s 8-file system is philosophically unique — it treats the AI agent as having personality and rituals:

FileAnalogue in Other ToolsWhat Makes It Unique
SOUL.mdNonePersonality, ethics, communication style
IDENTITY.mdNoneName, role, goals as structured profile
HEARTBEAT.mdCron job / hookPeriodic maintenance rituals with configurable cadence
BOOTSTRAP.mdNoneFirst-run interview script
TOOLS.mdSettings permissionsEnvironment quirks, risky command warnings
USER.mdMemoryStructured info about the human user

Cursor Notepads (Beta)

Persistent context notes that:

  • Are referenced with @notepad-name in chat
  • Survive across sessions
  • Act like pinned context without consuming always-on token budget
  • Distinct from rules (which activate automatically) and skills (which activate on match)

Copilot Workspace (GitHub Next)

Running coding agents via GitHub Actions — agent workflows as CI/CD:

  • Agent gets a PR, makes changes, runs tests
  • Operates in GitHub’s infrastructure, not local machine
  • Distinct from Copilot Chat / VS Code integration

Goose Auto-OAuth

HTTP extensions automatically handle OAuth flows on 401 responses — no manual token management.

Antigravity Multi-Model

Agent Manager supports dispatching agents on different LLM backends simultaneously:

  • Gemini 3 Pro, Claude Sonnet 4.5, GPT-OSS
  • Choose model per agent based on task characteristics

15. Full Cross-Tool Comparison Matrix

StructureClaude CodeCodex CLICopilotCursorWindsurfAntigravityGemini CLIOpenClawAmpGooseAider
Skills (SKILL.md)Full+Std+FullAdoptedStdCompatStd
Instruction fileCLAUDE.mdAGENTS.mdcopilot-instructions.mdglobal_rules.mdGEMINI.mdGEMINI.mdAGENTS.md
Rules dir.claude/rules/.github/instructions/.cursor/rules/.windsurf/rules/
MemoryMEMORY.mdhistory.jsonlCopilot Memorieslearned-memories.mdcCascadebrain//memoryMEMORY.md + dated
Hooks12 eventsnotifyCascade hooksHEARTBEAT.mdtool:post-execute
Agents.claude/agents/sandbox.github/agents/.cursor/agents/Agent ManagerSOUL.md etc.
Pluginsplugin.jsonExtensionsMarketplacegemini-extension.jsonExtensions
MCP.mcp.jsonSupportedSupported.cursor/mcp.jsonSupportedMCP Hubsettings.jsonopenclaw.jsonSupportedCore
Settingssettings.jsonCLI flagsVS Code settings.cursor/SettingsSettingssettings.jsonJSON5 configSettingsTOML.aider.conf.yml
UniqueOutput styles, LSPSandbox policy, OTelChat participants, WorkspaceNotepadsEnterprise MDMMulti-model@file importsSOUL/HEARTBEAT/BOOTSTRAPAuto-OAuthGit integration

Legend: Full+ = standard with extensions, Std+ = standard with extra config, Std = standard, Compat = compatible, Adopted = recently added, — = not supported


16. Diagrams

Format Family Tree

graph TD
    Standard["Agent Skills Standard<br/>(agentskills.io)"]

    subgraph "SKILL.md Family"
        CC["Claude Code<br/>(standard + extensions)"]
        Codex["Codex CLI<br/>(standard + openai.yaml)"]
        AG["Antigravity<br/>(standard)"]
        OC_skill["OpenCode<br/>(standard)"]
        Cursor_skill["Cursor<br/>(adopted 2026)"]
        Copilot_skill["VS Code Copilot<br/>(full, Jan 2026)"]
        Amp_skill["Amp<br/>(migrating to)"]
    end

    subgraph "AGENTS.md Family"
        Codex_agents["Codex CLI"]
        OC_agents["OpenCode<br/>(CLAUDE.md fallback)"]
        OClaw["OpenClaw<br/>(+ SOUL.md, IDENTITY.md, ...)"]
        Nano["Nanobot<br/>(OpenClaw subset)"]
    end

    subgraph "Custom Rules"
        Cursor_rules["Cursor<br/>(.mdc with globs)"]
        Windsurf_rules["Windsurf<br/>(4 activation modes)"]
        Copilot_rules["VS Code Copilot<br/>(.instructions.md with applyTo)"]
    end

    subgraph "Plain Markdown"
        Claude_md["Claude Code<br/>(CLAUDE.md)"]
        Gemini_md["Gemini CLI<br/>(GEMINI.md + @imports)"]
        Copilot_md["VS Code Copilot<br/>(copilot-instructions.md)"]
    end

    subgraph "MCP-Only"
        Goose_mcp["Goose<br/>(6 extension types)"]
        Pico_mcp["PicoClaw"]
    end

    Standard --> CC
    Standard --> Codex
    Standard --> AG
    Standard --> OC_skill
    Standard --> Cursor_skill
    Standard --> Copilot_skill
    Standard --> Amp_skill
    OClaw --> Nano

tome Connector Mapping

graph LR
    subgraph "Source Connectors"
        S1["Claude Plugins<br/>(installed_plugins.json)"]
        S2["SKILL.md directories"]
        S3["Cursor .mdc rules"]
        S4["Windsurf rules"]
        S5["OpenClaw workspace"]
        S6["Copilot .instructions.md"]
    end

    subgraph "tome Library"
        L["Canonical format<br/>(SKILL.md standard)"]
    end

    subgraph "Target Connectors"
        T1["Symlink targets<br/>(Claude, Codex, Antigravity,<br/>OpenClaw, Copilot, OpenCode, Amp)"]
        T2["MCP targets<br/>(Goose, PicoClaw)"]
        T3["Format transform targets<br/>(Cursor .mdc, Windsurf rules,<br/>Copilot .instructions.md)"]
    end

    S1 --> L
    S2 --> L
    S3 -. "transform<br/>.mdc → SKILL.md" .-> L
    S4 -. "transform<br/>rules → SKILL.md" .-> L
    S5 --> L
    S6 -. "transform<br/>.instructions.md → SKILL.md" .-> L

    L --> T1
    L --> T2
    L -. "transform<br/>SKILL.md → native" .-> T3

17. Guidelines for Writing Portable Skills

Do

  • Keep descriptions single-line to avoid Claude Code’s YAML multiline parser bug
  • Stay under 5,000 tokens per SKILL.md body (recommended by agentskills.io)
  • Stay under 6,000 chars if you also target Cursor/Windsurf
  • Use [a-z0-9-]+ for skill names — lowercase, hyphens only, matching directory name
  • Use triple-backtick code blocks with language tags
  • Use UTF-8 without BOM, LF line endings
  • Include trigger keywords in descriptions so semantic matching works across tools
  • Move detailed content to references/ for progressive disclosure
  • Version control your skills — they’re the most portable artifact
  • Stick to standard frontmatter fields: name, description, license, metadata, compatibility, allowed-tools

Don’t

  • Don’t rely on XML tags for structure — only Claude parses them
  • Don’t use !command or $ARGUMENTS in skills meant for multiple tools
  • Don’t put emoji or unicode in skill names (descriptions are fine)
  • Don’t assume multiline YAML works — test across tools
  • Don’t put secrets in skills/rules — they’re version-controlled markdown
  • Don’t use tool-specific frontmatter (disable-model-invocation, agents/openai.yaml, globs, alwaysApply) in cross-platform skills — move these to tool-specific config
  • Don’t exceed 200 lines for auto-loaded memory files (Claude truncates beyond that)

The Portability Hierarchy

Most portable ──────────────────────────── Least portable

   SKILL.md          Instruction files       Memory
   (agentskills.io)  (CLAUDE.md, etc.)       (tool-specific)

   20+ tools         Symlink across 3-4      Zero interop
   Same format       Cursor/Windsurf need    Every tool different
   Same directory    format transforms       File, DB, or API
   structure

Tool-Specific Features to Use Sparingly

FeatureToolPortable Alternative
context: forkClaude CodeNone — unique to Claude
agents/openai.yamlCodex CLIStandard SKILL.md metadata
globs / alwaysApplyCursorDescription-based semantic matching
Activation modesWindsurfDescription-based semantic matching
@file importsGemini CLIreferences/ directory
!command executionClaude Codescripts/ directory
$ARGUMENTSClaude CodeAgent-level argument handling
excludeAgentVS Code CopilotNone — unique to Copilot
SOUL.md, HEARTBEAT.mdOpenClawSKILL.md + memory

18. Common Pitfalls

Dead Rules

Path globs break after refactoring. Renamed directories cause rules to silently stop matching. Audit regularly.

Memory Poisoning

Auto-generated memories can be manipulated (prompt injection via committed files). Review auto-memory content before trusting it.

Size Bloat

Auto-memories grow unbounded. Large instruction files consume context budget, leaving less room for actual conversation. Claude Code’s 200-line auto-load limit is a safeguard.

LLM Inconsistency

Cursor rules behave differently depending on which LLM backend is active. The same .mdc rule may work with Claude but fail with GPT-4. Agent Skills are more consistent because the SKILL.md body is pure natural language.

Migration Pain

No tool provides automatic format conversion. Moving from .cursorrules to SKILL.md, or from Windsurf rules to anything else, requires manual work. This is the gap tome aims to fill.


19. What This Means for tome

Format Families to Support

Based on this research, tome’s connector architecture needs to handle four format families:

FamilyToolsDistribution MethodTranslation Needed
SKILL.md standardClaude Code, Codex, Copilot, Antigravity, OpenClaw, OpenCode, AmpSymlink (same format)No — canonical format
Custom rulesCursor (.mdc), Windsurf (activation modes), Copilot (.instructions.md)Copy with transformYes — SKILL.md ↔ native rules
MCP-onlyGoose, PicoClawMCP config injectionNo transform, just config entry
Config-onlyAiderConfig injectionMinimal (YAML config entry)

Syncable Structures (Today)

StructureHow to SyncComplexity
Skills (SKILL.md)Symlink — same format everywhereLow
Instruction filesSymlink with rename (CLAUDE.md → AGENTS.md → GEMINI.md)Low
MCP configConfig injection (write entry into tool’s .mcp.json)Medium

Syncable with Transforms (Future)

StructureTransform Needed
Cursor rulesSKILL.md ↔ .mdc (add/strip globs, alwaysApply)
Windsurf rulesSKILL.md ↔ .md with activation mode
Copilot instructionsSKILL.md ↔ .instructions.md (add/strip applyTo)

Not Syncable (Tool-Specific)

StructureWhy
HooksFundamentally different event models, different config formats
AgentsDifferent frontmatter fields, capability models, isolation strategies
PluginsDifferent manifest formats, different bundling conventions
MemoryDifferent storage, different lifecycle, different retrieval
Settings/PermissionsDifferent security models entirely

Connector Design Recommendations

  1. SKILL.md as canonical format — The library stores everything in Agent Skills standard format. This is already what most tools natively understand. As of Feb 2026, 7+ major tools read SKILL.md natively.

  2. Symlink-first for SKILL.md tools — Claude Code, Codex, Copilot, Antigravity, OpenCode, and Amp all read SKILL.md natively. Symlinks from library to target skill directory are zero-cost and keep everything in sync.

  3. Transform pipeline for custom rules — Three tools need format transforms:

    • Cursor: Generate .mdc files with description, globs, and alwaysApply frontmatter from SKILL.md metadata
    • Windsurf: Generate rules with appropriate activation mode from skill description
    • Copilot: Generate .instructions.md with description and applyTo frontmatter
  4. MCP config for tool-based targets — Tools that consume skills via MCP get a config entry pointing at the tome MCP server rather than direct file access. Goose is the primary MCP-only target.

  5. OpenClaw/Nanobot as special cases — Their 8-file workspace model means a connector needs to map skills into the appropriate slot (AGENTS.md for instructions, TOOLS.md for capabilities, etc.) or just target their skills directory.

What’s Portable vs. What’s Not

LayerPortable?Details
SkillsYesSKILL.md standard fields translate across 7+ tools
MCPYesOpen standard, every major tool supports it
Instruction filesPartiallySame markdown content, different filenames — symlinkable
RulesPartiallyNeed format transforms for Cursor (.mdc), Windsurf, Copilot (.instructions.md)
HooksNo4 tools have hooks, all with incompatible formats and event models
AgentsNo5 tools have agents, all with different frontmatter, capabilities, isolation
PluginsNo3 tools have plugin systems, all with different manifests
MemoryNo7 tools have memory, all with different storage, lifecycle, retrieval

Tool-specific skill extensions are lost in translation — tome should preserve them in metadata when syncing between tools of the same type, but can safely drop them when translating to a different format family.

The Convergence Trend

As of Feb 2026, the industry is consolidating around:

  1. Agent Skills (SKILL.md) for portable instructions — the clear winner
  2. MCP for portable tool integrations — universal adoption
  3. Markdown instruction files for project rules — same concept, different filenames

Everything else (hooks, agents, plugins, memory) remains fragmented with no signs of standardization. For tome, this means the v1 focus on skills + MCP + instruction files covers the portable surface area. Extended structures would require per-tool connectors with no format translation possible.


20. Skill Installers: npx skills (Vercel Labs)

npx skills is a JavaScript-based skill installer with a polished interactive CLI. It supports 41 agent targets and uses a two-tier architecture remarkably similar to tome’s library model.

Registry: skills.sh — browsable skill registry with per-skill detail pages.

Architecture

Canonical copies are stored in .agents/skills/<name>/ (the emerging universal path) or tool-specific directories. Distribution to individual tools uses symlinks from their native skill dirs back to the canonical location.

Install flow: clone repo → select skills → select targets → choose scope (project/global) → choose method (symlink/copy) → security assessment → install.

.skill-lock.json (v3)

Lockfile at .agents/.skill-lock.json tracks installed skills with provenance and content hashes:

{
  "version": 3,
  "skills": {
    "skill-name": {
      "source": "owner/repo",
      "sourceType": "github",
      "sourceUrl": "https://github.com/owner/repo.git",
      "skillPath": "skills/skill-name/SKILL.md",
      "skillFolderHash": "c2f31172b6f256272305a5e6e7228b258446899f",
      "installedAt": "2026-03-06T12:49:32.629Z",
      "updatedAt": "2026-03-06T12:49:32.629Z"
    }
  },
  "dismissed": { ... },
  "lastSelectedAgents": ["amp", "cline", "codex", "cursor", "..."]
}

Key fields: sourceType + sourceUrl for provenance, skillFolderHash for content-based idempotency (similar to tome’s SHA-256 manifest hashes), installedAt/updatedAt timestamps.

Agent Targets (41 total)

Universal agents (share .agents/skills/ as project-scoped path):

AgentGlobal Path
Amp$XDG_CONFIG_HOME/agents/skills
Cline~/.agents/skills
Codex$CODEX_HOME/skills
Cursor~/.cursor/skills
Gemini CLI~/.gemini/skills
GitHub Copilot~/.copilot/skills
Kimi Code CLI$XDG_CONFIG_HOME/agents/skills
OpenCode$XDG_CONFIG_HOME/opencode/skills
Replit$XDG_CONFIG_HOME/agents/skills

Additional agents (tool-specific paths):

AgentProject PathGlobal Path
Adal.adal/skills~/.adal/skills
Antigravity.agent/skills~/.gemini/antigravity/skills
Augment.augment/skills~/.augment/skills
Claude Code.claude/skills$CLAUDE_HOME/skills
CodeBuddy.codebuddy/skills~/.codebuddy/skills
Command Code.commandcode/skills~/.commandcode/skills
Continue.continue/skills~/.continue/skills
Cortex.cortex/skills~/.snowflake/cortex/skills
Crush.crush/skills$XDG_CONFIG_HOME/crush/skills
Droid.factory/skills~/.factory/skills
Goose.goose/skills$XDG_CONFIG_HOME/goose/skills
iFlow CLI.iflow/skills~/.iflow/skills
Junie.junie/skills~/.junie/skills
Kilo.kilocode/skills~/.kilocode/skills
Kiro CLI.kiro/skills~/.kiro/skills
Kode.kode/skills~/.kode/skills
MCPJam.mcpjam/skills~/.mcpjam/skills
Mistral Vibe.vibe/skills~/.vibe/skills
Mux.mux/skills~/.mux/skills
Neovate.neovate/skills~/.neovate/skills
OpenClawskills(custom)
OpenHands.openhands/skills~/.openhands/skills
Pi.pi/skills~/.pi/agent/skills
Pochi.pochi/skills~/.pochi/skills
Qoder.qoder/skills~/.qoder/skills
Qwen Code.qwen/skills~/.qwen/skills
Roo.roo/skills~/.roo/skills
Trae.trae/skills~/.trae/skills
Trae CN.trae/skills~/.trae-cn/skills
Windsurf.windsurf/skills~/.codeium/windsurf/skills
Zencoder.zencoder/skills~/.zencoder/skills

CLI Commands

CommandPurpose
npx skills add <url>Install skills from a git repo
npx skills find <query>Search the skills.sh registry
npx skills listList installed skills
npx skills checkVerify skill integrity
npx skills updateUpdate installed skills
npx skills removeRemove installed skills
npx skills initInitialize skills in a project

Security Assessment

The installer integrates security risk assessment with three providers:

ProviderAssessment
GenSafe / Unsafe
SocketAlert count
SnykRisk level

Each skill gets a security rating before installation, with a link to details on skills.sh.

Implications for tome

  1. .agents/skills/ is the emerging universal path — 9 agents converge on it. Tome’s Directory source type can discover skills there today.
  2. Lockfile as prior art for tome.lock.skill-lock.json v3 tracks the same concepts tome needs: content hashes for idempotency, source provenance, install timestamps.
  3. Symlink vs copy choicenpx skills offers both, recommending symlink. Tome already uses this model (library copies + symlink distribution).
  4. Security assessment — interesting prior art for a future tome audit command.
  5. 41-agent coverage — significantly expands the known agent landscape beyond tome’s current connector list.

Sources

Standards & Specifications

Claude Code

Codex CLI

VS Code Copilot

Cursor & Windsurf

Gemini CLI & Antigravity

OpenClaw, Amp, Goose, Aider, Others

Skill Installers

  • npx skills (Vercel Labs) — 41-agent skill installer with lockfile and security assessment
  • skills.sh — skill registry with per-skill detail pages and security ratings

Analysis & Security

Frontmatter Compatibility

SKILL.md files use YAML frontmatter to declare metadata. The base standard comes from the Agent Skills spec, but each platform extends it with its own fields. This page documents the current state of compatibility across tools.

Base Standard (agentskills.io)

FieldRequiredConstraints
nameYesMax 64 chars. Lowercase letters, numbers, hyphens only. Must match directory name.
descriptionYesMax 1024 chars. Non-empty.
licenseNoLicense name or reference.
compatibilityNoMax 500 chars. Environment requirements.
metadataNoArbitrary key-value map.
allowed-toolsNoSpace-delimited tool list. (Experimental)

Platform Extensions

These fields are valid on their respective platforms but will be silently ignored (or warned about) elsewhere.

FieldPlatformPurpose
disable-model-invocationClaude CodeUser-only invocation (no auto-trigger)
user-invocableClaude Codefalse = model-only background knowledge
argument-hintClaude CodeHint for argument parsing
contextClaude Codefork = run in isolated subagent
agentClaude CodeSpecify subagent type (e.g., Explore)
hooksClaude CodeLifecycle hooks scoped to the skill
excludeAgentVS Code CopilotTarget coding-agent vs code-review

Codex uses a separate agents/openai.yaml file instead of extending SKILL.md frontmatter.

Non-Standard Fields Found in the Wild

These appear in community skills but are not part of any spec. They will be silently ignored by standard-compliant tools.

FieldIssueRecommendation
versionNot in any specMove to metadata.version
categoryNot in any specMove to metadata.category
tagsNot in any specMove to metadata.tags
last-updatedNot in any specMove to metadata.last-updated
modelAgent frontmatter field, not SKILL.mdRemove or move to agent config

Known Bugs & Gotchas

VSCode validator flags valid fields

The VS Code Copilot extension’s skill validator has an outdated schema that flags allowed-tools as unsupported, even though it’s part of the base spec. This is a known issue.

Multiline YAML descriptions break on Claude Code

Claude Code’s SKILL.md parser does not handle implicit YAML folding (Prettier-style wrapped lines). Descriptions that span multiple lines without an explicit block scalar will be silently truncated.

Breaks:

---
description: This is a long description that has been
  wrapped by Prettier across multiple lines
---

Works:

---
description: This is a long description on a single line
---

Also works (explicit block scalar):

---
description: |
  This is a long description that uses
  an explicit block scalar indicator
---

Unknown fields are silently ignored

All standard-compliant tools silently ignore unknown frontmatter fields. The VS Code extension is an exception — it shows warnings for unrecognized fields. This means non-standard fields won’t cause errors but also won’t do anything.

Case sensitivity

  • All field names must be lowercase
  • The filename must be exactly SKILL.md (uppercase)

Platform Limits

ConstraintLimitPlatform
name length64 charsAll (base spec)
description length1024 charsAll (base spec)
description length500 charsVS Code Copilot (stricter)
compatibility length500 charsAll (base spec)
Skill body size~6000 charsWindsurf
Skill body size~5000 tokensGeneral recommendation

How tome Uses This

tome currently symlinks skill directories as-is without parsing frontmatter. The v0.3.x release will add:

  • Frontmatter parsing during discovery
  • tome lint command with tiered validation (errors, warnings, info)
  • tome doctor frontmatter health checks
  • tome status metadata summary per skill

See the Roadmap for details.

Vercel Skills Comparison

Research into vercel-labs/skills (npx skills) — the closest comparable project to tome. Both manage AI coding skills across multiple tools. This doc catalogs features and tooling patterns tome is missing to inform roadmap decisions.

Last updated: March 2026


1. Overview

tomeVercel Skills
LanguageRust (edition 2024)TypeScript (Node.js 18+)
Installcargo install tome / Homebrewnpx skills (zero-install)
Versionv0.3.1v1.4.5
ArchitectureLibrary-first: discover → consolidate → distributeInstaller-first: fetch → install (symlink/copy)
ScopeMulti-machine library manager with lockfile syncSingle-machine skill installer with remote sources

Core philosophical difference: Tome treats the library as the source of truth — skills are consolidated into a local library, then distributed to targets. Vercel Skills is an installer — it fetches from remote sources and symlinks directly into agent directories. There’s no intermediate “library” abstraction.


2. Feature Comparison

FeaturetomeVercel SkillsNotes
Local directory sourcesBoth scan local paths for SKILL.md dirs
Claude plugin sourcesTome reads installed_plugins.json; Vercel reads .claude-plugin/marketplace.json
GitHub remote sources🔜 v0.6skills add owner/repo, shorthand syntax, branch specs
GitLab remote sources🔜 v0.6Full URL support
Well-known HTTP providersRFC 8615 /.well-known/skills/index.json endpoints
npm/node_modules sync✅ (experimental)Crawls node_modules for skills
Symlink distributionBoth use symlinks as primary distribution method
MCP distribution❌ (removed)Was removed — all tools now scan SKILL.md dirs natively
Copy fallbackVercel falls back to copy when symlinks fail
Lockfiletome.lock.skill-lock.json v3Both track content hashes and provenance
Per-machine preferencesmachine.tomlTome can disable skills per machine
Multi-machine synctome syncLockfile diffing with interactive triage
Library consolidationTome’s two-tier model; Vercel installs directly
Interactive browsetome browseTUI with fuzzy search (ratatui + nucleo)
Skill scaffoldingskills initGenerates SKILL.md template
Public search/registryskills findAPI-backed search at skills.sh with install counts
Remote update checkingskills checkCompares GitHub tree SHAs for available updates
Agent auto-detection🔜 (wizard only)Async detection of 50+ installed agents
Format transforms🔜 v0.4Planned: SKILL.md ↔ .mdc ↔ .instructions.md
Frontmatter validation🔜 v0.4PartialVercel parses name/description/metadata.internal
Doctor/diagnosticstome doctorOrphan detection, manifest repair, symlink health
MCP server❌ (removed)Was removed — no known consumers
Dry-run modePreview changes without filesystem writes
Git commit integrationAuto-offers commit after sync when library is a git repo
TelemetryAnonymous usage tracking (disabled in CI)
Known agent targets750+Significant coverage gap

3. Notable Features Tome Lacks

3.1 Remote Git Sources

Vercel’s source parser accepts multiple formats:

skills add owner/repo                    # GitHub shorthand
skills add owner/repo@skill-name         # specific skill from repo
skills add owner/repo/tree/main/skills/  # subpath targeting
skills add https://gitlab.com/org/repo   # GitLab
skills add git@github.com:owner/repo.git # SSH
skills add ./local-path                  # local directory

Branch/tag targeting via /tree/<ref> syntax. Subpath extraction lets users install a single skill from a multi-skill repo.

Tome status: Planned for v0.6 (Git Sources). Vercel’s UX — especially the shorthand syntax and subpath targeting — is worth studying when designing tome add.

3.2 Skill Scaffolding (skills init)

npx skills init my-skill

Generates a SKILL.md template with frontmatter boilerplate. Low complexity, high convenience for skill authors.

Tome status: Not on roadmap. Would be a simple addition — tome new <name> that creates <name>/SKILL.md with a frontmatter template. Consider adding as a quick win.

3.3 Public Search & Registry

skills find [query] provides:

  • Interactive terminal UI with keyboard navigation
  • API-backed search at https://skills.sh/ (top 10 results, sorted by install count)
  • Debounced queries with formatted output

The registry at skills.sh acts as a public directory of community skills. This creates a discovery loop: authors publish, users search, install counts drive ranking.

Tome status: Not on roadmap. A public registry is a significant undertaking. However, integrating with skills.sh as a read-only source could be a lighter-weight option — tome could query the same API without building its own registry.

3.4 Remote Update Checking

skills check POSTs to a backend API with current lockfile state, compares GitHub tree SHAs to detect available updates. skills update then fetches and replaces.

Tome status: tome sync exists but only diffs the local lockfile against the current discovery state. It doesn’t check remote sources for newer versions. Once git sources land (v0.6), remote update checking should follow naturally.

3.5 Well-Known Providers

Vercel supports RFC 8615 /.well-known/skills/index.json endpoints — any HTTP server can advertise available skills by hosting a JSON manifest at a well-known URL. This enables decentralized skill distribution without a central registry.

Tome status: Not on roadmap. Novel approach worth considering for the connector architecture. Could be a lightweight alternative to a full registry.

3.6 Agent Target Coverage (50+)

Vercel supports 50+ agents. Their agents.ts defines per-agent configuration including:

  • Project and global skill paths
  • Whether the agent shares the universal .agents/skills/ directory
  • Installation detection method

Agents in Vercel not in tome’s KnownTarget list:

AgentSkills PathNotes
Cline.cline/skills/VS Code extension
Warp.warp/skills/Terminal-native
OpenCode.agents/skills/Universal path
CodeBuddy.codebuddy/skills/
Goose.goose/skills/
Amp.amp/skills/
Aider.aider/skills/
Kilo Code.kilo-code/skills/
RooCode.roo-code/skills/
Zed.zed/skills/
Trae.trae/skills/
Melty.melty/skills/
otto-eng.otto/skills/
Pear.pear/skills/
Sourcegraph Cody.sourcegraph-cody/skills/
Void.void/skills/
Junie.junie/skills/
Augment.augment/skills/
Aide.aide/skills/
Blackbox AI.blackbox-ai/skills/
Qodo.qodo/skills/
Tabnine.tabnine/skills/
GitHub Spark.spark/skills/

Many share the universal .agents/skills/ path. Tome’s data-driven target config already supports arbitrary agents, but expanding KnownTarget auto-discovery would improve the wizard experience.

Notable exception — OpenClaw: Unlike most tools that have a single skills path, OpenClaw has a two-level structure: a shared .openclaw/skills/ directory across all agents plus per-agent skills/ directories under each agent’s workspace. This may require a multi-path target model or an OpenClaw-specific connector extension.

Design consideration — per-target skill selection: Vercel’s --agent flag filters which agents receive a skill at install time, but the assignment is not persisted — their lockfile has no per-skill agent tracking. lastSelectedAgents is just a UX hint for the next prompt. Changing which agents have a skill requires reinstalling. This is a significant limitation.

Tome can do better by managing assignments entirely in machine.toml (no skill frontmatter changes needed). Proposed resolution model with layered precedence:

# machine.toml

# Global: applies to all targets unless overridden (existing behavior)
[disabled]
skills = ["noisy-skill"]

# Per-target: disable additional skills for this target
[targets.codex]
disabled = ["claude-only-skill"]

# Per-target allowlist: ONLY these skills go to this target
[targets.openclaw-agent-x]
enabled = ["specialized-skill"]

Resolution order:

  1. Skill is enabled by default for all targets
  2. Global disabled removes it everywhere (existing machine.toml behavior)
  3. Per-target disabled removes it from specific targets only
  4. Per-target enabled (if present) acts as an allowlist — only listed skills reach that target

This keeps the common case simple (everything goes everywhere) while supporting opt-out at two granularity levels. The enabled allowlist is only needed for niche cases like OpenClaw’s per-agent workspaces. All managed in tome settings — no skill frontmatter modifications required.

Tome status: Partially addressed in #248 (audit known targets against platform docs). The data-driven config means users can add any target manually, but wizard auto-discovery only covers 7 agents.

3.7 npm/node_modules Sync

skills experimental_sync scans node_modules/ for packages containing skills. This supports distributing skills as npm packages — a novel distribution channel.

Tome status: Not on roadmap. Low priority given the Rust ecosystem focus, but the concept of “skills as packages” in language-specific package managers is worth noting.

3.8 Plugin Manifest Compatibility

Vercel reads .claude-plugin/marketplace.json and .claude-plugin/plugin.json to discover skills bundled with Claude plugins. This enables compatibility with the Claude plugin marketplace ecosystem.

Tome status: Tome reads installed_plugins.json from the Claude plugin cache directory (a different integration point). The .claude-plugin/ manifest format is not currently parsed. Both approaches achieve plugin-sourced skill discovery, but through different mechanisms.


4. Tooling & DX Patterns

Source Parser

Vercel’s source-parser.ts normalizes diverse input formats into a unified ParsedSource type:

type ParsedSource = {
  owner: string;
  repo: string;
  provider: 'github' | 'gitlab' | 'local';
  ref?: string;           // branch/tag
  subpath?: string;       // path within repo
  skillName?: string;     // specific skill
}

This decouples source resolution from installation logic. When tome implements git sources, a similar parser would be valuable.

Lockfile Versioning

Vercel’s lockfile has a version field (currently v3). When an old-format lockfile is detected, it’s wiped entirely — users must reinstall. This aggressive migration strategy avoids complex upgrade code at the cost of user inconvenience.

Tome’s tome.lock doesn’t yet have a version migration strategy. Worth adding a version field early to avoid future pain.

Agent Auto-Detection

Vercel detects installed agents asynchronously by checking for agent-specific markers (config directories, binaries). This enables smart defaults during installation — only install to agents the user actually has.

Tome’s wizard does basic path existence checks for known source/target locations, but doesn’t detect agents as a first-class concept. The wizard could benefit from a richer detection step.

Security: Path Sanitization

Vercel’s sanitizeName() prevents directory traversal via skill names, and isSubpathSafe() rejects .. segments. Tome’s SkillName type rejects path separators (/, \) at parse time, achieving the same goal through the type system. Tome’s approach is arguably stronger — invalid names can’t even be constructed.


5. Architectural Differences

AspecttomeVercel Skills
Data flowSources → Library → TargetsRemote → Agent directories
Canonical locationLibrary dir (~/.tome/skills/)Agent skills dirs (.agents/skills/)
Multi-machineLockfile + per-machine prefsSingle-machine only
Offline supportFull (library is local)Partial (needs network for remote sources)
Update modelDiff-based triage (tome sync)Replace-based (skills update)
CleanupAutomated stale removal with interactive confirmManual skills remove
Diagnosticstome doctor with repairNone

Key takeaway: Tome’s library abstraction adds complexity but enables features Vercel can’t easily replicate (multi-machine sync, lockfile diffing, automated cleanup, diagnostics). Vercel’s installer model is simpler but single-machine.


6. Recommendations

Prioritized by effort-to-value ratio, mapped to existing roadmap items where applicable.

Quick Wins (small effort, immediate value)

  1. Expand KnownTarget list — Add 15–20 more agents from Vercel’s list to wizard auto-discovery. Data-only change in wizard.rs. (Extends #248)

  2. tome new <name> scaffolding — Generate a <name>/SKILL.md template with standard frontmatter. Simple new command. (New issue)

  3. Lockfile version field — Add "version": 1 to tome.lock now, before we need migration logic. (New issue)

Medium-Term (aligns with existing roadmap)

  1. Per-target skill selection — Extend machine.toml with per-target disabled/enabled lists. Layered resolution: global disabled → per-target disabled → per-target enabled allowlist. Enables OpenClaw per-agent workspaces and general skill-to-agent affinity. Vercel’s --agent flag is install-time-only with no persistence — tome can do better. (#253)

  2. Source parser for git remotes — Study Vercel’s shorthand syntax (owner/repo, @skill-name, /tree/branch) when designing tome add. (Informs v0.6: Git Sources, #58)

  3. Remote update checking — Extend tome sync to check remote sources, not just local lockfile diffs. (After v0.6)

  4. Agent auto-detection — Upgrade wizard to detect installed agents dynamically rather than just checking path existence. (Enhancement to wizard)

Future Consideration (worth watching)

  1. Well-known providers — RFC 8615 skill endpoints could complement git sources as a lightweight discovery mechanism. Novel and decentralized.

  2. skills.sh integration — Read-only integration with Vercel’s public registry as a discovery source. Avoids building our own registry while providing discoverability.

  3. Copy fallback — Vercel supports copy when symlinks fail. Tome is Unix-only and symlink-only. Worth considering if Windows support ever becomes a goal.

Test Setup

tome has two layers of tests: unit tests co-located with each module, and integration tests that exercise the compiled binary end-to-end. All tests run in CI on both Ubuntu and macOS.

Test Architecture

graph TB
    subgraph CI["GitHub Actions CI (ubuntu + macos)"]
        FMT["cargo fmt --check"]
        CLIP["cargo clippy -D warnings"]
        TEST["cargo test --all"]
        BUILD["cargo build --release"]
        FMT --> CLIP --> TEST --> BUILD
    end

    subgraph TEST_SUITE["cargo test --all"]
        UNIT["Unit Tests<br/><i>~825 across 25+ modules (v0.11)</i>"]
        INTEG["Integration Tests<br/><i>~197 across cli_*.rs files (v0.11)</i>"]
    end

    TEST --> TEST_SUITE

Two Test Types

Unit Tests (co-located, #[cfg(test)])

Each module has a mod tests block that tests its public functions in isolation. These tests create temporary directories with tempfile::TempDir and never touch the real filesystem.

Integration Tests (crates/tome/tests/cli_*.rs)

These compile the tome binary and run it as a subprocess using assert_cmd. They verify the full CLI flow: argument parsing, config loading, pipeline execution, and output formatting. Post-HARD-13 (v0.10), the original tests/cli.rs was split into per-domain files (cli_sync.rs, cli_doctor.rs, cli_status.rs, cli_init.rs, cli_make_release.rs, etc.) with shared helpers under tests/common/ — the module-level tables below are a point-in-time snapshot from pre-split and will drift; run cargo test -p tome -- --list for the live breakdown.

graph LR
    subgraph Integration["tests/cli.rs"]
        CMD["assert_cmd<br/>spawns tome binary"]
        TMP["assert_fs::TempDir<br/>isolated filesystem"]
        PRED["predicates<br/>stdout assertions"]
        CMD --> TMP
        CMD --> PRED
    end

    subgraph Unit["#[cfg(test)] modules"]
        TEMP["tempfile::TempDir<br/>isolated filesystem"]
        SYML["unix_fs::symlink<br/>real symlink ops"]
        TEMP --> SYML
    end

Module-by-Module Breakdown

Note: Test counts below reflect a point-in-time snapshot. Run cargo test for current counts.

graph TB
    subgraph unit_tests["Unit Tests (~825 — counts drift, run cargo test --list)"]
        CONFIG["config.rs<br/>─────────<br/>25 tests"]
        DISCOVER["discover.rs<br/>─────────<br/>17 tests"]
        LIBRARY["library.rs<br/>─────────<br/>31 tests"]
        DISTRIBUTE["distribute.rs<br/>─────────<br/>12 tests"]
        CLEANUP["cleanup.rs<br/>─────────<br/>8 tests"]
        DOCTOR["doctor.rs<br/>─────────<br/>20 tests"]
        STATUS["status.rs<br/>─────────<br/>18 tests"]
        LOCKFILE["lockfile.rs<br/>─────────<br/>15 tests"]
        MANIFEST["manifest.rs<br/>─────────<br/>8 tests"]
        MACHINE["machine.rs<br/>─────────<br/>12 tests"]
        UPDATE["update.rs<br/>─────────<br/>8 tests"]
        WIZARD["wizard.rs<br/>─────────<br/>6 tests"]
        PATHS["paths.rs<br/>─────────<br/>8 tests"]
        BROWSE["browse/<br/>─────────<br/>14 tests"]
        LIB["lib.rs<br/>─────────<br/>12 tests"]
    end

    subgraph integration_tests["Integration Tests (~197 across cli_*.rs)"]
        CLI["tests/cli_*.rs<br/>─────────<br/>18 files post-HARD-13"]
    end

    style CONFIG fill:#e8f4e8
    style DISCOVER fill:#e8f4e8
    style LIBRARY fill:#e8f4e8
    style DISTRIBUTE fill:#e8f4e8
    style CLEANUP fill:#e8f4e8
    style DOCTOR fill:#e8f4e8
    style STATUS fill:#e8f4e8
    style LOCKFILE fill:#e8f4e8
    style MANIFEST fill:#e8f4e8
    style MACHINE fill:#e8f4e8
    style UPDATE fill:#e8f4e8
    style WIZARD fill:#e8f4e8
    style PATHS fill:#e8f4e8
    style BROWSE fill:#e8f4e8
    style LIB fill:#e8f4e8
    style CLI fill:#e8e4f4

config.rs — 25 tests

Tests config loading, serialization, tilde expansion, validation, and target parsing.

TestWhat it verifies
expand_tilde_expands_home~/foo becomes /home/user/foo
expand_tilde_leaves_absolute_unchanged/absolute/path passes through
expand_tilde_leaves_relative_unchangedrelative/path passes through
default_config_has_empty_sourcesConfig::default() has no sources or exclusions
config_loads_defaults_when_file_missingMissing file returns default config (no error)
config_roundtrip_tomlSerialize -> deserialize preserves all fields
config_load_fails_on_malformed_tomlMalformed TOML returns Err
config_parses_full_tomlFull config string with sources + targets parses correctly
config_parses_arbitrary_target_nameCustom target names work in BTreeMap
config_parses_claude_target_from_tomlClaude-specific target fields parse correctly
config_roundtrip_claude_targetClaude target serialization roundtrip
load_or_default_errors_when_parent_dir_missingMissing parent dir returns error
load_or_default_returns_defaults_when_parent_existsExisting parent dir with no file returns defaults
target_config_roundtrip_symlinkSymlink target serialization roundtrip
targets_iter_includes_claudeClaude target included in iterator
try_from_raw_rejects_unknown_methodUnknown method string rejected
try_from_raw_rejects_symlink_without_skills_dirSymlink target requires skills_dir field
validate_passes_for_valid_configValid config passes validation
validate_rejects_duplicate_source_namesDuplicate source names rejected
validate_rejects_empty_source_nameEmpty source name rejected
validate_rejects_library_dir_that_is_a_fileLibrary dir pointing to a file rejected
target_name_accepts_validValid target names pass validation
target_name_rejects_emptyEmpty target name rejected
target_name_rejects_path_separatorTarget names with / rejected
target_name_deserialize_rejects_emptyEmpty target name rejected during deserialization

discover.rs — 17 tests

Tests skill discovery from both Directory and ClaudePlugins source types, plus skill name validation.

TestWhat it verifies
discover_directory_finds_skillsFinds */SKILL.md dirs, ignores dirs without SKILL.md
discover_directory_warns_on_missing_pathMissing source path returns empty vec (no crash)
discover_directory_skips_skill_md_at_source_rootSKILL.md directly in source root is ignored
discover_all_deduplicates_first_winsSame skill name in two sources -> first source wins
discover_all_applies_exclusionsExcluded skill names are filtered out
discover_all_collects_dedup_warningsDeduplication produces warnings
discover_all_collects_naming_warningsNaming issues produce warnings
discover_all_with_partial_config_returns_skillsWorks with incomplete config
discover_claude_plugins_reads_jsonv1 format: flat array with installPath
discover_claude_plugins_reads_v2_jsonv2 format: { plugins: { "name@reg": [...] } }
discover_claude_plugins_unknown_formatUnrecognized JSON structure returns empty vec
discover_claude_plugins_deduplicates_within_sourceSame plugin listed twice in JSON -> deduplicated
discover_claude_plugins_v1_no_provenancev1 format skills have no provenance metadata
skill_name_accepts_validValid skill names pass validation
skill_name_rejects_emptyEmpty name rejected
skill_name_rejects_path_separatorNames with / rejected
skill_name_conventional_checkNaming convention warnings

library.rs — 31 tests

Tests the consolidation step — copying local skills and symlinking managed skills into the library.

TestWhat it verifies
consolidate_copies_skillsLocal skill -> copied into library
consolidate_copies_nested_subdirectoriesNested dirs within skills are preserved
consolidate_idempotentSame skill twice -> unchanged == 1, no filesystem change
consolidate_dry_run_no_changesdry_run=true reports counts but creates nothing
consolidate_dry_run_doesnt_create_dirLibrary dir not created during dry run
consolidate_dry_run_no_manifest_writtenManifest not written during dry run
consolidate_dry_run_manifest_reflects_would_be_stateDry run manifest shows expected state
consolidate_updates_changed_sourceChanged source content -> library copy updated
consolidate_detects_content_changeContent hash change triggers re-copy
consolidate_skips_unmanaged_collisionExisting non-managed dir not overwritten
consolidate_force_recopiesforce=true re-copies even if unchanged
consolidate_local_manifest_reflects_updateManifest updated after local skill change
consolidate_manifest_persistedManifest written to disk
consolidate_symlinks_managed_skillManaged skill -> symlinked into library
consolidate_managed_idempotentManaged skill symlink is idempotent
consolidate_managed_path_changedSource path change -> symlink updated
consolidate_managed_dry_run_no_symlink_createdManaged dry run creates no symlinks
consolidate_managed_force_recreates_symlinkForce recreates managed symlinks
consolidate_managed_skips_non_manifest_dir_collisionNon-manifest dir collision handled
consolidate_managed_manifest_records_managed_flagManifest records managed flag
consolidate_managed_repairs_stale_directoryStale directory state repaired to symlink
consolidate_migrates_v01_symlinkv0.1 symlinks migrated to copies
consolidate_migrates_v01_symlink_records_discovered_sourceMigration records source provenance
consolidate_migrates_v01_symlink_with_broken_targetBroken v0.1 symlink migrated gracefully
consolidate_strategy_transition_local_to_managedLocal -> managed strategy transition
consolidate_strategy_transition_managed_to_localManaged -> local strategy transition
gitignore_lists_managed_skills.gitignore lists managed skill dirs
gitignore_does_not_list_local_skills.gitignore excludes local skills
gitignore_idempotentRepeated gitignore writes are idempotent
gitignore_always_ignores_tmp_files.gitignore includes *.tmp pattern

distribute.rs — 12 tests

Tests the distribution step — pushing skills from library to target tools.

TestWhat it verifies
distribute_symlinks_creates_linksSymlink method creates links in target dir
distribute_symlinks_idempotentSecond run -> linked=0, unchanged=1
distribute_symlinks_force_recreates_linksForce recreates all links
distribute_symlinks_updates_stale_linkStale link pointing elsewhere updated
distribute_symlinks_skips_non_symlink_collisionRegular file at target path -> skipped
distribute_symlinks_skips_manifest_file.tome-manifest.json not distributed
distribute_symlinks_dry_run_doesnt_create_dirTarget dir not created during dry run
distribute_symlinks_dry_run_with_nonexistent_libraryDry run works with missing library
distribute_disabled_target_is_noopenabled: false -> no work done
distribute_skips_disabled_skillsMachine-disabled skills not distributed
distribute_skips_skills_originating_from_target_dirSkills from target’s own dir skipped
distribute_idempotent_with_canonicalized_pathsIdempotent with canonicalized paths

cleanup.rs — 8 tests

Tests stale symlink and manifest cleanup from library and targets.

TestWhat it verifies
cleanup_removes_stale_manifest_entriesManifest entries for missing skills removed
cleanup_removes_broken_legacy_symlinksBroken legacy symlinks cleaned up
cleanup_removes_managed_symlinkStale managed symlinks removed
cleanup_preserves_current_skillsActive skills preserved during cleanup
cleanup_dry_run_preserves_staleDry run counts but doesn’t delete
cleanup_target_removes_stale_linksBroken target links removed
cleanup_target_dry_run_preserves_stale_linksTarget dry run preserves links
cleanup_target_preserves_external_symlinksLinks pointing outside library preserved

doctor.rs — 20 tests

Tests library diagnostics and repair.

TestWhat it verifies
check_healthy_library_returns_no_issuesClean library has no issues
check_detects_orphan_directoryOrphan dir (not in manifest) detected
check_detects_missing_source_pathMissing source path flagged
check_library_no_issuesHealthy library check passes
check_library_orphan_directoryOrphan directory in library detected
check_library_missing_manifest_entryMissing manifest entry detected
check_library_broken_legacy_symlinkBroken legacy symlink detected
check_library_missing_dirMissing library dir handled
check_config_valid_sourcesValid source config passes
check_config_missing_sourceMissing source config flagged
check_target_dir_stale_symlinkStale target symlink detected
check_target_dir_missing_dirMissing target dir handled
check_target_dir_ignores_external_symlinksExternal symlinks ignored
check_unconfigured_returns_not_configuredUnconfigured state detected
diagnose_shows_init_prompt_when_unconfiguredShows init prompt when no config
repair_library_healthy_is_noopRepair on healthy library is no-op
repair_library_removes_orphan_manifest_entryRepair removes orphan manifest entries
repair_library_removes_broken_legacy_symlinkRepair removes broken legacy symlinks
repair_library_removes_broken_managed_symlinkRepair removes broken managed symlinks

lockfile.rs — 15 tests

Tests lockfile generation, loading, and serialization.

TestWhat it verifies
generate_empty_manifestEmpty manifest produces empty lockfile
generate_managed_skill_with_provenanceManaged skills include provenance
generate_local_skill_no_provenanceLocal skills omit registry fields
generate_discovered_skill_not_in_manifestDiscovered skill without manifest entry handled
generate_manifest_entry_without_discovered_skillManifest entry without discovered skill handled
generate_mixed_skillsMix of managed and local skills
deterministic_outputOutput is deterministic (sorted)
roundtrip_serializationSerialize -> deserialize roundtrip
save_creates_fileSave creates lockfile on disk
save_does_not_leave_tmp_fileAtomic write cleans up temp file
load_missing_file_returns_noneMissing lockfile returns None
load_valid_file_returns_someValid lockfile loads successfully
load_corrupt_file_returns_errorCorrupt lockfile returns error
empty_version_string_becomes_noneEmpty version string normalized to None
local_skill_omits_registry_fields_in_jsonLocal skills omit registry fields in JSON

machine.rs — 12 tests

Tests per-machine preferences loading, saving, and disabled skill/target tracking.

TestWhat it verifies
default_prefs_has_empty_disabledDefault prefs have empty disabled set
is_disabled_checks_setis_disabled() checks the disabled set
load_missing_file_returns_defaultsMissing file returns defaults
load_malformed_toml_returns_errorMalformed TOML returns error
save_load_roundtripSave -> load roundtrip preserves state
save_creates_parent_directoriesSave creates parent dirs if needed
save_does_not_leave_tmp_fileAtomic write cleans up temp file
toml_format_is_readableSerialized TOML is human-readable

Run cargo test -p tome -- machine::tests --list for the full current list.

manifest.rs — 8 tests

Tests library manifest operations and content hashing.

TestWhat it verifies
load_missing_manifest_returns_emptyMissing manifest returns empty map
load_corrupt_json_returns_errorCorrupt JSON returns error
manifest_roundtripSave -> load roundtrip
hash_directory_deterministicSame content produces same hash
hash_directory_changes_with_contentChanged content produces different hash
hash_directory_different_filenames_different_hashesDifferent filenames produce different hashes
hash_directory_includes_subdirsSubdirectory contents included in hash
now_iso8601_formatTimestamp format is ISO 8601

status.rs — 18 tests

Tests status gathering and health checks.

TestWhat it verifies
count_entries_counts_directoriesCounts directories in library
count_entries_empty_dirEmpty dir returns 0
count_entries_ignores_hidden_directoriesHidden dirs (.foo) excluded
count_entries_ignores_regular_filesRegular files excluded from count
count_health_issues_empty_dirEmpty dir has no health issues
count_health_issues_ignores_hidden_dirsHidden dirs excluded from health check
count_health_issues_detects_orphan_directoryOrphan directory detected
count_health_issues_detects_manifest_disk_mismatchManifest/disk mismatch detected
gather_unconfigured_returns_not_configuredUnconfigured state detected
gather_with_library_dir_counts_skillsLibrary dir skill count
gather_with_sources_marks_configuredSources marked as configured
gather_with_targets_populates_target_statusTarget status populated
gather_health_detects_orphanHealth check detects orphan dirs
status_shows_init_prompt_when_unconfiguredShows init prompt when unconfigured
status_shows_tables_with_configured_sources_and_targetsFull status output with tables
status_warns_when_library_missing_but_sources_configuredWarning when library dir missing

update.rs — 8 tests

Tests lockfile diffing and triage logic used by tome sync.

TestWhat it verifies
diff_empty_lockfilesTwo empty lockfiles produce no changes
diff_identical_lockfilesIdentical lockfiles produce no changes
diff_added_skillNew skill detected as added
diff_removed_skillMissing skill detected as removed
diff_changed_skillChanged hash detected as changed
diff_same_hash_different_source_is_unchangedSame hash with different source is unchanged
diff_mixed_changesMix of added/removed/changed/unchanged
diff_detects_managed_skillManaged skills flagged in diff

wizard.rs — 6 tests

Tests wizard auto-discovery and overlap detection.

TestWhat it verifies
find_known_sources_in_discovers_existing_dirsAuto-discovers known source paths
find_known_sources_in_empty_home_returns_emptyEmpty home returns no sources
find_known_sources_in_skips_files_with_same_nameFiles with source dir names skipped
detects_source_target_overlapSource/target path overlap detected
detects_claude_source_target_overlapClaude-specific overlap detected
no_overlap_when_paths_differDistinct paths pass overlap check

lib.rs — 12 tests

Tests orchestration-level functions (disabled skill cleanup, commit message generation, tome home resolution).

TestWhat it verifies
cleanup_disabled_removes_library_symlinkDisabled skill symlink removed from target
cleanup_disabled_preserves_external_symlinkNon-library symlinks preserved
cleanup_disabled_skips_non_symlinkRegular files not removed
cleanup_disabled_dry_run_preserves_symlinkDry run preserves symlinks
cleanup_disabled_nonexistent_dir_returns_zeroMissing dir returns 0
commit_message_all_changesCommit message with all change types
commit_message_created_onlyCommit message with creates only
commit_message_no_changesCommit message with no changes
resolve_tome_home_absolute_path_returns_parentAbsolute path resolves to parent
resolve_tome_home_none_returns_defaultNone returns default home
resolve_tome_home_relative_path_returns_errorRelative path rejected
resolve_tome_home_bare_filename_returns_errorBare filename rejected

tests/cli_*.rs — ~197 integration tests across 18 files

Each test compiles and runs the tome binary in a temp directory with a custom config. The table below is a pre-HARD-13 snapshot from when all integration tests lived in a single tests/cli.rs (32 tests); post-v0.10 they’re split across cli_sync.rs, cli_doctor.rs, cli_status.rs, cli_init.rs, cli_make_release.rs, cli_migrate_library.rs, cli_overrides.rs, cli_reassign.rs, cli_remove.rs, cli_browse.rs, cli_backup.rs, cli_add.rs, cli_config.rs, cli_eject.rs, cli_lint.rs, cli_list.rs, cli_misc.rs, and cli_sync_reconcile.rs with shared helpers under tests/common/.

TestCommandWhat it verifies
help_shows_usage--helpPrints usage text
version_shows_version--versionPrints version from Cargo.toml
list_with_no_sources_shows_messagelist“No skills found” with empty config
list_shows_discovered_skillslistSkill names + count in output
list_json_outputs_valid_jsonlist --jsonValid JSON array output
list_json_with_no_skills_outputs_empty_arraylist --jsonEmpty array when no skills
list_json_with_quiet_still_outputs_jsonlist --json -qJSON output even in quiet mode
sync_dry_run_makes_no_changes--dry-run sync“Dry run” in output, library empty
sync_copies_skills_to_librarysyncSkills copied to library dir
sync_creates_lockfilesynctome.lock created
sync_dry_run_does_not_create_lockfile--dry-run syncNo lockfile in dry run
sync_distributes_to_symlink_targetsyncSymlinks created in target dir
sync_idempotentsync (x2)Second run: 0 created, 1 unchanged
sync_updates_changed_sourcesync (x2)Changed source content triggers update
sync_force_recreates_allsync --forceForce re-copies all skills
sync_migrates_v01_symlinkssyncLegacy v0.1 symlinks migrated
sync_lifecycle_cleans_up_removed_skillssync (x2)Removed source -> cleaned up
sync_respects_machine_disabledsyncDisabled skills not distributed
sync_respects_machine_disabled_targetssyncDisabled targets skipped during sync
sync_dry_run_skips_git_commit--dry-run syncNo git commit in dry run
sync_quiet_skips_git_commit-q syncNo git commit in quiet mode
sync_skips_git_commit_without_ttysyncNo git commit without TTY
status_shows_library_infostatus“Library:”, “Sources:”, “Targets:” in output
status_without_config_shows_init_promptstatusInit prompt when unconfigured
config_path_prints_default_pathconfig --pathPrints path containing config.toml
doctor_with_clean_statedoctor“No issues found”
doctor_detects_broken_symlinksdoctorIssues detected with broken symlink
doctor_without_config_shows_init_promptdoctorInit prompt when unconfigured
update_shows_new_skillsupdateNew skills shown after initial sync
update_dry_run_makes_no_changes--dry-run updateDry run preserves state
update_with_no_lockfile_works_gracefullyupdateWorks without existing lockfile
update_disable_removes_symlinkupdateDisabled skill symlink removed

Filesystem Isolation Strategy

Every test creates its own TempDir that is automatically cleaned up when the test ends. This means:

  • Tests never interfere with each other (no shared state)
  • Tests never touch the real ~/.tome/
  • No manual cleanup is needed
  • Tests can run in parallel safely
graph TB
    subgraph test_env["Each Test Gets Its Own World"]
        TD["TempDir::new()"]
        TD --> CONFIG_FILE["config.toml<br/>(points library_dir to temp)"]
        TD --> SOURCE_DIR["source/<br/>skill-a/SKILL.md<br/>skill-b/SKILL.md"]
        TD --> LIBRARY_DIR["library/<br/>(copies + symlinks created here)"]
        TD --> TARGET_DIR["target/<br/>(symlinks distributed here)"]
    end

    subgraph assertions["Assertions"]
        FS["Filesystem checks<br/>is_symlink(), exists(),<br/>read_link(), read_to_string()"]
        COUNTS["Result struct counts<br/>created, unchanged,<br/>updated, linked, removed"]
        OUTPUT["CLI stdout<br/>predicate::str::contains()"]
    end

    test_env --> assertions

Test Dependencies

Defined in the workspace Cargo.toml and used via [dev-dependencies]:

CrateVersionPurpose
tempfile3TempDir for filesystem isolation in unit tests
assert_cmd2Run compiled binary as subprocess in integration tests
assert_fs1TempDir for integration tests (compatible with assert_cmd)
predicates3Composable stdout/stderr assertions (contains, and, etc.)

How to Run Tests

# All tests (unit + integration)
make test              # or: cargo test

# Just one crate
cargo test -p tome

# A specific test by name
cargo test test_name

# Tests in a specific module
cargo test -p tome -- discover::tests

# Only integration tests
cargo test -p tome --test cli

# With output (see println! from tests)
cargo test -- --nocapture

CI Pipeline

GitHub Actions runs on every push to main and every PR, on both ubuntu-latest and macos-latest:

graph LR
    subgraph matrix["Matrix: ubuntu + macos"]
        A["cargo fmt --check"] --> B["cargo clippy -D warnings"]
        B --> C["cargo test --all"]
        C --> D["cargo build --release"]
    end

    PUSH["Push to main<br/>or PR"] --> matrix

The full pipeline is defined in .github/workflows/ci.yml. Running it locally is equivalent to:

make ci    # runs: fmt-check + lint + test

Roadmap

VersionThemeKey FeaturesStatus
v0.1.xPolish & UXWizard improvements, progress spinners, table output, GitHub Pages docs
v0.2Scoped SOTLibrary copies skills (not symlinks), git-friendly library dir
v0.2.1Output LayerData struct extraction, warning collection, --json for list
v0.3Connector ArchitectureBTreeMap targets, KnownTarget registry, npm skill source research
v0.3.xPortable Library (MVP)Per-machine preferences, tome update, lockfile
v0.4.1Browsetome browse (ratatui+nucleo): fuzzy search, preview, sort, actions
v0.4.2Skill Validationtome lint, frontmatter parsing, cross-tool compatibility checks
v0.5Managed SourcesAuto-install, remote sync, unified tome sync
v0.5.1BugfixDefault library_dir from TOME_HOME, skip managed skills to own tool
v0.5.2BugfixLegacy managed symlink cleanup during sync
v0.5.3UX & CLI PolishNO_COLOR, --no-input, grouped triage, batch cleanup, docs update
v0.5.4InfrastructureConfig-based tool root, --json, signal handling, frontmatter, XDG config
v0.6Unified Directory ModelBidirectional directories, git sources, per-target skill selection
v0.7Skill CompositionWolpertinger: merge/synthesize skills from multiple sources via LLM

v0.1.x — Polish & UX

  • Wizard interaction hints: Show keybinding hints in MultiSelect prompts (space to toggle, enter to confirm) — embedded in prompt text to work around dialoguer’s limitation.
  • Clarify plugin cache source: Clarified in v0.4.1 (#312).
  • Wizard visual polish: Color, section dividers, and summary output via console::style() — implemented in wizard.rs.
  • Modern TUI with welcome ASCII art: Evaluate ratatui vs console + indicatif before committing to a framework. → Decision: ratatui + nucleo for interactive commands (tome browse), plain text for non-interactive commands. See v0.2.1 and v0.4.1.
  • Progress spinners for sync (indicatif): Spinners during discover → consolidate → distribute → cleanup steps, implemented in lib.rs.
  • Table-formatted output (tabled): tabled::Table used for tome list and tome status output.
  • Explain symlink model in wizard: Clarify that the library uses symlinks (originals are never moved or copied), so users understand there’s no data loss risk.
  • Optional git init for library: Wizard asks whether to git init the library directory for change tracking — implemented in wizard.rs.
  • Fix installed_plugins.json v2 parsing: Current parser expects a flat JSON array (v1); v2 wraps plugins in { "version": 2, "plugins": { "name@registry": [...] } } — discovery silently finds nothing. Support both formats going forward.
  • Finalize tool name: Decided on tome“Cook once, serve everywhere.”
  • GitHub Pages deployment: Add CI workflow to build and deploy mdBook + cargo doc to GitHub Pages.

v0.2 — Scoped SOT

Make the library the source of truth for local skills. tome sync copies skill directories into the library instead of creating symlinks back to sources. Distribution to targets still uses symlinks (target → library).

  • Library as canonical home (#37): Local skills live directly in the library (real directories, not symlinks). tome sync copies from sources into library, making the library the single source of truth.
  • Git-friendly library directory (#42): Library directory works as a git repo — local skills tracked in git, distribution symlinks are separate.
  • Two-tier symlink model: Sources → (copy) → Library → (symlink) → Targets. Sources are read-only inputs; the library owns the canonical copies; targets get symlinks into the library.
  • Idempotent copy semantics: Only copy when source content has changed (compare timestamps or content hashes). Skip unchanged skills to keep syncs fast.

Not in scope (deferred to v0.5): lockfile, tome update, per-machine preferences, managed source support, git-backed backup.

v0.2.1 — Output Layer ✓

Decouple output rendering from business logic. Prerequisite for tome browse (v0.4.1) and --json output (#167), ensuring new connectors in v0.3 get clean data separation from day one.

  • Renderer trait (ui/mod.rs): Abstract output interface for sync reporting, skill listing, status display, doctor diagnostics, warnings, and confirmations — Closed as superseded (#183). Data struct extraction was the real prerequisite; ratatui (v0.4.1) will consume data structs directly rather than going through a trait.
  • Data struct extraction: status::gather() -> StatusReport, doctor::diagnose() -> DoctorReport, sync pipeline returns SyncReport — pure computation separated from rendering
  • Warning collection: Replace scattered eprintln! in discover/library/distribute with Vec<Warning> returned alongside results
  • TerminalRenderer: Reimplements current output using console/indicatif/tabled/dialoguer — identical user-facing behavior, routed through the trait — Superseded along with Renderer trait.
  • QuietRenderer: Replaces quiet: bool parameter threading with a renderer that suppresses non-error output — Closed as superseded (#188). Not needed without the Renderer trait; quiet parameter threading is sufficient.
  • --json for tome list (#167): Trivially enabled once data structs exist — serialize Vec<SkillRow> directly

v0.3 — Connector Architecture ✓

Replaced the hardcoded Targets struct with a flexible, data-driven target configuration. Originally scoped as a full connector trait architecture, but the pragmatic first step — config flexibility — shipped as the milestone deliverable.

Delivered

  • Generic [[targets]] array: Replaced the hardcoded Targets struct with BTreeMap<String, TargetConfig> (#175). Each target has a name, path, method (symlink/mcp), and connector-specific options. Data-driven KnownTarget registry in the wizard enables custom target support without code changes.
  • npm-based skill source research (#97): Investigated npx skills (Vercel Labs). Confirmed: canonical copies in .agents/skills/<name>/, lockfile at .agents/.skill-lock.json (v3) with content hashes and provenance. A Directory source pointed at ~/.agents/skills/ works for basic discovery; a dedicated source type would preserve provenance metadata from the lockfile.
  • .agents/skills/ as emerging universal path: 9 agents converge on .agents/skills/ as the project-scoped canonical skills directory. Documented in tool-landscape research.

Moved forward

  • Connector trait#192. Unified source/target interface. The BTreeMap solved config flexibility; the trait solves architectural abstraction.
  • Built-in connectors → Part of #192. Claude, Codex, Antigravity, Cursor, Windsurf, Amp, Goose, etc.
  • Format awareness per connector → Captured in #57 (Format Transforms).
  • .claude/rules/ syncing#193. Managed from ~/.tome/rules/, distributed to each target’s rules dir. See Tentative — Format Transforms.
  • Instruction file syncing#194. Managed from ~/.tome/instructions/, mapped to tool-specific filenames. See Tentative — Format Transforms.

v0.3.x — Portable Library (MVP) ✓

Complete the multi-machine skill management story. The lockfile (#38, shipped early) provides the diff mechanism; this milestone adds the interactive UX and per-machine control.

  • Per-machine preferences (#39) (~/.config/tome/machine.toml): Per-machine opt-in/opt-out for skills — machine A uses skills 1,2,3 while machine B only wants 1 and 3. Disabled skills stay in the library but are skipped during distribution.
  • tome update command (#40): Reads lockfile, diffs against local state, surfaces new/changed/removed skills interactively. Offers to disable unwanted new skills. Notification-only for managed plugins — auto-install deferred to v0.5.

v0.4.1 — Browse

Interactive skill browser. Depends on v0.2.1 output layer for clean data access.

tome browse — Interactive TUI (#162)

Full-screen interactive skill browser using ratatui for rendering and nucleo (Helix editor’s fuzzy engine) for matching. skim was ruled out because it owns the terminal and can’t be embedded in a ratatui layout.

  • Basic list with fuzzy search (#164): fzf-style interactive filtering of library skills
  • Preview panel (#165): Split-pane layout showing SKILL.md content alongside the list
  • Sorting and grouping (#166): Sort by name/source/last synced, group by source
  • Detail screen with actions (#169): Per-skill actions (view source, copy path, disable/enable)

Other v0.4.1 Items

  • Enhance tome status display (#168): Health indicators (✓/✗/⚠), tilde-collapsed paths
  • Clarify plugin cache source wording (#312): Clarified as “active plugins installed from Claude Code marketplace”

v0.4.2 — Skill Validation & Linting

YAML frontmatter parsing and a tome lint command that catches cross-tool compatibility issues. See Frontmatter Compatibility for the full spec comparison. Tracked in #47 and #176.

Frontmatter Parsing

  • Add serde_yaml dependency
  • Create SkillFrontmatter struct with typed fields (name, description, license, compatibility, metadata, allowed-tools, Claude Code extensions)
  • skill.rs module: extract and parse YAML frontmatter from --- delimiters, capture unknown fields via #[serde(flatten)]
  • Parse frontmatter during discovery (enrich DiscoveredSkill) — deferred to follow-up
  • Store parsed metadata for status display — deferred to follow-up

tome lint Command

  • lint.rs module with tiered validation (error/warning/info)
  • tome lint CLI command with --format text|json and optional PATH argument
  • Exits with code 1 on errors (CI-friendly)
  • Missing name is a warning (Claude Code infers from directory), name mismatch is an error
  • Unicode Tag codepoint scanning (U+E0001–U+E007F)
  • Non-standard field detection (version, category, tags, etc.)
  • Platform limit warnings (description >500 chars for Copilot, body >6000 chars for Windsurf)

Enhance Existing Commands

  • tome doctor: Add frontmatter health checks alongside existing symlink diagnostics — parse all library skills and report validation results
  • tome status: Show parsed frontmatter summary per skill — name, description (truncated), field count, and any validation issues inline

Target-Aware Warnings (Future)

Requires the v0.3 connector architecture. When distributing to specific targets, warn about:

  • Fields unsupported by that target
  • Description length exceeding target’s limit
  • Body syntax incompatible with target (e.g., XML tags, !command, $ARGUMENTS)

v0.5 — Managed Sources ✓

Auto-install managed plugins, remote sync, and unified tome sync flow. Builds on the portable library foundation from v0.3.x.

  • Auto-install managed plugins (#347, #355): tome sync detects missing managed plugins from the lockfile, prompts to install via claude plugin install <registry_id>. Runs before discovery so newly installed plugins are found immediately.
  • Git repo scope to ~/.tome/ (#348, #350): Backup git repo moved from ~/.tome/skills/ to ~/.tome/, tracking skills, tome.toml, tome.lock, and future config. Top-level .gitignore excludes .tome-manifest.json.
  • Remote sync in tome sync (#349, #353): Pull from remote before sync, push after commit. Fast-forward-only merges — diverged histories bail with actionable error. tome backup init offers remote setup wizard.
  • Collapse tome sync and tome update (#352): tome update removed (breaking). tome sync now includes lockfile diffing and interactive triage. --no-triage flag for CI/scripts.
  • Claude marketplace first (#41): Managed source targeting the Claude plugin marketplace. Version pinning via version string and git commit SHA (gitCommitSha). Lockfile records registry_id, version, and git_commit_sha for full reproducibility.
  • Git-backed backup & restore (#94): tome backup init/snapshot/list/restore/diff with optional auto_snapshot pre-sync snapshots via [backup] config section.
  • Portable config paths: Wizard writes ~/-prefixed paths in tome.toml for portability across machines.
  • Shell completions (#208): tome completions <shell> for bash, zsh, fish, PowerShell via clap_complete
  • Demote lockfile write failure to warning (#224): Lockfile write failures demoted to warning
  • Skill lifecycle (#252): Forking, evaluation, and publishing workflow — unscoped, deferred

v0.5.1 — Bugfix ✓

  • Default library_dir from TOME_HOME (#383): library_dir defaults to TOME_HOME/skills instead of hardcoded ~/.tome/skills
  • Skip managed skills to own tool (#385): Managed plugin skills (e.g., from ~/.claude/plugins) are no longer distributed to their own tool’s skills directory, preventing duplicates

v0.5.2 — Bugfix ✓

  • Legacy symlink cleanup (#385): tome sync removes legacy managed skill symlinks from targets on first run after upgrading

v0.5.3 — UX & CLI Polish ✓

  • NO_COLOR support (#371): console crate respects NO_COLOR env var — colors disabled in non-TTY and when NO_COLOR=1
  • Semantic exit codes (#375): Exit code 2 for invalid arguments (via clap), exit code 1 for runtime errors
  • --no-input flag (#376): Global flag to suppress all interactive prompts (cleanup, triage, install, doctor). Implies --no-triage for sync. Errors on tome init.
  • Keybinding hints (#381): “(space to toggle, enter to confirm)” on triage MultiSelect prompt
  • Managed skill counts (#389): Sync output shows skipped_managed count per target (e.g., “216 skipped (managed)”)
  • Group triage by source (#380): Changes grouped under source headers with +/~/- indicators
  • Batch stale messaging (#382): Cleanup shows all stale skills grouped by previous source, confirms once
  • Subcommand help examples (#378): Every subcommand has usage examples in --help
  • Docs update (#368): README and commands.md updated with all commands and new flags

v0.5.4 — Infrastructure ✓

  • Config-based tool root detection (#390): Derive tool root from source/target config paths instead of hardcoded TOOL_DIRS. Falls back gracefully when paths can’t be resolved.
  • Lockfile write = error (#394): Lockfile write failure now blocks sync instead of just warning
  • --json for status/doctor (#374): Structured JSON output with CountOrError type for clean API shape
  • Signal handling (#373): Graceful Ctrl-C via ctrlc crate — clean exit with code 130
  • Frontmatter in discovery (#393): Parse frontmatter during tome sync discovery; warnings on parse failures
  • XDG config for tome_home (#369): ~/.config/tome/config.toml with tome_home field — no shell env var needed
  • Closed: Merge lockfile/manifest (#370) — not planned, separation is by design
  • Deferred: Init consolidation (#362) — moved to v0.6 (unified directory model)

v0.6 — Unified Directory Model

Replaces separate [[sources]] / [targets.*] config with a unified [directories.*] concept. Each directory declares its relationship to tome (managed, synced, library-only, target-only). See #396 for the full design.

  • Unified directory config (#396): Replace sources/targets with bidirectional directories
  • Git sources (#58): Remote skill repos with clone/pull, branch/tag/SHA pinning, private repo support
  • Standalone SKILL.md import (#92): Import from arbitrary GitHub repos without plugin.json
  • Per-target skill selection (#253): Control which skills are distributed to which targets
  • tome remove (#392): CLI to remove sources/targets from config
  • Change skill source (#395): Switch a skill’s source (local → git) without re-adding
  • Browse TUI polish (#365): Theming, match highlighting, scrollbar, markdown preview

v0.7 — Skill Composition (“Wolpertinger”)

Highly experimental. Generate custom skills by combining or synthesizing content from multiple skill authors/sources.

  • Multi-source skill synthesis (#267): Select parts from multiple skills (GitHub repos, Claude marketplace, npx skills) and let an LLM create a merged “franken-skill”
  • ACP-based authentication: LLM calls go through an Agent Communication Protocol (ACP) flow — authenticate via existing CLIs the user already has (codex-cli, claude-code, gemini CLI) rather than requiring a separate OAuth/API-key setup
  • Skill evaluation/creation skill (#268): A companion skill that agents can use to evaluate, validate, and author skills against the agent skills standard — dogfooding the format
  • tome lint standard validation (extension): Extend tome lint (v0.4.1) to validate against the emerging agent skills standard, not just cross-tool frontmatter compat

Dependencies: v0.5 (managed sources for marketplace access), v0.6 (git sources for GitHub repos), v0.4.1 (lint infrastructure)

Tentative — Per-Target Skill Management

Convenient UX for managing which skills are active per target, and whether per-target config should live centrally or locally. Builds on #253 (per-target skill selection in machine.toml).

  • Target skill management commands: Convenient CLI for adding/removing active skills per target without editing TOML by hand. E.g. tome target claude enable my-skill, tome target codex disable my-skill, or interactive via tome browse actions.
  • Package-level toggling: Enable/disable all skills from a package at once (e.g. tome target codex disable --package axiom-ios-skills). Requires the package/repo label from SkillProvenance.registry_id. Also support glob patterns (e.g. asc-*). In machine.toml, this could be disabled_packages = [...] alongside the existing disabled skill set.
  • Local per-target config: Investigate whether per-target config should live in the target folder itself (e.g. ~/.claude/tome.toml) instead of only centrally. Trade-offs:
    • Central (~/.tome/tome.toml): single source of truth, easy to version-control, but needs namespacing for per-target overrides
    • Local (e.g. ~/.claude/tome.toml): self-contained per tool, discoverable where the tool lives, but scattered across filesystem
    • Hybrid: local overrides central if present — local file wins for that target’s skill selection, central file is the default. Central config would need a [targets.<name>.skills] section or similar namespacing.
    • Current leaning: local replaces central for simplicity — if a local tome.toml exists in the target folder, it fully owns that target’s skill selection. No merge semantics to reason about.
    • Remaining question: How does this interact with machine.toml per-machine preferences?

Tentative — Format Transforms

Not yet scheduled. Needs more design work before committing to a milestone.

  • Rules syncing (#193): Manage tool-specific rule files from ~/.tome/rules/, distributed via symlinks to each target’s rules directory (.claude/rules/, .cursor/rules/, etc.)
  • Instruction file syncing (#194): Manage root-level instruction files (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules) from ~/.tome/instructions/. High complexity — each tool expects a different filename and format; needs a mapping layer and conflict handling.
  • Connector trait (#192): Unified source/target interface as an architectural abstraction over the existing BTreeMap config.
  • Pluggable transform pipeline: Connectors declare input/output formats; the pipeline resolves the translation chain. Preserves original format — transforms are output-only.
  • Copilot .instructions.md format: Copilot’s .instructions.md as a transform target alongside Cursor .mdc and Windsurf rules.
  • Deprecate DistributionMethod::Mcp: Removed in #262. No known targets used MCP distribution — all major AI coding tools read SKILL.md files from disk via symlinks. The tome-mcp binary, tome serve command, and TargetMethod::Mcp distribution path were removed along with the rmcp and tokio dependencies. MCP support can be re-added if a concrete use case emerges.

Tentative — Expand Wizard Auto-Discovery

Scope needs clarifying before committing. The question: which global home-dir skill paths exist for tools not yet covered by the wizard (e.g. ~/.cursor/skills/, Windsurf’s equivalent, etc.)? Per-project paths (.github/skills/, .cursor/rules/) are explicitly out of scope — only global home-dir paths qualify.

  • Audit which global home-dir paths exist across all major tools
  • Add any confirmed paths to KNOWN_SOURCES in wizard.rs

Tentative — Watch Mode

Not yet scheduled. Low priority until core sync pipeline stabilizes.

  • tome watch for auto-sync on filesystem changes (#59)
  • Debounced fsnotify-based watcher
  • Optional desktop notification on sync

Future — Companion macOS App

Native macOS skill manager app (inspired by CodexSkillManager):

  • Browse & manage library: View all skills in the tome library with rendered Markdown previews using swift-markdown-ui
  • Visual skill editing: Edit skill frontmatter and body with live preview
  • Sync trigger: Run tome sync from the GUI with status feedback
  • Source & target management: Configure sources and targets visually instead of editing tome.toml
  • Health dashboard: Surface tome doctor and tome status diagnostics in a native UI
  • Import/export: Import skills from folders or zip files; export skills for sharing
  • Tech stack: SwiftUI (macOS 15+), swift-markdown-ui for rendering, invokes tome CLI under the hood

Future Ideas

  • Plugin registry: Browse and install community skill packs (precursor to v0.7 Wolpertinger)
  • Conflict resolution UI: Interactive merge when skills collide
  • Shell completions: Shipped in v0.4.1 (#208)
  • Homebrew formula: brew install tome
  • Backup snapshots: Moved to v0.5 as git-backed backup (#94)
  • Token budget estimation: Show estimated token cost per skill per target tool in tome status output
  • Security audit command: tome audit to scan skills for prompt injection vectors, hidden unicode, and suspicious patterns
  • Portable memory extraction: Suggest MEMORY.md entries that could be promoted to reusable skills (tome suggest-skills)
  • Plugin output generation: Package the skill library as a distributable Claude plugin, Cursor plugin, etc.
  • Publish on crates.io: Make tome installable via cargo install tome from the crates.io registry
  • Improve doc comments for cargo doc: Module-level //! coverage is uneven across modules; no # Examples sections. Low priority polish.
  • Syntax highlighting in browse preview: Render SKILL.md with markdown/YAML syntax highlighting in the tome browse detail panel (e.g. via syntect or tree-sitter-highlight). Low priority polish.
  • Package/repo label for skills: Surface the plugin name (e.g. martinp7r/axiom-ios-skills) or git repo slug as a searchable package field in browse. Currently SkillProvenance.registry_id stores this for marketplace skills but it doesn’t reach the browse UI or fuzzy search. Would also enable “group by package” in browse.
  • tome relocate (#333): Shipped in v0.3.7
  • tome eject (#334): Shipped in v0.3.7
  • Library inside a parent git repo: Superseded by the “git repo scope” item in v0.5. Open design question: scope git to just skills, or broader ~/.tome/ home including hooks/commands/agents.
  • Plugin marketplace discovery (#309): Make tome skills discoverable in the Claude Code marketplace
  • Vercel skills.sh format compatibility (#304): Evaluate mapping tome lockfile to/from Vercel’s skills-lock.json for cross-ecosystem compatibility
  • Central library architecture (#306): Source skills should not be used directly — always go through the library as single source of truth
  • Skill-scribe extraction (#307): Extract format conversion into a standalone skill-scribe package. See also format transform pipeline (#57)

API Reference

The full Rust API documentation is generated by cargo doc and hosted alongside these docs.

View API Reference

Key public types

For a v1.0 GUI / library consumer, the most important types to know about:

  • SyncReport — return shape of the full sync() pipeline (reconcile → discover → consolidate → distribute → cleanup → save). Primary data source for any “what happened this sync” surface.
  • reconcile::ReconcileReport — outcome of one reconcile pass (Match / Drift / Vanished / Missing classifications plus edit-in-library user decisions).
  • marketplace::MarketplaceAdapter — pluggable trait for managed-skill install/update/availability. Two production implementations ship (ClaudeMarketplaceAdapter, GitAdapter); third-party adapters can implement the trait directly (sealing is tracked as a v1.0 follow-up in #518).
  • CleanupResult — bucket-by-bucket cleanup outcomes (removed-from-config / missing-from-disk / now-in-exclude-list). Accessors are read-only; the renderer owns the user-facing surface.
  • status::StatusReport + status::DirectoryStatustome status --json shape. Since v0.12.0, DirectoryStatus.role is the typed DirectoryRole enum (with the human-readable description in a separate role_description field).
  • doctor::DoctorReporttome doctor --json shape, including IssueCategory (Library / Directory / Config / Foreign-symlink) groupings.

The architecture narrative — how these types fit together, why the library is canonical, how reconcile interacts with the manifest and lockfile — lives in Architecture.

Newtypes

Domain-specific wrappers with validation at construction: