ai-canon
v0.3.0
Published
Sync skills, rules, MCP servers, and helper scripts from one trusted AI canon into every repo and coding agent.
Maintainers
Readme
Define your canon once (skills, rules, MCP servers, helper scripts) in a single git repo. Each consumer repo declares which subset it wants via a manifest. Developers run one command and get native config for the agents they actually use:
| Agent | Skills | MCP | Rules |
| --- | --- | --- | --- |
| Claude Code | .claude/skills/ | .mcp.json | Not applicable |
| Codex CLI | .agents/skills/ | .codex/config.toml | Not applicable |
| Cursor | .cursor/skills/ | .cursor/mcp.json | .cursor/rules/<ns>-rules.mdc |
| OpenCode | .opencode/skills/ | opencode.json | Not applicable |
(For rules, lean on AGENTS.md: every major agent reads it now. ai-canon fills the gaps that are still tool-specific.)
When to use this (and when not to)
Tools like rulesync and Ruler solve one repo → many agents and support far more tools than ai-canon does. If your canon lives happily inside a single repository, use them. Microsoft APM covers multi-repo distribution too, as a full package ecosystem with manifests, lockfiles, and org governance; if you want packages from many sources across many agents, use that.
ai-canon is the small version of that idea, one canon repo → many repos → many agents, with a few safety behaviors the bigger tools skip:
- A central, versioned canon repo owned by your platform/AI-enablement folks
- Per-repo manifests: the backend repo gets backend skills, the frontend repo gets frontend skills, everyone gets the common set
- A CI-friendly drift check (
ai-canon doctor --check) so repos notice when the canon moved - Ownership safety: generated files carry a marker and are never silently overwritten if hand-authored; stale skills are cleaned up only within your namespace
- Secrets hygiene:
${VAR}placeholders in MCP config resolve from the developer's environment; servers with missing values are skipped and reported, never written with unresolved placeholders
Quick start
1. Author a canon (once per team)
mkdir our-ai-canon && cd our-ai-canon && git init
npx ai-canon init canon
git add . && git commit -m "our AI canon" && git pushThis scaffolds:
canon.json name, namespace, defaults
canon/
skills/common/*.md skills (SKILL.md format + frontmatter)
mcp/common.json MCP server catalog (${VAR} placeholders allowed)
rules/common.md rules content (for agents without AGENTS.md support)
scripts/ helper scripts installed to .ai/scripts/
manifests/
<repo-name>.json one manifest per consumer repoThe scaffold includes manifests/example.json. Copy it once per consumer repo and edit the selected content:
cp manifests/example.json manifests/your-app.json
# Edit "repo" to "your-app", then select the skills/rules/MCPs/scripts it needs.
git add manifests/your-app.json && git commit -m "add your-app manifest" && git pushThe filename must match the consumer's repo value (normally its directory name).
2. Wire up each consumer repo
cd your-app
npx ai-canon init --canon [email protected]:your-org/our-ai-canon.git
git add .ai.yaml .gitignore && git commit -m "adopt ai-canon"3. Every developer, any machine
npx ai-canon syncInteractive on a TTY (pick agents, skills, MCPs); fully scriptable otherwise:
npx ai-canon sync --agent claude,cursor --no-interactive
npx ai-canon sync --agent codex --skill acme-test --mcp context7A successful first sync is explicit about every local file it created:
ai-canon [acme]: your-app -> claude, codex
Changes (5)
created .claude/skills/acme-test/SKILL.md
created .agents/skills/acme-test/SKILL.md
created .mcp.json
created .codex/config.toml
created .ai.lock.jsonThe canon is cloned/fetched into the repo-local, gitignored .ai/.canon on every run, so updating everyone is just git push to the canon repo; developers pick it up on their next sync.
4. Keep repos honest in CI
npx ai-canon doctor --check # exit 1 if generated files drifted from the canonConcepts
canon.json (canon repo root)
{
"name": "acme",
"namespace": "acme",
"generatedNotice": "GENERATED FILE. Do not edit directly. Run: ai-canon sync"
}namespace scopes every canon skill name to <namespace>-*. Stale cleanup also requires ai-canon's structured ownership marker, so hand-authored skills are left alone; the marker lets owned cleanup remain correct if a canon deliberately changes namespace.
Skills
Standard SKILL.md files with a little extra frontmatter:
---
name: acme-open-pr
description: Open a PR following Acme conventions. Use when the user says "open a PR".
agents: [claude, codex] # optional, default: all
default: false # optional, opt-in skill (default: true)
requires-env: [GITHUB_TOKEN] # optional, skipped + reported if missing
---
Instructions...MCP catalog
{
"mcpServers": {
"context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp"] },
"jira": {
"command": "uvx",
"args": ["mcp-atlassian"],
"env": { "JIRA_API_TOKEN": "${JIRA_API_TOKEN}" },
"agents": ["claude", "codex"],
"default": false
}
}
}Placeholders resolve from the process environment or the gitignored .ai.local/env. Developers can add personal servers in .ai.local/mcp.json; they merge on top of the canon.
Manifests (one per consumer repo)
{
"version": 1,
"repo": "backend",
"skills": ["common/*.md", "common/acme-jira-*.md", "backend/*.md"],
"rules": ["common.md", "backend.md"],
"mcp": ["common.json", "backend.json"],
"scripts": ["open-pr/*"],
"defaultAgents": ["claude", "codex"]
}Consumer .ai.yaml
canon: [email protected]:your-org/our-ai-canon.git # or a relative path
repo: backend # manifest name; defaults to the directory name
canonRef: v1.2.0 # optional pin (tag, branch, or commit; default: remote default branch)Safety rules
- Treat the canon repository as trusted code. It distributes agent instructions, MCP commands, and executable helper scripts. Review canon changes like application code and protect who can push to it.
- Generated files are local developer state: keep them gitignored (
initsets this up). They may contain resolved secrets. Sync refuses secret-bearing generated config that is already tracked by Git; remove it from the index withgit rm --cached <path>before continuing. - Every generated file carries a structured
[ai-canon:owned]marker. Files at managed destinations (including skills, scripts, and root agent configs) are overwritten only when that marker or a legacy 0.1 marker proves ownership. Otherwise, sync refuses before changing anything. Override deliberately with--force. - Stale skills are removed only when ownership is proven (legacy 0.1 files additionally require the current namespace prefix). Stale owned scripts are cleaned only under
.ai/scripts/. - Manifest paths are confined to their canon directories, and generated destinations are confined to the consumer repo. Absolute paths, traversal, backslashes, and escaping symlinks are rejected.
- MCP servers with unresolved
${VAR}placeholders are skipped and reported, never written. - Secret-bearing root MCP files are mode
0600on POSIX. Git URLs are redacted in errors and lockfiles, but credentials should come from your Git credential manager rather than being embedded in URLs. - Sync validates and plans the whole operation before writing. Guard conflicts make no changes; a later filesystem failure rolls the entire plan back, and individual file replacement is atomic.
Local canon development
AI_CANON_SOURCE=../our-ai-canon npx ai-canon sync --no-interactive # use a local checkout
AI_CANON_REF=origin/my-branch npx ai-canon sync # test a canon branch.ai.lock.json records which canon commit/content generated the current files (audit only).
Recovery and rollback
- Preview drift without changing files:
npx ai-canon doctor --check. - Inspect the configured source, manifest, pending changes, removals, and conflicts:
npx ai-canon doctor. - Roll back by setting
canonRefin.ai.yamlto a known tag or commit, then runnpx ai-canon sync --no-interactive. - Return to the remote default branch by removing
canonRefand syncing again. - If a generated root config was intentionally replaced by hand-authored content, sync stops before modifying anything. Move personal MCP entries to
.ai.local/mcp.json, or review the replacement and use--forceonce. - If
.ai/.canonis damaged, remove only that gitignored cache directory and sync again. ai-canon also replaces it automatically when the configured canon URL changes.
doctor --check reports writes, updates, removals, conflicts, and lockfile drift, and exits 1 whenever the next sync would change local state.
Development
pnpm install
pnpm check # typecheck
pnpm test # e2e CLI tests
pnpm build # bundle dist/cli.mjsReleasing
Maintainers configure the repository's NPM_TOKEN secret once, bump package.json, update CHANGELOG.md, and push a matching v<version> tag. The release workflow reruns checks, publishes with npm provenance, and creates GitHub release notes. A mismatched tag fails before publishing.
License
MIT
