Portable Format for AI-Assistance Content
Version: 0.6.0 Status: Draft Date: 2026-05-13
Abstract
This specification defines a portable, tool-agnostic format for authoring rules, skills, and agents targeting AI coding assistants. It also defines a bundle manifest format for distributing curated sets of these items. The format is inspired by and compatible with the Agent Skills open standard (agentskills.io) and extends it to cover always-on rules and agent definitions, which the open standard does not address.
The specification intentionally leaves implementation details (installation mechanics, caching, registry protocols, CLI surface) to the implementing tool. The scope is the on-disk content contract: file layout, frontmatter schemas, body conventions, conditional directives, and per-client override mechanics.
Audience
Authors of AI-assistance content (rules, skills, agents) targeting multiple AI coding clients from a single source. Implementers of tools that consume this format to generate client-specific outputs. Maintainers of CI pipelines that validate content against this specification.
Conformance keywords
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119 when, and only when, they appear in all uppercase as shown.
Client-name conventions
This document uses Claude Code, GitHub Copilot, Codex and
opencode in prose. The lowercase source family identifiers claude,
copilot, codex and opencode appear in audience lists (§3.1) and
conditional content directives (§6). Historical passthrough blocks (§3.5) use
claude, copilot and opencode; the restricted Codex instruction producer
rejects native extension maps.
1. Terminology
Item: a rule, skill, or agent. The three kinds share a base schema and differ in semantic role and a small number of kind-specific fields.
Rule: always-on behavioral guidance, loaded into every session of compatible clients. Rules constrain behavior (“never do X”, “always check Y”).
Skill: on-demand procedural content, activated by name or by a client’s automatic discovery mechanism. Skills teach a procedure (“to accomplish X, follow these steps”).
Agent: a persona definition with its own system prompt and tool scope, invocable as a subagent or primary agent depending on the target client. Agents establish identity and role (“you are a security reviewer; when invoked, do X”).
Bundle: a manifest file grouping items (and optionally other bundles) by reference for distribution. Bundles contain no content themselves.
Client: a target AI coding assistant. This specification recognizes three initial clients: Claude Code, GitHub Copilot (VS Code + CLI + cloud agent), and opencode. Implementations MAY extend this list.
SSOT: single source of truth. The canonical form of an item, authored per this specification, from which client-specific outputs are derived.
Client-specific output: a file or configuration entry written for a particular client’s expected location, layout, and frontmatter conventions. Generated from the SSOT by an implementing tool.
Source registry: a git repository (or local path used as one) that holds SSOT items in their canonical form. Source registries are the only place SSOT items exist. Authors edit items in source registries; consumers fetch from them.
Consumer project: a developer’s working repository where they run
upskill add (or equivalent) to install items. Consumer projects only ever
contain generated, client-specific outputs — never SSOT items. The same applies
to a consumer’s global install location (~/.agents/ and per-client directories
under ~/.config/, ~/.claude/, etc.): generated content only.
2. File layout
2.1 Item directory structure
Each item is a directory containing at minimum one entrypoint file. Kind is determined by the entrypoint filename:
<item-root>/
├── <name-a>/
│ └── RULE.md
├── <name-b>/
│ └── SKILL.md
├── <name-c>/
│ └── AGENT.md
└── <name-d>/ # optional co-location: multiple kinds in one item directory
├── SKILL.md # name omitted or name: <name-d> (skill must match the directory)
└── RULE.md # name: <name-d> or a divergent name (rule/agent name MAY differ)
Constraints:
- An item directory MUST contain at least one entrypoint file (
RULE.md,SKILL.md, orAGENT.md). - An item directory MAY contain more than one entrypoint when the kinds
represent a tightly-coupled set for one capability (for example, a skill
paired with its enforcing rule, or an agent paired with a skill it preloads).
Co-location is optional; solo-kind item directories are the common case.
Co-location expresses a symmetric, inseparable unit; a directed “A needs
B” relationship is expressed with
requires(§3.7) instead. See ADR-0009. - Effective name and relaxed naming. Each entrypoint resolves to an
effective name:
- A skill
name:is optional. When present it MUST equal the directory name (the Agent Skills standard mandate). When absent, the skill’s effective name is the directory name. - A rule or agent
name:comes from frontmatter and MAY differ from the directory name. When absent, the effective name is the directory name. A divergent rule/agent name is legal and is not a diagnostic.
- A skill
- Identity is
(kind, effective-name)— layout-independent, never a path. A directory MAY hold independently-named kinds (for example, a skill named after the directory alongside a rule whose name diverges). The directory name is the co-location grouping key and the fallback default for any entrypoint that omitsname:. - The effective name MUST consist of lowercase letters (
a-z), digits (0-9), and hyphens (-). - The effective name MUST NOT start or end with a hyphen.
- The effective name MUST be at most 64 characters.
- Kind is determined by the entrypoint filename:
RULE.mdfor a rule,SKILL.mdfor a skill,AGENT.mdfor an agent. Implementations MUST NOT infer kind from any other source (parent directory name, frontmatter field, etc.). <item-root>is the SSOT path within a source registry repository. SSOT items live only in source registries; consumer projects (where developers runupskill add) only ever hold generated, client-specific outputs and never SSOT items.- Within a source registry,
<item-root>MAY be any path that fits the team’s organisation (content/,skills-src/,skills/, etc.). The upskill project recommendsskills/; see Conventions for the full recommended layout. Source registries SHOULD avoid.agents/as their<item-root>to prevent confusion with the consumer-side opencode canonical-store path (§7.3).
Agent Skills standard compatibility. A skill item directory
<item-root>/<name>/SKILL.md is a valid Agent Skills directory per
agentskills.io: the standard requires <dir>/SKILL.md with name matching the
parent directory and permits “any additional files or directories” in the skill
directory. A sibling RULE.md or AGENT.md is therefore standard-compliant
additional content and is ignored by agentskills.io-only tooling.
2.2 Bundle files
Bundles are flat YAML manifest files, not directories:
<bundle-root>/
├── platform-baseline.bundle.yaml
├── android.bundle.yaml
└── rust-embedded.bundle.yaml
- A bundle manifest is a pure YAML file (no
---delimiters, no markdown body). - The filename stem (before
.bundle.yaml) MUST match thenamefield in the manifest. - Bundle files MAY live anywhere within a source registry — alongside item
directories under
<item-root>(§2.1), in a sibling directory, or in a dedicatedbundles/directory. Implementations discover bundles by scanning for the.bundle.yamlsuffix and MUST NOT depend on a specific bundle-root path. A YAML file matching the suffix but lacking a top-level integerschema:key is silently skipped in discovery (the schema field is the bundle contract gate); explicit-path operations against such a file MUST surface a parse error. The upskill project recommends placing bundles alongside item directories underskills/; see Conventions. - A bundle MAY have an optional sibling Markdown file
<name>.bundle.mdcarrying human-readable documentation (install examples, adoption path, caveats). Implementations ignore<name>.bundle.md; it exists for humans browsing the registry. See ADR-0007.
2.3 Per-client override files
For the three historical generation clients (claude, copilot, opencode),
any entrypoint MAY have optional per-client body overrides alongside it in the
same item directory:
<name>/
├── RULE.md # canonical, always required
├── RULE.claude.md # optional — Claude Code body override
├── RULE.copilot.md # optional — GitHub Copilot body override
└── RULE.opencode.md # optional — opencode body override
The same pattern applies to skills (SKILL.claude.md, etc.) and agents
(AGENT.claude.md, etc.). When an item directory holds multiple kinds (§2.1),
each kind has its own set of override files keyed by entrypoint name
(RULE.claude.md, SKILL.claude.md, AGENT.claude.md); they coexist in the
same directory without ambiguity.
Override file rules:
- Override files contain body content only; they MUST NOT include YAML frontmatter.
- Frontmatter is always taken from the canonical entrypoint.
- The
<client>suffix MUST be one ofclaude,copilotoropencode. The four audience/directive family identifiers in §3.1 and §6.1 do not extend this override vocabulary.codexis not a supported override suffix; the bounded Codex instruction producer rejects rule overrides.
Current implementation status. The resolution order below is the specified
format contract, not current installer behavior. The historical installer
renders the canonical body and does not apply override bodies. The captured
compiler rejects an active matching override, including one hidden by ignore;
it does not fall back to canonical content for that requested client. See
AD-0025
and debt #324. Override
execution requires a separate implementation and compatibility tests.
Specified body-resolution order for a given client. For historical
generation client C (claude, copilot or opencode), a conforming override
implementation would resolve the body as follows:
- If
<KIND>.<C>.mdexists, use its body verbatim. Directives in the canonical body are NOT processed for clientC. - Otherwise, use the canonical body with
<!-- @client: -->directives (§6) processed for clientC.
An override replaces the whole canonical body; it does not append to or compose with it. §6.4 cross-references this specified rule and its implementation status; this section is the authoritative statement.
Sharing a procedure across client-specific orchestration
For small differences, keep the common procedure in SKILL.md and surround only
the client-specific orchestration instructions with inline client directives
(§6). These directives are processed during upskill generation; a native client
reading the source directory directly does not perform that preprocessing.
For large differences, a candidate authoring pattern puts the common procedure in a supporting resource and gives each client wrapper an explicit instruction to read it before using that client’s agent mechanism:
review/
├── SKILL.md
├── SKILL.claude.md
├── SKILL.copilot.md
└── references/
├── review-procedure.md
├── code-reviewer.md
└── test-reviewer.md
For example, each wrapper can say: “Read references/review-procedure.md and
follow its review criteria. Launch two reviewers using this client’s agent
mechanism.” The example illustrates the specified override format; the current
installer/compiler boundaries above still apply. The canonical body must also be
complete for clients without an override. There is no SKILL.codex.md override
suffix.
This shares the procedure through runtime file loading. Supporting resources are copied alongside each generated entrypoint (§2.4); their contents are not embedded into the body or processed for client directives. Embedding a common core would require a separately specified composition/include feature. Neither an override nor a resource reference supplies that feature.
Shared authoring is distinct from shared native discovery. Keep this variant
family outside scanned shared catalogs; expose each logical skill once per
client through a qualified route. A directly discovered .agents/skills entry
must already be consumption-ready. Runtime resource loading must be qualified
for relative paths, permissions, missing files and package relocation. A read
instruction does not register subagents, grant tool permissions or configure MCP
servers. See the
delivery contract.
2.4 Supporting resources
Items MAY contain supporting files in their directory:
<name>/
├── SKILL.md
├── templates/
│ └── handler.ts.tmpl
├── scripts/
│ └── validate.sh
└── references/
└── patterns.md
- Supporting files are referenced from the entrypoint body using standard markdown relative links.
- Implementations MUST preserve supporting files and their directory structure during generation (copy them alongside the generated entrypoint into the client-expected location).
- An item MAY declare an
ignorelist (§3.1) to scope which supporting files are copied. The list is.gitignore-style and subtractive only — there is noinclude/allowlist form. A file matching anyignorepattern is not copied into the client-expected location.ignoreis author-declared and is stripped from generated output. - When an item directory holds multiple kinds (§2.1), supporting resources are shared: any entrypoint in the directory MAY reference them via relative links, and implementations MUST copy them alongside each generated entrypoint that references them.
3. Frontmatter schema
Item entrypoint files (RULE.md, SKILL.md, AGENT.md) begin with YAML frontmatter
delimited by --- markers, followed by a blank line and then markdown body
content. Bundle manifests (*.bundle.yaml, §2.2) are pure YAML — the same
schema, but the file content IS the YAML, with no --- wrapping and no body.
3.1 Common fields (all kinds and bundles)
| Field | Type | Required | Description |
|---|---|---|---|
schema | integer | YES | Protocol version of this specification. Current: 1. |
name | string | varies | Stable identifier. Required for bundles; optional for items (effective name defaults to the directory name). See §2.1. |
description | string | YES | What this item does and when to use it. Max 1024 characters. Skill-specific guidance: see §3.3. |
license | string | no | SPDX identifier or custom (proprietary, LicenseRef-*). |
audience | string[] | no | Target families (claude, copilot, codex, opencode). When present, output is limited to listed families and supported selected surfaces. When absent, all supported selected families are targeted. |
metadata | map | no | Free-form governance block. See §3.6. |
requires | map | no | Directed item dependencies (rules/skills/agents). See §3.7. Stripped from output. |
ignore | string[] | no | .gitignore-style subtractive copy-scope (§2.4). Stripped from output. |
Example:
---
schema: 1
name: license-awareness
description: Use when reviewing third-party code, AI suggestions, or external snippets to check licensing compatibility and avoid incorporating incompatible licensed code
license: proprietary
metadata:
version: "1.0.0"
author: platform-security
---
Field semantics:
schema: implementations MUST reject files withschemagreater than the highest version they support and SHOULD report a clear upgrade message. See §8 for evolution rules.name: the effective name (§2.1) is used as the/slash-commandname in clients that support it and as the identifier in bundle manifests. Whennameis omitted on an item, the directory name is the effective name; a rule or agentnameMAY differ from its directory.description: for skills, this is the activation hint — the model reads it during discovery and decides whether to load the full body. For rules and agents, it serves as documentation. Front-load the key use case. Convention: start with “Use when…” for skills and agents.license: if the item might be shared outside the authoring organization, SHOULD be populated.audience: the canonical mechanism for restricting an item to a subset of clients. Conditional body directives (§6) limit body content per client; passthrough blocks (§3.5) emit client-specific frontmatter;audienceis the only mechanism that filters whether the item is generated at all. The three are complementary: useaudienceto scope the item, directives to scope body content within an emitted item, and passthrough blocks to add client-specific frontmatter to an emitted item.metadata: see §3.6. Implementations MUST preserve unknown keys withinmetadata.requires: directed prerequisite items. Eachrequires.<kind>entry is either a bare name (same source, resolved by(kind, name)) or a{ name, source }map for a cross-source dependency, wheresourcereuses theupskill addsource DSL. See §3.7 for resolution, cycle, and conflict rules.ignore:.gitignore-style subtractive copy-scope (§2.4). Bothrequiresandignoreare SSOT-only authoring fields and are stripped from every client output (the same treatment asschema,metadata, andlicense).
3.2 Rule-specific fields
Rules MAY include path-scoping:
scope:
paths:
- "src/api/**/*.ts"
- "src/handlers/**/*.ts"
| Field | Type | Required | Description |
|---|---|---|---|
scope | map | no | Scoping constraints. |
scope.paths | string[] | no | Glob patterns. If absent or empty, rule is always-on. |
When scope.paths is present, implementations:
- SHOULD emit the rule with path-scoping for clients that support it (Claude
Code’s
paths:array, GitHub Copilot’sapplyTo:comma-joined string). - SHOULD emit the rule unconditionally for clients that lack path-scoping (opencode), accepting the token-cost trade-off.
3.3 Skill-specific fields
No additional required fields beyond §3.1.
Skill description length. For skills, description is the activation hint:
discovering clients read it on every selection decision and the text contributes
to runtime context cost. The §3.1 1024-character cap MUST be respected, but
skill authors SHOULD aim for ~200 characters or fewer. This is tighter than
rules and agents, where description serves only as documentation.
The Agent Skills open standard defines optional fields (allowed-tools,
when_to_use, disable-model-invocation, context, agent, argument-hint,
paths, etc.) which implementations MAY pass through to client outputs when
generating for clients that support them. This specification does not redefine
those fields; see agentskills.io/specification for their semantics.
Implementations SHOULD allow unknown top-level fields in skill frontmatter to enable pass-through of client-specific or standard-defined fields.
3.4 Agent-specific fields
mode: subagent
model: sonnet
tools:
- read
- grep
- glob
- bash
preload-skills:
- security-baseline
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
mode | string | no | subagent | primary, subagent, or all. Primary consumer is opencode; see notes below. |
model | string | no | sonnet | Short alias. Spec-guaranteed aliases listed below. |
tools | string[] | no | client-supported §4 | Capability-level tool names. See §4. Default semantics defined below. |
preload-skills | string[] | no | (none) | Skill names to load at agent startup. Primary consumer is Claude Code; see notes below. |
mode is opencode-primary. opencode’s agent definition has a real top-level
mode: field with subagent / primary / all semantics that change agent
selection behavior. Claude Code derives mode from file location
(.claude/agents/ is always subagent); GitHub Copilot has no equivalent
concept. Implementations MUST emit mode: for opencode; they MAY omit it for
other clients. Authors who need opencode-specific mode behavior with no analog
elsewhere SHOULD prefer opencode.mode in the passthrough block (§3.5) over a
top-level mode: field, to make the intent explicit.
preload-skills is Claude-Code-primary. Claude Code emits this list as
skills: in agent frontmatter so the named skills are loaded at agent startup.
GitHub Copilot and opencode have no equivalent frontmatter field.
Implementations MUST emit skills: for Claude Code; for GitHub Copilot and
opencode, implementations SHOULD render the preloaded skills as a prose
## Skills section appended to the agent body (no frontmatter field), so the
agent is informed which skills it relies on.
preload-skills implies requires.skills (soft). Listing a skill in
preload-skills implies requires.skills for skills present in the same
source: those skills are auto-installed alongside the agent. A preloaded skill
absent from the same source is treated as a runtime hint and is neither
auto-installed nor an error (this differs from an explicit requires entry,
which errors when missing).
model aliases. This specification guarantees the following short aliases:
| Alias | Family |
|---|---|
sonnet | Anthropic Claude Sonnet (latest) |
opus | Anthropic Claude Opus (latest) |
haiku | Anthropic Claude Haiku (latest) |
Implementations MUST resolve these aliases to a current client-appropriate model
identifier. Implementations MAY accept additional aliases or pass through
fully-qualified model identifiers (anthropic/claude-sonnet-4-6,
openai/gpt-5, etc.); these are not guaranteed portable across implementations.
tools default. When tools: is absent, implementations MUST emit, for
each target client, the set of §4 capabilities documented as supported by that
client (the non-— cells in the §4 table). When tools: is present, only the
listed capabilities are emitted, subject to the §4 generation rules (capability
dropped with a warning when the target client has no mapping).
Implementations map these fields to each client’s agent definition format:
- Claude Code:
.claude/agents/<name>.mdwithtools:as capitalized names,model:as literal,skills:frompreload-skills.modeis implicit by file location and not emitted. - GitHub Copilot:
.github/agents/<name>.agent.mdwithtools:filtered per §4 mapping;modeomitted;preload-skillsrendered as a prose## Skillssection in the body (no frontmatter field). - opencode:
.opencode/agents/<name>.mdwithmode:,permission:map (per §7.3), and passthrough fields;preload-skillsrendered as a prose## Skillssection in the body (no frontmatter field).
3.5 Client-specific passthrough blocks
Items MAY include top-level blocks named after specific clients for fields that have no neutral equivalent:
copilot:
excludeAgent: code-review
opencode:
temperature: 0.2
color: "#8b5cf6"
Rules:
- Implementations MUST merge these blocks into the client-specific output only for the matching client.
- Implementations MUST NOT emit passthrough blocks for non-matching clients.
- Implementations MUST NOT validate the contents of passthrough blocks (they are opaque to the spec).
- Known passthrough block names:
claude,copilot,opencode. Additional names follow the same pattern for new clients.
3.6 Metadata block
The metadata field is a free-form map for governance, versioning, and
implementation-specific extensions.
Recommended conventions (not required by this specification):
| Key | Type | Convention |
|---|---|---|
version | string | Semver, quoted (e.g., "1.0.0"). MUST be quoted to avoid YAML float coercion. |
author | string | Team or individual identifier. |
updated | string | ISO 8601 date of last content change. |
Audience targeting is a top-level field with spec-defined semantics; see
audience in §3.1.
Implementations:
- MUST preserve all keys within
metadataduring processing. - MUST NOT require any specific key within
metadatabeyond what the implementation itself needs. - SHOULD validate
versionas semver when present.
3.7 Bundle schema
Bundles are source-registry artifacts. They reference items by name; the
items themselves live in source registries (potentially the same repo,
potentially others). Consumer projects do not contain bundle files — an
implementation records which bundles a consumer project has installed in its own
state file (e.g. .upskill-lock.json for the upskill CLI).
<bundle-root>/platform-baseline.bundle.yaml:
schema: 1
name: platform-baseline
description: Baseline rules, skills, and agents for all repositories
license: proprietary
items:
rules:
- license-awareness
- security-baseline
- commit-style
skills:
- pr-summary
agents:
- security-reviewer
requires: []
plugins:
superpowers:
claude:
source: anthropics/claude-plugins
marketplace: claude-plugins
plugin: superpowers
metadata:
version: "1.2.0"
author: platform-dx
An optional sibling <bundle-root>/platform-baseline.bundle.md MAY carry
human-readable documentation; the parser ignores it (§2.2).
| Field | Type | Required | Description |
|---|---|---|---|
schema | integer | YES | Same as §3.1. |
name | string | YES | Same as §3.1. Matches filename stem. |
description | string | YES | Same as §3.1. |
license | string | no | Same as §3.1. |
items | map | YES | What this bundle provides. |
items.rules | string[] | no | Rule names. |
items.skills | string[] | no | Skill names. |
items.agents | string[] | no | Agent names. |
requires | array | no | Bundle dependencies. See below. |
plugins | map | no | Client-native plugins. See below. |
mcps | map | no | MCP server descriptors. See below. |
metadata | map | no | Same as §3.6. |
requires entries are maps. Each entry references another bundle by name,
optionally pinned with a semver version constraint, and optionally located in
another source with source (the upskill add source DSL — owner/repo@ref,
https URLs, local paths). Absent source resolves the required bundle in the
same source registry as the requiring bundle:
requires:
- { name: "platform-baseline", version: "^1.0.0" } # same source
- { name: "rust-baseline" } # same source, any version
- { name: "markspec-core", source: "driftsys/markspec@v0.8.0" } # cross-source
The single-form (map-only) shape avoids the polymorphic-string-or-map
alternative until that flexibility has a documented author need. source
mirrors the item-level requires.<kind> cross-source locator (§3.7, ADR-0009).
Implementations:
- MUST resolve
items.*entries against configured item sources by matching thenamefield. - MUST report unresolved item names as errors.
- MUST resolve
requirestransitively: installing a bundle implies installing every bundle in its transitiverequiresclosure, then unioning the items contributed by all reached bundles. Item-level conflicts (the same item name resolving to different sources or versions) MUST be reported as errors. - MUST resolve a
{ name, source }entry by fetching that source (each distinct source fetched once per resolution) and locating the named bundle in it; the closure MAY span sources. Each bundle and each item is recorded in the lockfile under its own source. The same bundle name resolving to two different sources within one closure MUST be reported as an error. - MUST reject circular
requireschains as errors (cycle detection keyed by source and name).
plugins map
The optional plugins map declares client-native plugins that accompany this
bundle. Unlike rules, skills, and agents (which are SSOT content rendered
per-client by the generation pipeline), plugins are installed via each client’s
native CLI. See
ADR-0008
for design rationale.
Each entry in the plugins map is keyed by an upskill-level plugin name (used
in the lockfile, CLI output, and upskill remove). The value is a map of
per-client install descriptors:
plugins:
superpowers:
claude:
source: anthropics/claude-plugins
marketplace: claude-plugins
plugin: superpowers
install_url: https://github.com/obra/superpowers#install
copilot:
source: obra/superpowers-marketplace
marketplace: superpowers-marketplace
plugin: superpowers
install_url: https://github.com/obra/superpowers#install
vscode:
extension: anthropic.superpowers
install_url: https://marketplace.visualstudio.com/items?itemName=anthropic.superpowers
opencode:
module: superpowers-opencode
install_url: https://opencode.ai/plugins/superpowers
Per-client descriptor fields:
| Client | Field | Type | Required | Description |
|---|---|---|---|---|
claude | source | string | YES | Marketplace source (passed to claude plugin marketplace add): owner/repo, URL, or path. |
claude | marketplace | string | YES | Marketplace name for the install ref <plugin>@<marketplace>. Distinct from source (see below). |
claude | plugin | string | YES | Plugin identifier (passed to claude plugin install). |
claude | install_url | string | no | URL shown in warn-skip message when CLI not found. |
copilot | source | string | YES | Marketplace source (passed to copilot plugin marketplace add). |
copilot | marketplace | string | YES | Marketplace name for the install ref <plugin>@<marketplace>. Distinct from source. |
copilot | plugin | string | YES | Plugin identifier (passed to copilot plugin install). |
copilot | install_url | string | no | URL shown in warn-skip message when CLI not found. |
vscode | extension | string | YES | Extension ID (passed to code --install-extension). |
vscode | install_url | string | no | URL shown in warn-skip message when CLI not found. |
opencode | module | string | YES | Module name (passed to opencode plugin). |
opencode | install_url | string | no | URL shown in warn-skip message when CLI not found. |
source and marketplace are distinct and both required for the
claude/copilot auto-install form. source is what marketplace add
consumes (owner/repo, a URL, or a path); marketplace is the registered
marketplace name the CLI requires in the install ref
<plugin>@<marketplace>. The CLI derives the name from the marketplace’s
manifest, so it generally differs from source — e.g. source
anthropics/claude-plugins registers a marketplace named claude-plugins, and
the plugin installs as superpowers@claude-plugins. Installing with
<plugin>@<source> fails (Plugin not found in marketplace <source>), so the
name MUST be supplied explicitly rather than inferred from source.
A plugin entry MAY declare any subset of client blocks. A plugin that only
exists for Claude Code carries only a claude: block; clients without a
matching block skip installation silently.
Additional descriptor forms:
A client block MAY use one of these alternative forms instead of the standard auto-install form. The mode is determined by which required field is present:
| Mode | Discriminating fields | Available for | Behavior |
|---|---|---|---|
| Auto-install | source+marketplace+plugin / extension / module | all clients | Shell out to client CLI |
| Instructions-only | instructions_url | all clients | Print info notice; no automated action |
| Config-write | plugin_uri | opencode only | Write URI to project opencode.json |
Instructions-only form:
plugins:
example:
opencode:
instructions_url: https://example.com/install-docs
summary: |
Add "example@git+https://example.com/repo.git" to the
plugin[] array in opencode.json, then restart opencode.
| Field | Type | Required | Description |
|---|---|---|---|
instructions_url | string | YES | URL to manual install documentation. |
summary | string | no | Short text (1-5 lines) printed during install. |
Config-write form (opencode only):
plugins:
example:
opencode:
plugin_uri: "example@git+https://example.com/repo.git"
install_url: https://example.com/install-docs
| Field | Type | Required | Description |
|---|---|---|---|
plugin_uri | string | YES | Plugin URI appended to opencode.json plugin[] array. |
install_url | string | no | URL shown in output for reference. |
Disambiguation: serde attempts variants in declaration order (Install first). If
both an auto-install field and an instructions-only field are present, the
auto-install form takes priority. upskill lint SHOULD warn about ambiguous
descriptors.
Install behavior:
| Client | CLI commands |
|---|---|
| claude | claude plugin marketplace add <source>, then claude plugin install <plugin>@<marketplace> --scope <scope> |
| copilot | copilot plugin marketplace add <source>, then copilot plugin install <plugin>@<marketplace> |
| vscode | code --install-extension <extension> |
| opencode | opencode plugin <module> |
Claude’s --scope derives from upskill’s project/global context:
| upskill scope | claude --scope |
|---|---|
| project | project |
| global | user |
CLI-missing policy (warn-skip): If the target client’s CLI is not on PATH,
the implementation MUST print a warning and continue installing the rest of the
bundle. If install_url is present, it MUST be included in the warning message.
The rules, skills, and agents portion of the bundle MUST install regardless of
plugin installation success.
Lockfile recording: Each installed or warn-skipped plugin MUST be recorded
in the lockfile with its client, identifier, scope, and install status so that
remove, update, and doctor can reconcile state. The status field
distinguishes outcomes:
status | Meaning |
|---|---|
"installed" | Plugin successfully installed via the client CLI or config-write. |
"skipped" | Client CLI was not on PATH at install time (warn-skip). |
"instructions" | Instructions-only — no automated install; consumer must follow manual steps. |
Plugins that fail for other reasons (non-zero exit from a present CLI) MUST NOT be recorded — these are transient errors and should not pollute the lockfile.
Lockfile plugin entry shape:
{
"name": "superpowers",
"client": "claude",
"identifier": "superpowers@anthropics/claude-plugins",
"scope": "project",
"bundle": "baseline",
"status": "installed"
}
The status field defaults to "installed" when absent (backward-compatible
with pre-v0.6.x lockfiles that lacked this field).
Implementations:
- MUST shell out to the native client CLI for auto-install plugin descriptors —
MUST NOT manipulate client config files directly, except for the opencode
config-write mode which writes to project-scope
opencode.jsonper the documented plugin registration path. - MUST treat plugin install as idempotent (re-running on an already-installed plugin is a no-op).
- MUST use warn-skip when the target client CLI is not found
(
ErrorKind::NotFound). - MUST NOT fail the entire bundle install when a single plugin install fails or is skipped.
mcps map
The optional mcps map declares MCP (Model Context Protocol) servers that
accompany this bundle. Configuring an MCP server requires no content generation
— instead, upskill writes the server into each targeted client’s MCP
configuration at install time. See
ADR-0010
for design rationale.
Each entry in the mcps map is keyed by an upskill-level MCP name (used in the
lockfile, CLI output, and upskill remove-mcp). The value carries a transport
descriptor and an optional list of declared environment variables:
mcps:
drawio: # remote server over HTTP
remote:
type: http # http | sse
url: https://mcp.draw.io/mcp
headers: # optional; values MUST use ${VAR} references for secrets
Authorization: "Bearer ${DRAWIO_TOKEN}"
requires-env: [DRAWIO_TOKEN]
my-tool: # local server spawned over stdio
local:
command: npx
args: ["-y", "my-tool-mcp"]
env: # optional; values MUST use ${VAR} references for secrets
API_KEY: "${MY_TOOL_API_KEY}"
requires-env: [MY_TOOL_API_KEY]
Exactly one of remote: or local: MUST be present per entry; both or neither
is a validation error. remote.type MUST be one of http or sse.
remote.url and local.command MUST NOT be empty.
Transport descriptor fields:
| Transport | Field | Type | Required | Description |
|---|---|---|---|---|
remote | type | string | YES | Protocol: http or sse. |
remote | url | string | YES | Endpoint URL the client connects to. |
remote | headers | map | no | HTTP headers. Values pass through verbatim (use ${VAR}). |
local | command | string | YES | Launcher command (e.g. npx, uvx, docker, bare binary). |
local | args | string[] | no | Arguments passed to the command. |
local | env | map | no | Environment variables. Values pass through verbatim. |
| (either) | requires-env | string[] | no | Env var names the server requires (declared, not valued). |
Secrets and ${VAR} indirection. Values in headers and env that
reference secrets MUST use ${VAR} syntax only. Implementations MUST pass these
values through verbatim to the client config — they MUST NOT expand ${VAR}
references, read the underlying values, or store literal secret values anywhere
(not in config files, not in the lockfile, not in SSOT). The requires-env list
declares which variable names are needed; implementations SHOULD warn at install
time (e.g., during upskill add) when a declared variable is unset in the
current environment, without reading its value.
Per-client install behavior: A bundle’s mcps: entry is configured for
every MCP target — Claude Code, GitHub Copilot, VS Code, and opencode —
CLI-first with a config-write fallback per target. VS Code is an MCP target but
not a generation client: it shares .github/** rules/skills/agents output
with Copilot and forks only on MCP, so the MCP target set (claude,
copilot, vscode, opencode) is modelled separately from the generation
Client enum (claude, copilot, opencode).
| Target | Primary (CLI) | Fallback config file | Root key |
|---|---|---|---|
| Claude Code | claude mcp add <name> --scope <scope> [-e K=${V}] -- <cmd> [args] (local) / claude mcp add --transport <http|sse> <name> <url> --scope <scope> [-H …] (remote) | .mcp.json | mcpServers |
| GitHub Copilot | copilot mcp add … (no --scope) | ~/.copilot/mcp-config.json (user scope) | mcpServers |
| VS Code | code --add-mcp '<json>' | .vscode/mcp.json | servers |
| opencode | — (no mcp add verb; config-only) | opencode.json | mcp |
Per-target config shapes differ:
- Claude Code / GitHub Copilot —
{ command, args?, env? }(local) or{ type, url, headers? }(remote). - VS Code — local servers are typed
"stdio"; remote servers carry theirhttp/ssevalue intype, plusurlandheaders. The root key isservers, notmcpServers. - opencode —
typeis"local"or"remote". For local serverscommandis a single array combining the command and its args (["npx", "-y", "my-tool-mcp"]) and the env map is namedenvironment(notenv). Remote servers carryurlandheaders. - GitHub Copilot scope caveat — Copilot has no documented project-scope MCP
config file (
.github/mcp.jsonis not a Copilot CLI config source). The config-write fallback therefore always targets the user-scope~/.copilot/mcp-config.json; project scope is configured via thecopilot mcp addCLI or warn-skipped.
Claude’s --scope derives from upskill’s project/global context:
| upskill scope | claude --scope |
|---|---|
| project | project |
| global | user |
CLI-missing policy (warn-skip): If the target client CLI is not on PATH, the implementation MUST fall back to writing the client config file. If the config-write also fails, it MUST print a warning and continue installing the rest of the bundle. The rules, skills, and agents portion of the bundle MUST install regardless of MCP configuration success.
Lockfile recording: Each configured or warn-skipped MCP server MUST be recorded in the lockfile with its client, scope, bundle, and status:
status | Meaning |
|---|---|
"installed" | Configured successfully via CLI shellout or config-write. |
"skipped" | Client CLI was not on PATH and no config-write target applied (warn-skip). |
Lockfile MCP entry shape:
{
"name": "drawio",
"client": "claude",
"scope": "project",
"bundle": "drawio-diagrams",
"status": "installed"
}
The mcps array is additive. Implementations that do not support this field
MUST ignore it (serde default). The lockfile schema field stays at 1 — no
version bump.
Removal: upskill remove-mcp <name> unregisters the named server from every
target it was configured for and drops the matching LockedMcp entries from the
lockfile. Targets with a native remove verb (Claude claude mcp remove, Copilot
copilot mcp remove) are CLI-first, falling back to deleting the entry from the
target’s config file when the CLI is absent; VS Code and opencode are
config-file only.
Reconciliation: upskill doctor reconciles each lockfile MCP entry against
its target. Claude is queried via claude mcp list; the config-write targets
(Copilot, VS Code, opencode) are checked against their config file. A server
present in the lockfile but absent from a present config file (or from the
Claude client) is reported as NotRegistered and causes doctor to exit
non-clean (exit 1). Claude also reports CliNotFound when the claude CLI is
absent and QueryFailed when the list query fails; a config-write target whose
config file is absent reports ConfigMissing and one that is unreadable reports
ConfigUnreadable — both are informational “cannot verify” states that are
surfaced but do not fail cleanliness (no false-positive exit 1), since the
server may be configured via the client’s own CLI. Doctor does not check
requires-env variables — the unset-env warning is emitted by upskill add at
install time.
Implementations:
- MUST prefer the client’s native MCP CLI verb over direct config-file writes.
- MUST fall back to writing the client config file when the CLI is not found.
- MUST treat MCP configuration as idempotent (re-configuring an existing server is a no-op or safe overwrite).
- MUST NOT fail the entire bundle install when a single MCP configuration fails or is skipped.
- MUST NOT expand
${VAR}references in header or env values.
Item requires resolution
Items (rules, skills, agents) MAY declare directed dependencies in their
requires field (§3.1). This is distinct from bundle requires, which links
bundles: item requires links individual items. See
ADR-0009.
Implementations:
- MUST install an item’s transitive
requiresclosure: installing an item implies installing every item reachable throughrequires. - MUST resolve each
requires.<kind>entry by(kind, name). - MUST reject circular item
requireschains as errors. - MUST report the same
(kind, name)resolving to a different source or ref as an error (the same rule already stated above for bundle item-level conflicts).
Each requires.<kind> entry is a bare name (same source) or a
{ name, source } map (cross-source), where source reuses the upskill add
source DSL. Same-source resolution (bare-name entries against the entry item’s
own already-fetched source) and cross-source resolution ({ name, source }
entries) are both implemented: a cross-source entry is fetched via the same
machinery as upskill add (a distinct source is fetched once and cached for the
duration of the resolution), the transitive closure MAY span sources, and each
dependency-pulled item is recorded in the lockfile with its own source
(and ref) plus a required_by provenance list. Cycle detection is keyed by
(canonical-source-label, kind, name); the same (kind, name) resolving to two
different sources within one closure — or conflicting with an existing
different-source install — is an error. There is no version-range solving.
3.8 Frontmatter canonicalisation
An implementation MAY provide a formatting command (e.g., upskill fmt) that
canonicalises SSOT frontmatter and the markdown body. Frontmatter
canonicalisation produces a deterministic key order, making diffs predictable
and reducing merge conflicts. Body canonicalisation applies the same markdown
formatter used for generated output (§7.4) and normalises the frontmatter↔body
seam to a single blank line, so authored source matches generated output. A
linter MAY report a body-format finding when a body is not canonical.
Canonical key order. When canonicalising frontmatter, implementations MUST emit keys in the following order (unlisted keys appear at the end in their original relative order):
| Kind | Key order |
|---|---|
| Rule | schema, name, description, audience, license, scope, metadata, requires, ignore, claude, copilot, opencode, extras |
| Skill | schema, name, description, audience, license, tools, preload-skills, metadata, requires, ignore, claude, copilot, opencode, extras |
| Agent | schema, name, description, audience, license, mode, model, tools, preload-skills, metadata, requires, ignore, claude, copilot, opencode, extras |
| Bundle | schema, name, description, license, items, requires, plugins, mcps, metadata, extras |
Comment preservation. Canonicalisation:
- MUST preserve YAML comments (
#). Comments immediately preceding a key (no blank-line separation) travel with that key during reordering. - MUST preserve preamble comments. A block of comments at the top of the frontmatter, separated from the first key by at least one blank line, remains pinned to the top.
- MUST NOT round-trip through a YAML serialiser that discards comments; line-level block reordering is the expected implementation strategy.
Validation. After reordering, the canonicalised output MUST parse identically to the original (same keys, same values). Implementations SHOULD validate this by parsing both the input and the output and comparing the resulting data structures.
Idempotency. Running canonicalisation twice on the same input MUST produce identical output.
4. Capability-level tool vocabulary
Agent frontmatter and item bodies reference tools by neutral capability names rather than client-specific identifiers. This decouples content from any single client’s naming conventions.
| Capability | Meaning | Claude Code | GitHub Copilot | opencode |
|---|---|---|---|---|
read | Read file contents | Read | — | read |
write | Create or overwrite files | Write | — | write |
edit | Modify existing files | Edit | — | edit |
bash | Execute shell commands | Bash | shell | bash |
grep | Search file contents | Grep | — | grep |
glob | Match file paths | Glob | — | glob |
web-fetch | Retrieve URL content | WebFetch | fetch | — |
web-search | Search the web | WebSearch | web_search | — |
Implementations maintain the mapping from capability names to client-specific identifiers. The mapping table above is informational; clients may change their tool names independently of this specification.
For MCP tools, which are server-specific and not covered by this vocabulary,
body content SHOULD use the ServerName:tool_name form (colon-separated,
preserving case). This form is readable across clients; client-specific internal
forms (e.g., mcp__server__tool) SHOULD NOT appear in SSOT content.
This vocabulary is intentionally small. It covers the tools common to all three initial clients. Future specification versions may extend it. For tools not covered, item bodies SHOULD describe the desired capability in natural language rather than naming a client-specific tool.
Generation behavior for the agent tools: field. When generating a
client-specific agent output, implementations process each entry in the SSOT
tools: list per the table above:
- Capabilities with a documented client mapping are emitted using the client’s name.
- Capabilities without a documented mapping for a client (
—cells above) are dropped from that client’s output, and implementations SHOULD log a warning at generation time identifying the dropped capability and the affected client. They are not passed through under the SSOT name — emitting an unrecognized identifier would produce a field the client does not consume.
Authors who need client-specific tool names beyond the documented mappings use
that client’s passthrough block (claude:, copilot:, opencode: per §3.5).
The passthrough mechanism is the supported way to surface non-spec tooling in
generated output.
opencode caveat. opencode does not consume a tools: array; it consumes a
permission: map, with capabilities mapped per a permission model where write
rolls into edit. The §4 table shows the opencode capability identifier for
reference, but the actual emission into opencode agent frontmatter follows the
permission-map shape documented in §7.3 and Appendix B.
5. Body content conventions
Item bodies are markdown content following the frontmatter. These conventions ensure generated output is portable and passes standard markdown linting (markdownlint default ruleset).
5.1 Heading structure
- Body content MUST NOT contain an H1 (
#) heading. When a client requires an H1, implementations MUST derive it from the item’snamefield (verbatim, no transformation).descriptionis not used for H1 derivation; using it would make the formatting guarantee in §7.4 unenforceable. - Body headings MUST start at H2 (
##) and MUST NOT skip levels (no H2 → H4 without an intervening H3).
Rationale: generated output prepends an H1 derived from the item metadata. An H1 in the body would produce duplicate H1s, violating MD025 (single-title/single-h1).
5.2 Fenced code blocks
All fenced code blocks MUST specify a language hint. Use text when no specific
language applies.
Rationale: MD040 (fenced-code-language) requires language hints. Generating output that violates this rule forces downstream consumers to either suppress the lint rule or fix the output.
5.3 Tool references in prose
Body content SHOULD describe tools by capability rather than by client-specific name:
- YES: “Search the codebase for occurrences of the deprecated function”
- YES: “Use
GitHub:create_issueto file a tracking issue” (MCP tool in portable form) - NO: “Use the
Readtool to open the file” (Claude-Code-specific) - NO: “Use
#tool:read” (GitHub-Copilot-specific)
5.4 File references
Body content SHOULD reference supporting files using standard markdown links with relative paths from the item directory:
See [approved licenses](./allowed-licenses.txt) for the canonical list.
Client-specific import syntax (@path for Claude Code, #file:path for GitHub
Copilot) MUST NOT appear in canonical bodies.
5.5 Prohibited constructs in canonical bodies
The following constructs MUST NOT appear in always-rendered body content — that is, the portion of the canonical entrypoint body that reaches every client. They are explicitly permitted inside:
<!-- @client:X -->…<!-- @endclient -->directive blocks (§6), whenXmatches the construct’s client. The directive processor strips the entire block from outputs targeting any other client, so the construct never reaches a client that would not understand it.- Per-client override files (§2.3) —
RULE.claude.md,SKILL.copilot.md,AGENT.opencode.md, etc. — when the filename suffix matches the construct’s historical generation client (claude,copilotoropencode). This is a format permission for the specified matching-client override selection, not implemented loading: the historical installer ignores override bodies and captured compilation rejects active matching overrides as recorded in §2.3.codexhas no override-file form.
| Construct | Why prohibited (in always-rendered content) | Client |
|---|---|---|
$ARGUMENTS, $0, $1 | Substitution variable | Claude Code |
${workspaceFolder}, ${file} | Substitution variable | GitHub Copilot |
!`command` | Shell pre-execution | Claude Code |
@path/to/file | File import | Claude Code |
#tool:name | Tool reference | GitHub Copilot |
#file:path | File reference | GitHub Copilot |
ultrathink | Extended thinking trigger | Claude Code |
If an item needs one of these constructs in content that would otherwise reach
every client, wrap it in a matching-client directive block (§6) or move it to a
matching override file for a historical generation client (§2.3). Items that
target only one client end-to-end MAY instead be marked single-client via
audience (§3.1).
6. Conditional content directives
Canonical bodies MAY include client-conditional content via HTML comment directives. These allow minor per-client variations without requiring separate override files.
6.1 Syntax
<!-- @client:claude -->
Content rendered only for Claude Code output.
<!-- @endclient -->
- Opening delimiter:
<!-- @client:<client-list> -->on its own line. - Closing delimiter:
<!-- @endclient -->on its own line. <client-list>is a comma-separated list of client identifiers, optionally prefixed with!for negation.
Known audience/directive family identifiers: claude, copilot, codex,
opencode. Override suffixes remain limited to the three historical generation
clients in §2.3; codex does not add an override suffix.
These identifiers describe source families. They do not add selectable surfaces or change historical default clients. The library-only Codex instruction subset is documented in generation.
6.2 Forms
Positive (include for listed clients):
<!-- @client:claude,copilot -->
This content appears in Claude Code and GitHub Copilot output, not in opencode.
<!-- @endclient -->
Negative (include for everyone except listed clients):
<!-- @client:!opencode -->
This content appears everywhere except opencode output.
<!-- @endclient -->
6.3 Processing rules
Implementations:
- MUST strip non-matching blocks entirely, including the delimiter lines and any surrounding blank lines that would otherwise produce consecutive blank lines (violating MD012).
- MUST preserve the content of matching blocks, removing only the delimiter lines.
- MUST validate that all opened directives are closed (balanced blocks).
- MUST validate that family identifiers in directives are from the known set.
- MUST reject nested directives. If nesting is detected, report an error.
- SHOULD process directives before any other body transformation (formatting, rendering).
6.4 Interaction with override files
Override-file precedence over directives is defined by §2.3 (per-client override
file rules); this subsection exists only as a cross-reference. When generating
output for a historical generation client (claude, copilot or opencode),
the specified resolution order applies only once override execution is
implemented. The current historical installer ignores override bodies; the
captured compiler rejects active matching overrides under AD-0025. The bounded
Codex instruction producer rejects rule override entrypoints and processes
directives only in the canonical body.
7. Generation: client-specific output
Delivery clarification (2026-10-19): the client output layouts below describe
format/generation mappings, not exclusive native discovery or qualified runtime
support. Compatibility directories, imports, globs and shared paths can expose
those outputs to other clients. Future production activation must satisfy the
delivery/discovery/activation contract,
including explicit intended consumers, negative/duplicate controls and ownership
preservation. Independent Codex/OpenCode rule delivery must not be inferred from
these historical file layouts or implemented by rewriting team AGENTS.md. The
user-decided native-first transition
uses one shared always-on Claude/Copilot file when both are selected, with
per-item reader consent and no second Copilot copy. Explicit OpenCode paths
await Q5 PASS; its shipped glob and other output tables remain current until a
separate code change. Managed package defaults and new layout ownership remain
pending adoption/implementation
(amendment inventory).
This section describes the expected output per client: which files an implementation writes for each item kind, and how SSOT frontmatter maps onto each client’s frontmatter shape. It is informational; client conventions may change independently of this specification. Implementations SHOULD verify output against actual client behavior.
This section is deliberately scoped to “what file and frontmatter each client reads”. Adjacent behavior — bridging files, IDE configuration mutation, third-party config-file management — is installer behavior owned by the implementing tool and intentionally out of this specification.
7.1 Claude Code
| Item kind | Output path | Frontmatter mapping |
|---|---|---|
| Rule | .claude/rules/<name>.md | paths: array from scope.paths (if non-empty). |
| Skill | .claude/skills/<name>/SKILL.md | name:, description: pass through. Agent Skills extended fields pass through. |
| Agent | .claude/agents/<name>.md | name:, description:, model:, tools: (capitalized). |
7.2 GitHub Copilot
| Item kind | Output path | Frontmatter mapping |
|---|---|---|
| Rule | .github/instructions/<name>.instructions.md | applyTo: (comma-joined from scope.paths, default "**"), name:, description:. copilot.excludeAgent if present. |
| Skill | .github/skills/<name>/SKILL.md | name:, description: pass through. Follows Agent Skills open standard. |
| Agent | .github/agents/<name>.agent.md | name:, description:, model:, tools: (only documented §4 mappings — bash → shell, web-fetch → fetch, web-search → web_search — survive; unmapped capabilities dropped). GitHub Copilot-specific fields from passthrough block. |
7.3 opencode
| Item kind | Output path | Frontmatter mapping |
|---|---|---|
| Rule | .agents/rules/<name>/RULE.md | name:, description:. opencode-specific fields from passthrough block. scope.paths dropped (no per-rule scoping per §3.2). |
| Skill | .agents/skills/<name>/SKILL.md | name:, description:. Agent Skills extended fields and opencode-specific fields from passthrough block. opencode walks this directory. |
| Agent | .opencode/agents/<name>.md | name:, description:, mode:, model:, permission: map (allow per capability; write rolls into edit). opencode-specific fields from passthrough block. |
7.4 Formatting guarantee
Generated output MUST be formatted such that:
- It is idempotent under the implementation’s chosen markdown formatter (e.g., dprint).
- It passes
markdownlint-cli2with the default ruleset (or a documented subset pinned by the implementation).
Implementations SHOULD format generated markdown as the final step before writing, using an embedded or invoked formatter, so that developers running the same formatter locally see no diffs on generated files.
8. Schema versioning
The top-level schema field declares the specification version the file was
authored against.
Current version: 1.
8.1 Rules for implementations
- MUST reject files with
schemagreater than the highest version the implementation supports. - MUST produce a clear error message identifying the required specification version and suggesting an upgrade of the implementing tool.
- MUST accept files with
schemaequal to or less than the highest supported version. - When processing older schema versions, MUST NOT silently apply newer defaults that would change the file’s meaning.
8.2 Rules for specification evolution
- MINOR additions (new optional fields, new optional top-level blocks) MAY occur within the same schema version. Implementations MUST ignore unknown fields they don’t support.
- BREAKING changes (removed fields, renamed fields, changed semantics of existing fields, new required fields) MUST increment the schema version.
- Each published schema version MUST have a stable reference document with a permanent URL.
9. Conformance
An implementation is conformant with this specification if:
- It correctly parses all frontmatter fields defined in §3, accepting unknown
fields within
metadataand client passthrough blocks without error. - It validates
nameagainst §2.1 constraints and rejects non-conforming names with a clear error. - It rejects files with unsupported
schemaversions per §8. - It correctly processes
@client:directives per §6.3, including balanced-block validation. - It resolves bundle
itemsreferences against available item sources and reports unresolved names as errors. - It preserves supporting files from item directories in generated output per §2.4.
- Generated output satisfies the formatting guarantee per §7.4.
A reference test corpus SHOULD accompany mature versions of this specification. For v0.1-draft, conformance is assessed against authored examples in the implementation’s own test suite.
10. Non-goals
This specification does not define:
- Client file layouts or frontmatter conventions beyond what §7 documents as informational context. These are owned by each client’s maintainer and may change independently.
- Installation, caching, synchronization, or registry protocols. These are implementation concerns.
- CLI command surfaces, configuration files, or user-facing workflows.
- Validation of semantic content quality. This is reserved for AI-review or other semantic analysis mechanisms outside the scope of format validation.
- Formatting conventions beyond what §5 requires for portability and §7.4 requires for generated output.
- Signing, provenance, supply-chain verification, or trust models. These are important but separate concerns for a future specification.
- Access control, classification enforcement, or governance workflows.
Implementations MAY use
metadatafields to integrate with external governance systems, but the mechanisms are not specified here.
11. Open questions for future versions
The following topics are explicitly deferred and tracked for future specification work:
- Scope patterns: whether
scope.pathsshould support negation (!test/**), boolean combinations, or named scopes. - Tool vocabulary: whether the §4 table should be expanded, whether MCP tool references need a more structured form, and how to handle tools that exist in only one client.
- Agent mode semantics: whether
primary/subagent/allis sufficient given divergent agent models across clients, or whether mode should be client-passthroughed entirely. - Bundle constraints: whether
requiresshould support semver ranges, platform predicates (requires-client: [claude >= 2.1]), or conditional items. - Per-client partial overrides: whether override files should support section-level overrides (replacing only a specific heading’s content) rather than full-body replacement.
- Skill activation control: whether
disable-model-invocationanduser-invocablefrom the Agent Skills standard should be surfaced as neutral fields or remain client passthroughs. - Multi-repo item sources (RESOLVED): the item
requiresfield with a{ name, source }locator (§3.7) defines the cross-source resolution contract —sourcereuses theupskill addsource DSL, conflicts reuse the same-name-different-source rule, and cycles are keyed by(canonical-source-label, kind, name). Both same-source and cross-source resolution are implemented. See ADR-0009. - Content hashing: whether items should carry a content hash for integrity
verification during distribution, independent of
metadata.version.
Appendix A: Complete frontmatter examples
A.1 Rule — always-on, all clients
---
schema: 1
name: license-awareness
description: Use when reviewing third-party code, AI suggestions, or external snippets to check licensing compatibility and avoid incorporating incompatible licensed code
license: proprietary
metadata:
version: "1.0.0"
author: platform-security
---
## Licensing rules
- Never paste code from external sources without verifying the license.
- When AI suggests code matching a recognizable upstream project, flag it before incorporating.
- See [approved licenses](./allowed-licenses.txt) for the canonical SPDX list.
- AGPL, SSPL, and BUSL code requires explicit approval from legal.
A.2 Rule — path-scoped, with GitHub Copilot passthrough
---
schema: 1
name: api-conventions
description: Use when writing or modifying API handler files to ensure consistent request validation, error formatting, and response shape
license: proprietary
scope:
paths:
- "src/api/**/*.ts"
- "src/handlers/**/*.ts"
copilot:
excludeAgent: code-review
metadata:
version: "2.1.0"
author: platform-api
---
## API handler conventions
- All endpoints include input validation using the project's schema library.
- Use the standard error response format defined in `src/api/errors.ts`.
- Never return raw database errors to the client.
A.3 Skill — on-demand, with directive
---
schema: 1
name: create-api-endpoint
description: Use when scaffolding new REST API endpoints with Zod validation, following project conventions for handler structure, error handling, and test coverage
license: proprietary
metadata:
version: "1.0.0"
author: platform-api
---
## Create a new API endpoint
Follow these steps to scaffold a complete endpoint:
1. Create the handler file at `src/api/handlers/{resource}.ts`.
2. Define the request schema using Zod in `src/api/schemas/{resource}.ts`.
3. Register the route in `src/api/routes.ts`.
4. Create the test file at `src/api/__tests__/{resource}.test.ts`.
<!-- @client:claude -->
When creating files, use the Write tool for new files rather than Edit.
<!-- @endclient -->
## Validation pattern
All inputs MUST be validated against the Zod schema before processing.
See [validation patterns](./references/validation-patterns.md) for examples.
A.4 Agent — subagent with tools and preloaded skill
---
schema: 1
name: security-reviewer
description: Use when reviewing code for injection flaws, authentication issues, secret leaks, and insecure data handling
license: proprietary
audience:
- claude
- copilot
- opencode
mode: subagent
model: sonnet
tools:
- read
- grep
- glob
- bash
preload-skills:
- security-baseline
opencode:
temperature: 0.2
metadata:
version: "1.0.0"
author: platform-security
---
## Security reviewer
You are a security-focused code reviewer. When invoked:
1. Identify injection vectors (SQL, command, path traversal, XSS).
2. Verify authentication and authorization checks on every sensitive endpoint.
3. Scan for hardcoded secrets, tokens, API keys, or credentials.
4. Flag insecure data handling (unencrypted PII, weak cryptographic choices, improper logging of sensitive data).
For each finding, report severity (critical/high/medium/low), file and line location, and a concrete remediation.
A.5 Bundle — with dependency
---
schema: 1
name: android
description: Rules, skills, and agents for Android application development teams
license: proprietary
items:
rules:
- android-lifecycle
- kotlin-coroutines
- compose-conventions
skills:
- create-viewmodel
- migrate-to-compose
agents:
- android-reviewer
requires:
- platform-baseline
metadata:
version: "0.8.0"
author: android-platform
---
## Android bundle
Layered on top of `platform-baseline`. Provides Android-specific development
conventions, Jetpack Compose patterns, and a specialized code reviewer.
A.6 Agent — with per-client override file
Directory structure:
agents/security-reviewer/
├── AGENT.md # canonical
├── AGENT.claude.md # Claude Code body override
└── AGENT.copilot.md # GitHub Copilot body override
This is a format illustration, not current installer behavior. Under the
specified §2.3 resolution order, AGENT.md supplies frontmatter for all
clients; the Claude and Copilot overrides replace the canonical body for their
respective clients. OpenCode uses the canonical body with directives processed.
The current historical installer instead renders the canonical body for all
three clients; the captured compiler rejects an active matching override under
AD-0025.
Neither path currently implements the illustrated override selection.
Appendix B: Client frontmatter mapping reference
This table summarizes how SSOT fields map to each client’s frontmatter when generating output. This is informational and subject to change as clients evolve.
| SSOT field | Claude Code output | GitHub Copilot output | opencode output |
|---|---|---|---|
name | name: | name: | name: |
description | description: | description: | description: |
scope.paths | paths: (array) | applyTo: (comma-joined string) | (not supported; always-on) |
mode | (implicit in file location) | (agent definition) | mode: |
model | model: (alias) | model: (full ID) | model: (provider/ID) |
tools (agent) | tools: capitalized names | tools: mapped only (§4); unmapped dropped | permission: map (allow per capability; write rolls into edit) |
preload-skills | skills: in agent | — | — |
copilot.* | (stripped) | Merged into frontmatter | (stripped) |
opencode.* | (stripped) | (stripped) | Merged into frontmatter |
claude.* | Merged into frontmatter | (stripped) | (stripped) |
metadata.* | (stripped from output) | (stripped from output) | (stripped from output) |
Appendix C: Directive quick reference
<!-- @client:claude -->
Include only in Claude Code output
<!-- @client:copilot --> Include only in Copilot output
<!-- @client:opencode --> Include only in opencode output
<!-- @client:claude,copilot -->
Include in Claude Code and GitHub Copilot, not opencode
<!-- @client:!opencode -->
Include everywhere except opencode
<!-- @endclient -->
Close any directive block (required)
Appendix D: Recommended source-registry layout
This specification is intentionally tool-agnostic and lets registries pick any
<item-root> (§2.1) and any <bundle-root> (§2.2). The upskill project records
its specific recommendation — a flat skills/ directory holding both items and
bundles, with a nested skills/bundles/ variant for large registries — in the
user guide:
That recommendation is non-normative; a conforming registry MAY deviate and conforming tooling MUST NOT reject deviations.
Rules: one directive per line, no nesting, balanced open/close, known client identifiers only.