A Composer plugin for AI agent skills in PHP projects. Skills are skill dependencies of your repo: you declare them through one or more of the mechanisms below (they are equivalent in the sense that everything flows into the same discovery, trust, and AGENTS.md pipeline—you may combine them). Versioning and publishing differ (semver packages vs pinned git lock), not whether the path is “official.”
- Three ways to bring skills in — see How skills enter your project: (1)
composer requireatype: ai-agent-skillpackage, (2)composer requireany package that lists skills underextra.ai-agent-skill, (3) declare GitHub or in-repo paths underextra.ai-agent-skillswithcomposer.skills.lockandcomposer skills …(Direct sources). - Automatic discovery: Finds skills from installed packages (legacy type and universal
extraform). - AGENTS.md generation: Creates XML skill index compatible with openskills
- CLI commands:
composer list-skills,composer read-skill, trust helpers,composer skills:*for direct sources, andcomposer outdatedappends direct-skill rows (text) or points tocomposer skills:outdated - Convention Over Configuration: Works out of the box with zero configuration
- Progressive Disclosure: Lightweight index, full details on demand
- Security First: Rejects unsafe paths, validates skill metadata (including descriptions bound for AGENTS.md)
- Multiple Skills Per Package: Support for both single and multi-skill packages
composer require netresearch/composer-agent-skill-pluginDuring installation, Composer will prompt you to authorize the plugin:
netresearch/composer-agent-skill-plugin contains a Composer plugin which is currently not in your allow-plugins config.
Do you trust "netresearch/composer-agent-skill-plugin" to execute code and wish to enable it now? [y,n,d,?]
Choose y to allow the plugin to activate.
For CI/CD or non-interactive environments, pre-authorize the plugin in your composer.json:
{
"config": {
"allow-plugins": {
"netresearch/composer-agent-skill-plugin": true
}
}
}composer requirea dedicated skill package —type: ai-agent-skill(classic).composer requireany package that ships skills —extra.ai-agent-skillon libraries, bundles, etc. (universal discovery).- Direct sources — root
extra.ai-agent-skills+composer.skills.lock+composer skills …. Same trust /AGENTS.mdpipeline as (1) and (2); you can use several of these together.
Install any package with type ai-agent-skill:
composer require vendor/database-analyzer-skillThe plugin automatically:
- Discovers skill packages during
composer installorcomposer update - Asks once before registering skills from a package it hasn't seen before — see Trust Model
- Generates/updates
AGENTS.mdin your project root with the trusted skills - Registers them in XML format for AI agent consumption
AI agents automatically discover skills via AGENTS.md:
# List all available skills
composer list-skills
# Read a specific skill
composer read-skill database-analyzerDesign rationale: see docs/adr/ for short Architecture Decision Records covering universal discovery, the trust prompt design, atomic-write persistence, glob semantics, direct skills (009–012), and more.
Path (3) above: pin skills from GitHub or from a directory inside the project using extra.ai-agent-skills and a generated lockfile composer.skills.lock. Full technical notes: docs/IMPLEMENTATION-DIRECT-SKILLS.md.
gitonPATHwhen using GitHub sources (clone/fetch).- Supported source strings match
SourceResolver: GitHub HTTPS/SSH,owner/reposhorthand (with optional:refsuffix), GitHub tree URLs, and existing local directories. Generic non-GitHub git HTTPS URLs are not supported yet.
For GitHub shorthand (or --ref), you can use Composer-style semver constraints against annotated/lightweight tags on the remote (git ls-remote --tags). Examples:
# Highest tag satisfying ^1.2 (e.g. 1.2.6), stored as ref "^1.2" in composer.json; lock pins the commit
composer skills:add acme/some-skill-repo:^1.2 --skill=my-skill
# Same, via flag
composer skills:add acme/some-skill-repo --skill=my-skill --ref='^1.2'On composer update, the plugin’s post-update hook re-resolves the constraint against current remote tags and refreshes composer.skills.lock (same idea as relaxing a lock when you bump constraints—here the constraint lives in extra.ai-agent-skills.sources[].ref). composer install still applies the pinned commit from the lock only.
Constraints are detected when the ref contains semver operators (^, ~, *, >=, ranges with , / ||, etc.). Plain names like main or v1.2.3 are still treated as literal git refs.
composer outdated(same ascomposer show --latest --outdated) lists Composer packages as usual. When you use text output, this plugin appends a short block for direct agent skills whosecomposer.skills.lockpin is behind the current remote git tip (for semver, branch, or tag sources) or whose path skill content hash changed on disk. Resolving usesgit ls-remote(read-only); failures are skipped with a comment on stderr.composer outdated -f jsonmust stay valid JSON for package rows only. If direct skills are stale, the plugin prints a stderr notice; runcomposer skills:outdated -f jsonfor a machine-readable{"outdated":[…]}document.composer skills:outdated— full list; add--strictfor exit code 1 when anything is outdated (handy in CI).composer outdated --strictstill reflects Composer packages only (unchanged upstream behaviour).
# Add a source and resolve skills into composer.skills.lock (writes composer.json + lock)
composer skills:add vercel-labs/skills --skill=find-skills --ref=main
# On CI or a fresh clone: install exactly what the lock pins (no implicit network update)
composer install
# After editing sources in composer.json: refresh the lock + installed files
composer skills:updateEquivalent forms: composer skills add … and composer skills:add … (same for install, update, remove, list, outdated).
| Key | Purpose |
|---|---|
version |
Schema version (currently 1) |
install-dir |
Where skill trees are materialized (default vendor/agent-skills/installed/) |
cache-dir |
Git clone and temporary work directories (default vendor/agent-skills/cache) |
sources-dir |
Declared directory prefix (included in the content-hash); clone cache uses cache-dir, not this path |
sources |
List of sources (type: path, github, or git with url / path / ref / skills) |
Relative directory keys must not use .. or absolute paths. Lockfile install-path / path / path-type url fields are checked the same way so a tampered lock cannot write outside the project.
Direct skills use the same extra.ai-agent-skill.allow-skills map as packages. Keys look like:
direct:<source-name>/<skill-id>
Use composer list-skills to see the exact package column value, then:
composer skills:trust 'direct:vercel-labs/skills/find-skills'See ADR-012: changing allow/deny does not change the lock content-hash; changing sources does.
Set environment variable:
COMPOSER_AGENT_SKILLS=0
…to skip direct-skill install/update hooks (legacy AGENTS.md behaviour is unchanged).
Skills are instructions an AI agent will follow in your project. To prevent transitive dependencies from silently injecting skills, the plugin asks before registering skills from a new package — modeled on Composer's own allow-plugins flow.
The first time a new package wants to register skills, you'll see:
Package "vendor/foo" wants to register AI agent skills.
Skills are instructions an AI agent will follow in your project.
Allow this package to register skills?
[y] Yes — allow & persist (writes to composer.json)
[n] No — deny & persist (suppress future prompts)
[a] Allow for this session only
[d] Discard — leave undecided, ask again next run
[?] Show details about this choice
(change later with: composer skills:trust vendor/foo [--deny|--revoke])
(defaults to n) [y,n,a,d,?]:
Decisions persist under extra.ai-agent-skill.allow-skills in your root composer.json:
{
"extra": {
"ai-agent-skill": {
"allow-skills": {
"vendor/foo": true,
"vendor/bar": false,
"trusted-org/*": true
}
}
}
}Use the dedicated commands (preferred):
composer skills:trust vendor/foo # allow & persist
composer skills:trust vendor/foo --deny # deny & persist
composer skills:trust vendor/foo --revoke # remove from the map (re-prompts next install)
composer skills:list-trust # show every persisted decisionOr edit composer.json directly. Glob patterns (vendor/*) are supported. Direct skills use keys direct:<source>/<skill-name> in the same map — see Direct sources.
⚠️ Glob matching is case-sensitive and*matches any characters within a pattern segment. Composer normalizes published package names to lowercase, so write trust patterns in lowercase. A pattern likeacme/skills-*also trustsacme/skills-anything-else— use globs only for namespaces you fully control. Exact-string keys always override matching globs, even when the glob was added first. Prefer explicit per-package entries when in doubt.
In non-interactive mode (composer install --no-interaction, CI), packages without an explicit decision are skipped with a warning — the plugin never auto-trusts on your behalf. The warning suggests composer skills:trust <package> so CI failures are one command away from a fix. composer list-skills shows the trust state per skill ([allowed] / [pending] / [denied]) without firing prompts.
The first time the plugin runs in a project that already has type: ai-agent-skill packages installed — typically a fresh install of those packages or an upgrade from v0.1.x where they were silently auto-trusted — you'll see a one-time prompt asking how to seed the trust map:
The AI Agent Skill plugin found 3 existing skill packages that have not been authorized yet.
- vendor/skill-a (in your require)
- vendor/skill-b (pulled in by another package)
- vendor/skill-c (pulled in by another package)
How should they be trusted on this first run?
[n] None — prompt for each package later (default, strict)
[d] Direct dependencies only — auto-trust packages your root composer.json explicitly requires
[a] All — auto-trust every existing skill package (including transitive)
(defaults to n) [n,d,a]:
n(default, recommended) — nothing is auto-trusted. Each package goes through the per-package prompt during the same install.d— only packages listed directly in your rootcomposer.json'srequire/require-devare auto-trusted. Transitive skill packages still prompt.a— every existing skill package is auto-trusted. This restores the v0.1.x behaviour at the user's explicit consent — fastest, widest trust surface.
The choice is persisted under extra.ai-agent-skill.allow-skills, so the prompt only fires once. In non-interactive mode (composer install --no-interaction, CI) the policy defaults to n and a warning lists every affected package with the composer skills:trust ... recovery command. CI installs never silently expand trust on your behalf.
Library-bundled skills (type: library + extra.ai-agent-skill) are not part of this first-run policy — they always go through the per-package prompt because the user chose the library for its primary purpose, not to import skills.
$ composer list-skills
Available AI Agent Skills:
database-analyzer vendor/db-skill 1.2.0 [allowed]
oro-bundle-helper vendor/oro-skill 1.0.0 [allowed]
symfony-security vendor/symfony-security 2.1.0 [pending]
3 skills available. Use 'composer read-skill <name>' for details.
1 pending — run `composer install` interactively to be prompted.$ composer read-skill database-analyzer
Reading: database-analyzer
Package: vendor/db-skill v1.2.0
Base Directory: vendor/vendor/db-skill
---
name: database-analyzer
description: Analyze and optimize database schemas and relationships
---
# Database Analyzer Skill
[Full SKILL.md content with instructions and examples]
Skill read: database-analyzerThe Base Directory is the directory containing SKILL.md, used as the root for resolving bundled resources like references/, scripts/, and assets/.
There are two ways to ship skills, both fully supported:
Any package — regardless of type — can ship skills by declaring extra.ai-agent-skill:
{
"name": "vendor/my-library",
"type": "library",
"extra": {
"ai-agent-skill": "skills/my-helper.md"
}
}This mirrors the pattern phpstan/extension-installer uses for PHPStan extensions: the library opts in via extra, no special package type required. Use this when an existing library wants to ship a companion skill alongside its primary purpose.
A package whose only purpose is shipping skills can use type: ai-agent-skill and place SKILL.md in the package root with no extra config:
1. Create composer.json:
{
"name": "vendor/my-skill",
"description": "My awesome AI agent skill",
"type": "ai-agent-skill",
"license": "MIT",
"require": {
"php": "^8.2"
}
}2. Create SKILL.md in package root:
---
name: my-skill
description: Brief description of what this skill does and when to use it
---
# My Skill
## Instructions
Step-by-step guidance for using this skill...
## Examples
Example 1: How to use feature X
Example 2: How to handle scenario Y
## Requirements
- PHP 8.2+
- Any other dependencies3. Publish to Packagist:
git tag 1.0.0
git push --tagsFor packages containing multiple skills, configure paths in extra.ai-agent-skill:
{
"name": "vendor/database-tools",
"type": "ai-agent-skill",
"require": {
"netresearch/composer-agent-skill-plugin": "*"
},
"extra": {
"ai-agent-skill": [
"skills/analyzer/SKILL.md",
"skills/optimizer/SKILL.md",
"skills/validator/SKILL.md"
]
}
}For a single skill in a non-standard location:
{
"name": "vendor/custom-skill",
"type": "ai-agent-skill",
"require": {
"netresearch/composer-agent-skill-plugin": "*"
},
"extra": {
"ai-agent-skill": "docs/agent-skill.md"
}
}Security Note: Only relative paths from package root are allowed. Absolute paths are rejected.
Skills must follow the Claude Code SKILL.md specification:
---
name: skill-name # lowercase, numbers, hyphens only (max 64 chars)
description: Clear description of functionality and triggers (max 1024 chars)
------
name: my-skill
description: What it does and when to use it
allowed-tools: [Read, Grep, Glob] # Claude Code only
---- Name format:
^[a-z0-9-]{1,64}$(lowercase alphanumeric and hyphens) - Name length: Maximum 64 characters
- Description length: Maximum 1024 characters
- YAML syntax: Valid YAML with proper delimiters (
---)
No configuration needed. Plugin looks for SKILL.md in package root:
vendor/my-skill/
├── composer.json
├── SKILL.md ← Auto-discovered
└── src/
{
"extra": {
"ai-agent-skill": "custom/path/skill.md"
}
}{
"extra": {
"ai-agent-skill": [
"skills/skill-one.md",
"skills/skill-two.md",
"docs/skill-three.md"
]
}
}[WARNING] No AI agent skills found in installed packages.
Solution: Install packages with "type": "ai-agent-skill" in their composer.json.
[vendor/tools-b] Duplicate skill name 'database-analyzer' (already defined by vendor/tools-a).
Using skill from vendor/tools-b (last one wins).
Behavior: Last package wins. Consider renaming skills to avoid conflicts.
[vendor/broken-skill] Invalid frontmatter in 'SKILL.md': Missing required field: 'description'
Solution: Ensure SKILL.md has both name and description fields in valid YAML format.
[vendor/broken-yaml] Malformed YAML in 'SKILL.md':
A colon cannot be used in an unquoted mapping value at line 3
Solution: Fix YAML syntax. Use spaces (not tabs), quote values with colons.
[vendor/missing-skill] SKILL.md not found at 'SKILL.md'.
Expected SKILL.md in package root (convention).
Solution: Create SKILL.md in package root or configure extra.ai-agent-skill path.
[vendor/unsafe-config] Absolute paths not allowed in 'extra.ai-agent-skill'.
Use relative paths from package root.
Solution: Use relative paths like "skills/analyzer.md" instead of /absolute/path.
- Plugin hooks into
composer installandcomposer updateevents - Finds all packages with type
ai-agent-skill - Reads each package's
composer.jsonfor skill paths - Parses SKILL.md files and validates frontmatter
- Generates XML skill registry in
AGENTS.md
<skills_system priority="1">
## Available Skills
<!-- SKILLS_TABLE_START -->
<usage>
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively.
How to use skills:
- Invoke: Bash("composer read-skill <skill-name>")
- The skill content will load with detailed instructions
- Base directory provided in output for resolving bundled resources
</usage>
<available_skills>
<skill>
<name>database-analyzer</name>
<description>Analyze and optimize database schemas</description>
<location>vendor/vendor/db-skill</location>
</skill>
</available_skills>
<!-- SKILLS_TABLE_END -->
</skills_system>- AGENTS.md: Lightweight index with skill names and descriptions
- read-skill: Full SKILL.md content loaded on demand
- Benefits: Fast discovery, reduced context size, on-demand details
- PHP 8.2 or higher
- Composer 2.1 or higher
- Symfony YAML Component 5.4+, 6.0+, or 7.0+
- Symfony Console Component 5.4+, 6.0+, or 7.0+
# Run all tests
./vendor/bin/phpunit
# Run with coverage
./vendor/bin/phpunit --coverage-text
# Run specific test
./vendor/bin/phpunit tests/Unit/SkillDiscoveryTest.php# PHPStan static analysis (level 8)
./vendor/bin/phpstan analyse
# PHP CS Fixer (PSR-12)
./vendor/bin/php-cs-fixer fix --allow-risky=yes
# Check without fixing
./vendor/bin/php-cs-fixer fix --dry-run --allow-risky=yessrc/
├── Commands/
│ ├── ListSkillsCommand.php # composer list-skills
│ └── ReadSkillCommand.php # composer read-skill
├── Exceptions/
│ └── InvalidSkillException.php
├── AgentsMdGenerator.php # AGENTS.md generation
├── CommandCapability.php # Command registration
├── SkillDiscovery.php # Package discovery
└── SkillPlugin.php # Main plugin class
tests/
├── Unit/ # Unit tests
├── Integration/ # Integration tests
└── Fixtures/ # Test fixtures
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
./vendor/bin/phpunit) - Run static analysis (
./vendor/bin/phpstan analyse) - Fix code style (
./vendor/bin/php-cs-fixer fix --allow-risky=yes) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- PSR-12 coding style
- PHPStan level 8
- 100% type coverage
- Comprehensive tests (>80% coverage)
This project is licensed under the MIT License - see the LICENSE file for details.
Inspired by openskills - Universal AI Agent Skills for standardized skill distribution across development environments.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ by Netresearch