# upskill — Specification
> Upskill your coding agents.
**Status**: v0.2 — shipped in v0.2.0.
## 1. Overview
`upskill` is a single-binary CLI that lets authors **create, manage, and
prolong** AI-assistance content — rules, skills, agents — across multiple AI
coding clients (Claude Code, GitHub Copilot, opencode) from a single source of
truth.
The central abstraction is **generation**: the SSOT (Single Source of Truth)
on disk is portable; per-client output is produced on demand by a generation
pipeline.
### 1.1 Three item kinds
| Rule | Always-on behavioural guidance. | `RULE.md` |
| Skill | On-demand procedural content. | `SKILL.md` |
| Agent | Persona definition with tool scope. | `AGENT.md` |
The on-disk contract is defined by the [portable format spec](./format-spec.md).
This document describes upskill's behaviour against that contract.
### 1.2 Two scopes
| Project | `<cwd>/.agents/` | Content shared across the team via git. |
| Global | `$HOME/.agents/` | Content available to a single user, all repos. |
### 1.3 Two roles
- **Source registry** — repository where SSOT items are authored. Holds the
canonical `RULE.md` / `SKILL.md` / `AGENT.md` files. Author commands
(`new`, `lint`, `fmt`) operate here.
- **Consumer project** — repository where generated outputs are installed
for use by AI clients. Holds only generated per-client files. Consumer
commands (`add`, `remove`, `update`, `list`, `doctor`, `search`) operate
here.
The same repository can be both, but SSOT and generated outputs do not mix
in the same tree.
## 2. CLI surface
No overlap between verbs.
| `add` | Consumer | Install content from any source. |
| `remove` | Consumer | Remove installed content. |
| `update` | Consumer | Pull latest, regenerate changed items. |
| `list` | Consumer | Show installed content from the lock file. |
| `doctor` | Consumer | Verify installation consistency. |
| `search` | Consumer | Look up skills via the public registry. |
| `new` | Author | Scaffold a new rule, skill, or agent. |
| `lint` | Author | Validate SSOT files against the format spec. |
| `fmt` | Author | Canonicalise YAML frontmatter (key order, quoting). |
### 2.1 `add`
```text
upskill add <source> [items...] [--global|--project]
```
`<source>` accepts (parity with `npx skills add`):
- `owner/repo` — GitHub shorthand
- `owner/repo@ref` — pinned ref (branch, tag, or commit SHA)
- `owner/repo:path/to/item` — subfolder
- `owner/repo@ref:path` — combined
- `https://github.com/owner/repo[...]` — full HTTPS URL
- `gitlab:owner/repo[...]` or `https://gitlab.com/[...]` — GitLab
- `./path`, `../path`, `/abs/path`, `~/path` — local paths
- `<bundle-name>` — a bundle resolved from configured registries
Source resolution order: registry index first; if no match, treat as git repo
or local path.
`upskill add <source>` installs everything the source contains. Optional
`items...` after the source filter to a subset. There is no interactive
picker; install-all-by-default fits bundle curation.
Default scope is `--project` (write into the current repo's `.agents/...`),
falling back to `--global` (`$HOME/.agents/...`) when the current directory
is not inside a git repo. Either flag can be passed explicitly.
### 2.2 `update`
```text
upskill update [name...] [--global|--project] [--dry-run]
```
Always fetches the latest source before regenerating. Absorbs the role of a
separate `sync` command — there is no `--offline` flag in v1; offline use is
covered by passing local paths to `add` / `update`.
`update` covers two distinct behaviours:
- **Absorb client drift.** When Copilot or Claude Code change a path or
frontmatter field, `update` regenerates outputs from the same SSOT.
- **Track upstream evolution.** `update` git-pulls source repos and
regenerates items whose source hash changed.
### 2.3 `remove`
```text
upskill remove [name...] [--source <label>] [--global]
```
Removes installed items from the matching scope. Either name one or more
items, or pass `--source <label>` to remove every item that came from a
single source. Bare `upskill remove` is rejected — be explicit. Ancillary
files (`CLAUDE.md`, `opencode.json`, `.vscode/settings.json`) are not
touched.
### 2.4 `list` / `doctor`
- `list` — show installed content from `.upskill-lock.json`, grouped by
kind. Bundles are surfaced in a separate section. The `--available`
view (items discoverable from configured sources) is deferred for a
future release.
- `doctor` — verify on-disk state matches the lock file. Three
independent buckets: missing per-client outputs (reinstall fixes),
SSOT hash drift on `local:` sources (`update` fixes), and lockfile
entries with no recoverable source (`remove` fixes). Exit 0 when
clean, 1 when any drift is found.
### 2.5 `new` / `lint` / `fmt`
Author commands. Run inside a source-registry working tree.
- `new <kind> <name>` — scaffold a new SSOT item directory (`<kind>` is one
of `rule`, `skill`, `agent`).
- `lint [paths...] [--strict]` — validate SSOT files. `--strict` is the CI
mode (warnings become errors).
- `fmt [paths...]` — canonicalise YAML frontmatter only. Markdown body
formatting is dprint's responsibility.
### 2.6 Aliases
`add` does **not** alias to `install`. `remove` does **not** alias to
`uninstall`. The unified verb names are canonical.
## 3. Generation pipeline
SSOT → per-client output runs on the developer's machine, on every install
or update. No CI generation step. No committed `dist/` artifacts.
### 3.1 Per-client output paths
| Rule | `.claude/rules/<name>.md` with `paths:` | `.github/instructions/<name>.instructions.md` with `applyTo:` | `.agents/rules/<name>/RULE.md` (canonical-store) |
| Skill | `.claude/skills/<name>/SKILL.md` | `.github/skills/<name>/SKILL.md` | `.agents/skills/<name>/SKILL.md` (canonical-store) |
| Agent | `.claude/agents/<name>.md` | `.github/agents/<name>.agent.md` | `.opencode/agents/<name>.md` |
Per-client frontmatter mapping is defined in [format-spec §7](./format-spec.md#7-generation-client-specific-output)
and Appendix B.
### 3.2 Copy, not symlink
All installation is file copy or generated output. No symlinks anywhere. One
code path; Windows portability without Developer Mode or
`core.symlinks=true`.
### 3.3 Markdown formatting
Generated output passes through embedded `dprint-plugin-markdown`
(exact-pinned at `=0.21.1`) before writing. This guarantees idempotence under
the same formatter in `dprint.json`. Authors who run `dprint` locally see no
diffs on generated files.
### 3.4 Ancillary file handling
Three files outside the per-item path tree are managed idempotently:
- **`CLAUDE.md`** at repo root — created once with `@AGENTS.md` content if
absent. Never overwritten.
- **`.vscode/settings.json`** — `chat.instructionsFilesLocations` is set;
other keys preserved.
- **`opencode.json`** — managed only on **first** opencode-rule install.
The entry `".agents/rules/**/RULE.md"` is added to `instructions[]` if not
already present, then the file is never mutated again. opencode's glob
picks up subsequent rule additions and removals automatically.
## 4. State files
State is split between two files, both schema-versioned (`schema: 1`).
### 4.1 `.upskill-lock.json` (per-project, committed)
Lives at the consumer-project root. Records what was installed, from where,
at what resolved git ref, with what content hashes. Plays the same role as
`package-lock.json`: deterministic regeneration on another developer's
machine and in CI.
```json
{
"schema": 1,
"items": [
{
"kind": "skill",
"name": "code-review",
"source": "github:driftsys/skills",
"ref": "v1.2.0",
"hash": "sha256:..."
}
],
"bundles": [
{
"name": "platform-defaults",
"source": "github:driftsys/bundles",
"ref": "v0.4.0",
"items": ["code-review", "secret-scanner"]
}
]
}
```
### 4.2 `~/.upskill/installed.json` (per-user)
Tracks the user-global view: items installed at the global scope, drift
state for the global location, and source-registry caches. Not committed.
## 5. Source format and authentication
Source format: see [§2.1](#21-add).
Token resolution order:
| GitHub | `GITHUB_TOKEN` → `GH_TOKEN` → `gh auth token` → unauthenticated |
| GitLab | `GITLAB_TOKEN` → `GL_TOKEN` → `glab auth token` → unauthenticated |
Self-hosted GitLab is supported via full URL form
(`https://gitlab.mycompany.com/team/repo`).
## 6. CLI compliance
### 6.1 Exit codes
| `0` | Success. |
| `1` | General error (network, parse, I/O). |
| `2` | Usage error (invalid args or flags). |
| `130` | Interrupted (Ctrl+C / SIGINT). |
### 6.2 Conventions
- POSIX/GNU long flags (`--flag-name`) and short flags (`-f`).
- Stdout for data, stderr for progress and errors.
- Honors `NO_COLOR` and TTY detection (no color in pipes/CI).
- Single static binary, no runtime dependencies.
### 6.3 Environment variables
| `NO_COLOR` | Disable colored output. |
| `GITHUB_TOKEN` | Authenticate GitHub requests (private repos). |
| `GH_TOKEN` | Fallback for `GITHUB_TOKEN`. |
| `GITLAB_TOKEN` | Authenticate GitLab requests (private repos). |
| `GL_TOKEN` | Fallback for `GITLAB_TOKEN`. |
| `HTTPS_PROXY` | HTTP proxy for network requests. |
## 7. Implementation status
All phases shipped in v0.2.0.
| 0 | Tag, branch, deps, model skeleton. | Done. |
| 1 | SSOT parser + generation pipeline for skills × all 3 clients. | Done. |
| 2 | Pipeline extension to rules and agents. | Done. |
| 3 | `add` / `update` / `remove` over the pipeline; bundles; ancillary files. | Done. |
| 5 | `lint` + `fmt`. | Done. |
| 6 | `new` (scaffolding). | Done. |
| 7 | Polish + v0.2.0 release. | Done. |
## 8. Out of scope
- Runtime invocation of installed content (each AI client's responsibility).
- Hosting a registry server or central marketplace.
- Content quality review (delegated to AI workflows outside upskill).
- Telemetry, analytics, or usage tracking.
- Publishing to non-Git destinations (npm, crates.io, PyPI).
## 9. References
- Format spec: [`docs/format-spec.md`](./format-spec.md)
- User guide: [Getting started](./getting-started.md), [Commands](./commands.md), [Recipes](./recipes.md)
- Agent Skills open standard: <https://agentskills.io>
- skills.sh ecosystem: <https://skills.sh>