Define AI instructions, agents, and skills once. Generate platform-specific config files for Claude Code and GitHub Copilot automatically.
npx aio init
npx aio validate
npx aio generate
load.test.ts ← parseYamlFile(), loadConfig()
render.test.ts ← renderFrontmatter(), renderDocument(), normalizeContent()
schema.test.ts ← AIO_SCHEMA
utils.test.ts ← normalizeConfig()
validate.test.ts ← validateSchema()
| What you want to define | Claude expects | Copilot expects |
|---|---|---|
| Project-wide instructions | `CLAUDE.md` | `.github/copilot-instructions.md` |
registry.test.ts
utils.test.ts
| Agent definitions | `.claude/agents/<id>.md` | `.github/agents/<id>.agent.md` |
| Skills | `.claude/skills/<id>/SKILL.md` | `.github/skills/<id>/SKILL.md` |
AI Orchestrator lets you write everything once in `.ai/` and generates all of the above.
---
## Installation
```bash
# one-off via npx
npx aio init
# or install globally
npm install -g aio
aio initRequirements: Node.js 20+
# 1. Scaffold the .ai/ directory with examples
npx aio init
# 2. Edit the generated YAML files to match your project
# 3. Check for errors
npx aio validate
# 4. Write Claude and Copilot output files
npx aio generateAfter generate, commit the outputs:
git add CLAUDE.md .claude/ .github/
git commit -m "chore: sync AI config".ai/
manifest.yml # entry point — lists targets and imports
instructions/ # global and path-scoped instructions
agents/ # AI agent definitions
skills/ # reusable skill definitions
fragments/ # shared content blocks (not emitted directly)
manifest.yml is the root of your configuration. It declares which platforms to target and which files to include.
version: 1
targets:
- claude
- copilot
imports:
- ./instructions/global.yml
- ./instructions/frontend/react.yml
- ./agents/code-reviewer.yml
- ./skills/git-commit.yml| Field | Type | Description |
|---|---|---|
version |
1 |
Schema version. Currently always 1. |
targets |
claude | copilot[] |
Which platforms to generate output for. |
imports |
string[] |
Relative paths to YAML object files to include. |
Every file in .ai/ (other than the manifest) defines one object with a kind field.
Instructions tell the AI how to behave. They can apply globally or only to files matching a path pattern.
kind: instruction
id: global-conventions
scope: global
content:
- Always run tests before committing.
- Write clear, descriptive commit messages.
- Prefer explicit over implicit code.kind: instruction
id: typescript-rules
scope: path
applyTo:
- "src/**/*.ts"
- "src/**/*.tsx"
content: |
Always use strict TypeScript. Avoid `any`. Prefer `unknown` for external data.
Use discriminated unions over optional fields where possible.| Field | Required | Description |
|---|---|---|
kind |
Yes | Must be "instruction" |
id |
Yes | Unique identifier (lowercase, hyphens) |
scope |
Yes | "global" or "path" |
content |
Yes | Instruction text — string or list of strings |
name |
No | Human-readable name (defaults to id) |
applyTo |
No | Glob patterns (required when scope: path) |
targets |
No | Restrict to specific targets: [claude], [copilot] |
fragments |
No | Fragment IDs to prepend to content |
| Scope | Claude output | Copilot output |
|---|---|---|
global |
CLAUDE.md |
.github/copilot-instructions.md |
path |
.claude/rules/<id>.md with paths: frontmatter |
.github/instructions/<id>.instructions.md with applyTo: frontmatter |
Agents are autonomous AI assistants that can use tools and follow a system prompt.
kind: agent
id: code-reviewer
name: Code Reviewer
description: Reviews code for quality, security, and maintainability.
tools:
- Read
- Grep
- Glob
model: sonnet
prompt: |
You are a senior code review agent. Analyze the provided code and give feedback on:
- Logic errors and potential bugs
- Security vulnerabilities
- Code style and readability
- Test coverage gaps
Be specific, actionable, and constructive.| Field | Required | Description |
|---|---|---|
kind |
Yes | Must be "agent" |
id |
Yes | Unique identifier |
description |
Yes | Short description of the agent's purpose |
prompt |
Yes | System prompt (Markdown) |
name |
No | Display name (defaults to id) |
tools |
No | List of tools the agent can use |
allowedTools |
No | Explicitly allowed tools |
disallowedTools |
No | Tools the agent cannot use (Claude only) |
model |
No | Model override: sonnet, opus, haiku |
maxTurns |
No | Max conversation turns (Claude only) |
targets |
No | Restrict to specific targets |
fragments |
No | Fragment IDs to prepend to prompt |
| Claude output | Copilot output |
|---|---|
.claude/agents/<id>.md |
.github/agents/<id>.agent.md |
Note:
disallowedToolsandmaxTurnsare Claude-only fields and are silently dropped when generating Copilot output. Runaio validate --target copilotto see warnings.
Skills are invocable capabilities that the AI can call when needed.
kind: skill
id: git-commit
name: git-commit
description: Generates concise, semantic git commit messages.
targets:
claude:
allowedTools:
- shell
copilot:
license: MIT
allowedTools:
- shell
content: |
When asked to create a commit message:
1. Review the staged diff carefully.
2. Identify the primary purpose of the change.
3. Write a concise subject line (50 chars max).
4. Add a body paragraph if the change is complex.
5. Use conventional commit format: feat/fix/chore/docs/refactor.| Field | Required | Description |
|---|---|---|
kind |
Yes | Must be "skill" |
id |
Yes | Unique identifier |
description |
Yes | Short description of what the skill does |
content |
Yes | Skill instructions (Markdown) |
name |
No | Display name (defaults to id) |
license |
No | License identifier, e.g. MIT (set under targets.copilot.license) |
allowedTools |
No | Tools the skill may invoke (set under targets.<platform>.allowedTools) |
targets |
No | Per-target overrides for Claude/Copilot |
fragments |
No | Fragment IDs to prepend to content |
| Field | Description |
|---|---|
whenToUse |
Extra context for when Claude should invoke the skill (when_to_use) |
argumentHint |
Autocomplete hint for expected arguments (argument-hint) |
arguments |
Named positional arguments (string or list) (arguments) |
disableModelInvocation |
Disable automatic invocation (disable-model-invocation) |
userInvocable |
Hide from the / menu when false (user-invocable) |
model |
Model override (model) |
effort |
Effort override: low, medium, high, xhigh, max (effort) |
context |
Execution context, e.g. fork (context) |
agent |
Subagent type when context: fork (agent) |
hooks |
Hook IDs to attach (hooks) |
paths |
Glob patterns limiting auto-activation (paths) |
shell |
Shell for command injection (shell) |
allowedTools |
Pre-approved tools (allowed-tools) |
| Claude output | Copilot output |
|---|---|
.claude/skills/<id>/SKILL.md |
.github/skills/<id>/SKILL.md |
Note:
licenseis a Copilot-only field. It is included in Copilot output but dropped from Claude output. No error is raised — just a warning fromaio validate.
Fragments are reusable content blocks that can be referenced by other objects. They do not produce any output files on their own.
kind: fragment
id: coding-standards
content:
- Prefer explicit naming over abbreviations.
- Avoid side effects in pure functions.
- Write self-documenting code.Reference a fragment from any instruction, agent, or skill:
kind: instruction
id: backend-api
scope: global
fragments:
- coding-standards # prepended first
content: |
Follow the repository pattern for data access.
Never expose raw database errors to callers.The merged content becomes:
- Prefer explicit naming over abbreviations.
- Avoid side effects in pure functions.
- Write self-documenting code.
Follow the repository pattern for data access.
Never expose raw database errors to callers.
| Field | Required | Description |
|---|---|---|
kind |
Yes | Must be "fragment" |
id |
Yes | Unique identifier used in fragments: references |
content |
Yes | Text content — string or list of strings |
name |
No | Optional label |
Scaffolds a .ai/ directory with working examples for all four object kinds.
aio initCreates:
.ai/
manifest.yml
instructions/global.yml
agents/code-reviewer.yml
skills/git-commit.yml
fragments/coding-standards.yml
Existing files are never overwritten.
Validates configuration without writing any files. Checks:
- Malformed YAML
- Missing required fields
- Duplicate IDs across all imported files
- Unresolved fragment references
- Target compatibility (fields that don't map to a given platform)
aio validate # validate for both targets
aio validate --target claude # claude only
aio validate --target copilot # copilot only
aio validate --strict # treat warnings as errorsExit codes: 0 on success, 1 on failure.
Generates platform-specific output files from your canonical config. Always validates first — generation is blocked if validation fails.
aio generate # generate for both targets
aio generate --target claude # claude output only
aio generate --target copilot # copilot output only
aio generate --dry-run # print what would be written, write nothing
aio generate --watch # watch .ai/ and regenerate on every change
aio generate --discover # auto-discover YAML files instead of using manifest importsClaude:
CLAUDE.md ← global instructions
.claude/
rules/<id>.md ← path-scoped instructions
agents/<id>.md ← agent definitions
skills/<id>/SKILL.md ← skill definitions
Copilot:
.github/
copilot-instructions.md ← global instructions
instructions/<id>.instructions.md ← path-scoped instructions
agents/<id>.agent.md ← agent definitions
skills/<id>/SKILL.md ← skill definitions
This table shows exactly how canonical fields map to each target's output format.
| Canonical field | Claude (paths frontmatter) |
Copilot (applyTo frontmatter) |
|---|---|---|
scope: global |
Content written to CLAUDE.md |
Content written to copilot-instructions.md |
scope: path |
.claude/rules/<id>.md |
.github/instructions/<id>.instructions.md |
applyTo |
paths: [...] |
applyTo: "glob1, glob2" |
content |
Markdown body | Markdown body |
| Canonical field | Claude | Copilot |
|---|---|---|
name |
name: frontmatter |
name: frontmatter |
description |
description: frontmatter |
description: frontmatter |
tools |
tools: frontmatter |
tools: frontmatter |
model |
model: frontmatter |
model: frontmatter |
allowedTools |
allowedTools: frontmatter |
dropped |
disallowedTools |
disallowedTools: frontmatter |
dropped (warning) |
maxTurns |
maxTurns: frontmatter |
dropped (warning) |
prompt |
Markdown body | Markdown body |
| Canonical field | Claude | Copilot |
|---|---|---|
name |
name: frontmatter |
name: frontmatter |
description |
description: frontmatter |
description: frontmatter |
targets.claude.allowedTools |
allowed-tools: frontmatter |
dropped |
targets.copilot.allowedTools |
dropped | allowed-tools: frontmatter |
targets.copilot.license |
dropped (warning) | license: frontmatter |
targets.claude.whenToUse |
when_to_use: frontmatter |
dropped |
targets.claude.argumentHint |
argument-hint: frontmatter |
dropped |
targets.claude.arguments |
arguments: frontmatter |
dropped |
targets.claude.disableModelInvocation |
disable-model-invocation: frontmatter |
dropped |
targets.claude.userInvocable |
user-invocable: frontmatter |
dropped |
targets.claude.model |
model: frontmatter |
dropped |
targets.claude.effort |
effort: frontmatter |
dropped |
targets.claude.context |
context: frontmatter |
dropped |
targets.claude.agent |
agent: frontmatter |
dropped |
targets.claude.hooks |
hooks: frontmatter |
dropped |
targets.claude.paths |
paths: frontmatter |
dropped |
targets.claude.shell |
shell: frontmatter |
dropped |
content |
Markdown body | Markdown body |
As your config grows, split it across as many files as you like. The manifest controls what gets loaded.
# .ai/manifest.yml
version: 1
targets:
- claude
- copilot
imports:
- ./instructions/global.yml
- ./instructions/frontend/react.yml
- ./instructions/backend/api.yml
- ./agents/code-reviewer.yml
- ./agents/test-planner.yml
- ./skills/git-commit.yml
- ./fragments/coding-standards.ymlPass --discover to scan instructions/, agents/, skills/, and fragments/ automatically without listing every file in the manifest:
aio generate --discoverThis is useful for large repos. Be aware that discovery order is alphabetical within each directory.
Add validation and generation to your CI pipeline so generated files are always in sync.
name: Sync AI config
on:
push:
paths:
- '.ai/**'
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npx aio validate --strict
- run: npx aio generate
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'chore: sync AI config [skip ci]'
file_pattern: 'CLAUDE.md .claude/ .github/'- run: npx aio validate --strict
- run: npx aio generate --dry-runUse --dry-run to catch drift without writing files — useful when generated files are committed alongside source.
| Error | Cause | Fix |
|---|---|---|
Duplicate id "foo" |
Two objects share the same id |
Rename one of them |
Cannot read file: ./path.yml |
Import path is wrong or file is missing | Check the path in manifest.yml |
Missing required field |
A required field is absent | Add the field |
Unknown fragment reference: "foo" |
fragments: [foo] but no fragment with that id exists |
Define the fragment or fix the id |
Malformed YAML in ... |
YAML syntax error | Check indentation, quotes, colons |
Circular import detected |
manifest.yml imports itself or another manifest file |
Remove the circular import |
Duplicate import detected |
The same file is listed more than once in imports |
Remove the duplicate entry |
| Warning | Meaning |
|---|---|
Field "license" not supported by claude |
license on a skill will be dropped in Claude output |
Field "disallowedTools" not supported by copilot |
disallowedTools on an agent will be dropped in Copilot output |
Path-scoped instruction has no applyTo |
Instruction with scope: path but no glob patterns |
git clone https://github.com/your-org/aio
cd aio
npm install
npm run typecheck # type-check src + tests
npm run test:run # run test suite once
npm run test # run tests in watch mode
npm run build # compile to dist/
npm run format # format with prettiersrc/
types/index.ts ← canonical type system
index.ts ← public library exports
cli/
index.ts ← Commander CLI entry point
utils.ts ← shared CLI helpers
commands/
init.ts ← aio init
validate.ts ← aio validate
generate.ts ← aio generate
load/
yaml-loader.ts ← parse individual YAML files
import-resolver.ts ← resolve manifest imports, loadConfig()
validate/
schema.ts ← Zod schemas, validateSchema()
compatibility.ts ← validateTargetCompatibility()
utils.ts ← normalizeConfig()
render/
frontmatter.ts ← YAML frontmatter serialization helpers
targets/
claude/index.ts ← renderClaude()
copilot/index.ts ← renderCopilot()
__tests__/ ← mirrors src/ structure
load/
yaml-loader.test.ts
import-resolver.test.ts
validate/
schema.test.ts
utils/
normalize.test.ts
targets/
claude.test.ts
copilot.test.ts
snapshots.test.ts ← golden snapshot tests
integration/
generate.test.ts ← full pipeline end-to-end tests
fixtures/
minimal/ ← single global instruction, claude-only target
full/ ← all four object kinds, both targets, mixed fragments
agents-and-skills/ ← agents + skills only, no instructions
loadConfig()
└─ parseYamlFile(manifest.yml)
└─ resolveImports() → parseYamlFile() per import
mergeFragments() ← inline fragment content
normalizeConfig() ← normalize IDs, set defaults
renderClaude() ← produce RenderedFile[]
renderCopilot() ← produce RenderedFile[]
writeFiles() ← mkdir + writeFile per output
The manifest version field is reserved for future migrations. All current configs should use version: 1. When breaking schema changes are introduced, a migration guide will be published and the version number will increment.
The current release is deliberately narrow. Planned additions:
- Variables —
vars:in manifest,{{ language }}in content - Target overrides — per-target field values in a single definition
- Conditional rendering —
when: { target: claude } - Remote imports — fetch shared config from a URL or package
- More targets — Cursor, Windsurf, Continue.dev, Cline, Aider
MIT