Project a canonical .agents/ directory into per-AI-tool config files.
Write your project context, skills, commands, subagents, hooks, permissions,
and MCP server config once. prism compiles them into the right shape
for Claude Code, Cursor, Gemini CLI, GitHub Copilot, Cline, Continue,
Windsurf, and AGENTS.md — preserving native semantics where the tool
supports them and degrading visibly where it doesn't.
$ prism compile
✓ AGENTS.md (agents-md, write)
✓ CLAUDE.md → .agents/context.md (claude, symlink)
✓ src/billing/CLAUDE.md → ../../.agents/... (claude, symlink, cascade)
✓ .claude/skills/pdf-editing/SKILL.md (claude, symlink)
✓ .claude/settings.json (claude, merge; preserves user keys)
✓ .claude/hooks/__scope-guard__/api-validate.sh (claude, write; wrapper)
✓ .mcp.json (claude, merge)
✓ .cursor/rules/_root.mdc (cursor, write)
✓ .cursor/rules/src-billing.mdc (cursor, write; globs frontmatter)
✓ .cursor/mcp.json (cursor, merge)
✓ GEMINI.md → .agents/context.md (gemini, symlink)
✓ .gemini/settings.json (gemini, merge)
✓ .gemini/hooks/__perms-guard__/policy.json (gemini, write; perms wrapper)
✓ .clinerules/00-context.md (cline, write)
✓ .continue/rules/_root.md (continue, write)
✓ .continue/mcpServers/linear.yaml (continue, write)
✓ .windsurf/rules/_root.md (windsurf, write; trigger frontmatter)
✓ .github/copilot-instructions.md (copilot, symlink)
✓ .github/instructions/src-billing.instructions.md (copilot, write; applyTo)
✓ .github/prompts/review.prompt.md (copilot, write)
Compiled: 38 changed, 0 unchanged, 0 removed, 9 warnings
Pre-built binaries for darwin/amd64, darwin/arm64, linux/amd64, linux/arm64, and windows/amd64 are on the releases page. SHA-256 sidecars next to each binary.
# darwin/arm64 example
curl -L -o prism https://github.com/os-tack/prism/releases/latest/download/prism-v0.8.2-darwin-arm64
chmod +x prism
./prism --help
Or build from source:
git clone https://github.com/os-tack/prism
cd prism
go build -o prism ./cmd/prism
cd your-project
prism init # scaffold .agents/ + agents.config.yaml
prism compile # generate the projections
prism check # verify (use in CI; exit 1 on drift)
prism watch # rebuild on .agents/ changes
Already using one of the supported tools? Import your existing config:
prism init --from claude # also: cursor, gemini, cline, continue, windsurf, copilot, agents-md
prism init --from cursor -i # interactive: pick which rules to import
Browse examples/ for runnable .agents/ layouts.
.agents/
context.md # root context, projected to CLAUDE.md / AGENTS.md / GEMINI.md / ...
agents.config.yaml # target enablement + per-target sync mode
<path>/context.md # nested scope: src/billing/context.md → src/billing/CLAUDE.md (Claude cascade)
<path>/scopes.yaml # optional: description, priority, custom globs
skills/<name>/
SKILL.md # frontmatter: description, trigger, globs, allowed-tools
scripts/* # optional executables (Claude symlinks them; Cursor warns; etc.)
commands/<name>.md # slash-command analog
agents/<name>.md # subagent definitions
hooks/<name>.yaml # { event, matcher, script }
permissions.yaml # { allow: [...], deny: [...], ask: [...] }
mcp.yaml # { servers: { <name>: { command, args, env, url } } }
<path>/permissions.yaml # scoped: applies under <path>/ only
<path>/hooks/<name>.yaml # scoped hooks (Claude gets a scope-guard wrapper)
<path>/skills/<name>/ # scoped skills
shared/<file>.md # snippet library for @include composition
<!-- include: path/to/file.md --> inside a context.md body splices
the file's content in. Includes are recursive (depth-limited) and contribute
to the source trace. Currently expanded only in context.md files (root and
scoped); SKILL.md, commands/*.md, and agents/*.md use a no-expand
read path.
prism's canonical model is versioned. v0.9.0 ships schema v2 — the
contract that the rest of the v0.9 → v1.0 line is committed to. Every
agents.config.yaml MUST declare schema_version: 2 at the top; v0.9
reads only 2, and a forward-incompat value (e.g. 3) is a hard error
with an upgrade message.
The full spec — seven canonical primitives, polymorphic activation,
dimension-aware permissions, the MCP transport/auth/policy surface, the
hook event taxonomy, the extensions: block, JSON Schema pointers, and
the per-field × per-plugin capability matrix — lives in
SPEC.md. The v0.9 line is the soak cycle before v1.0
declares stability per SPEC §11.2.
JSON Schemas for editor integration live at schema/v2/. Each example
under examples/ ships a # yaml-language-server: $schema=... hint
at the top so editors auto-resolve.
PLUGIN CONTEXT PATHS SEMANTIC SKILLS CMDS AGENTS HOOKS PERMS MCP
agents-md native degr. degr. degr. degr. degr. degr. degr. degr.
claude native native degr. native native native native native native
cline native native native degr. native ---- native native native
continue native native native degr. native ---- native native native
copilot native native degr. degr. native native native* native* native
cursor native native native native native native native ---- native
gemini native native degr. degr. native native native native native
windsurf native native native degr. degr. ---- native ---- native
- native: 1:1 mapping; full fidelity.
- native* (copilot Hooks + Perms): native projection, opt-in via
--enable-preview-hooksbecause the underlying Copilot hook API is in public preview at the GitHub side. Default OFF; flip on per-run or in CI. - All v0.9.0 Phase 2.5 plumbing items (continue hooks-native, claude permission rule fan-out, MCP transport-aware wire shape, cline filename-dispatch hooks, cursor cross-emit dedup, gemini per-action event translation, AGENTS.override.md split) landed in the v0.9.0 line. See CHANGELOG.md for the per-cell breakdown. Remaining out-of-scope items (rate-limiting, http handler shimming, single-tool events, etc.) are listed under "Still deferred".
- degr. (degraded): approximated in the target's nearest equivalent; some semantics lost. The plugin emits an info warning explaining what was lost.
- ---- (unsupported): not projected. Plugin emits a warning naming the dropped item.
Schema v2 is in soak cycle for the v0.9 line — additive changes resume at v0.10 once the contract test suite has run against the registry and any third-party plugins. The fine-grained per-field capability matrix (which fields are silent vs unsupported on which plugins) lives in SPEC.md §12.
As of v0.9.0, the schema v2 canonical contract is live and every plugin honors it in wire form — Continue HOOKS flipped from ---- to native via the shared Claude-shape serializer, cline hooks rewrote to filename-dispatch scripts per SPEC §4.4.5, claude permissions fan out through the dimension-aware grammar, MCP servers carry full transport + auth + 6 policy fields. The 218-entry Phase 2.5 punch list in the capability contract test (plugins/capability_contract_test.go) is empty after Phase 2.6 — every (plugin × primitive × field) cell now passes the strict contract. See CHANGELOG.md v0.9.0 for the per-cell breakdown.
Show the matrix with prism capabilities.
prism init --from <tool> reads the tool's native config and writes the
canonical .agents/ shape. Round-trip tested (init --from X then
compile reproduces the original).
| Source | Importer |
|---|---|
CLAUDE.md + .claude/ |
claude |
.cursor/rules/*.mdc |
cursor |
GEMINI.md + .gemini/ |
gemini |
.clinerules/ |
cline |
.continue/rules/ |
continue |
.windsurf/rules/ |
windsurf |
.github/instructions/ |
copilot |
AGENTS.md |
agents-md |
Pass -i / --interactive to pick which imported items make it into
.agents/ (per skill, command, scope, MCP server, agent):
prism init --from cursor -i
# include skill `pdf-editing`? [Y/n/a/d] y
# include scope `src/billing`? [Y/n/a/d/s] s # keep scope, skip its skills
# include MCP server `linear`? [Y/n/a/d] a # accept all remaining
EOF on stdin (Ctrl-D) auto-accepts the rest. Non-TTY stdin is refused loudly rather than silently filtering nothing in CI.
~/.agents/ is your personal global layer. Project .agents/ is project-specific.
~/.agents/
agents/reviewer.md # your personal code reviewer
skills/code-review/ # your house style
permissions.yaml # always-on Bash allowlist
your-project/.agents/
context.md # project-specific context
src/billing/context.md # project-specific scope
Compile merges them: project wins on collision for scopes / skills / commands / agents / MCP (matched by name); permissions are unioned; hooks are concatenated; Context falls back to global only if project has none.
Auto-detected when ~/.agents/ exists. Override:
prism compile --global /other/path
prism compile --no-global # ignore the global layer
Lockfile and prism which show clean tagged sources so you can tell what
came from where:
$ prism which AGENTS.md
context.md
global:agents/reviewer.md
global:skills/code-review/SKILL.md
src/billing/context.md
Each plugin emits one of four op kinds per file:
symlink— used when the projected content is identical to the source (e.g.,CLAUDE.md → .agents/context.md). Zero runtime cost; source-of-truth visible in the tree. On Windows whereos.Symlinkrequires admin/dev mode, symlinks auto-downgrade to content writes.write— used when transformation is needed (frontmatter injection, glob translation, section sectioning). Output is committed.merge— used forsettings.json,.mcp.json, etc. The engine reads the existing file's bytes, the plugin'sMergerclosure produces the merged content, and the engine writes the result. Preserves your manual edits to keys the plugin doesn't own.append— used for hook lists and similar additive cases.
Per-target override in .agents/agents.config.yaml:
targets: [claude, cursor, agents-md] # or omit to autodetect
target_options:
claude:
mode: write # force write instead of default symlink
cursor:
disabled: true # skip even if autodetectedTwo enforcement concepts — path-scoping and permission policies — are
spec'd canonically in .agents/ but not every tool has the native
runtime surface to enforce them. prism ships two hidden subcommands
(prism scope-guard, prism perms-guard) that fill the gap as
generated bash wrappers. Plugins emit a wrapper alongside each hook
that needs the gate; the wrapper calls back into the prism binary at
runtime.
When a hook lives under a scope (.agents/src/billing/hooks/...) but
the target tool fires hooks globally, the plugin emits:
.{plugin}/hooks/__scope-guard__/<scope>-<event>-<hook>.sh
The script reads the tool's hook JSON from stdin, extracts
tool_input.file_path, and invokes the user's hook only if the path
falls under the scope. Used by every tool whose hook contract is
JSON-on-stdin with exit-2-blocks semantics:
| Plugin | scope-guard | Native scoping? |
|---|---|---|
| claude | yes | no |
| cursor | yes | no |
| gemini | yes | no |
| cline | yes | no |
| windsurf | yes | no |
| copilot | preview-on | preview-on |
| continue | — | folded (warn) |
| agentsmd | — | (no hook primitive) |
Sidecar at .{plugin}/hooks/__perms-guard__/policy.json; wrapper at
.{plugin}/hooks/__perms-guard__/<scope>.sh. The wrapper reads the
hook JSON, consults the policy, and either execs the underlying
script (Allow), exits non-zero (Deny), or prompts on TTY (Ask).
Policy rules use the canonical SPEC §4.6.2 grammar: <target>:<pattern>
plus the dimension synonyms fs:, network:, mcp:<server>[:<tool>],
recursive ** glob, and ! negation prefix. Deny dominates Allow,
which dominates Ask. In non-TTY contexts, Ask degrades to Deny.
| Plugin | perms-guard | Native? |
|---|---|---|
| gemini | yes | no |
| cline | yes | no |
| copilot | yes (preview-off) | yes (preview-on, opt-in) |
| claude | — | .claude/settings.json |
| continue | — | .continue/permissions.yaml |
| cursor | — | (folded into sandbox profile) |
| windsurf | — | unsupported |
| agentsmd | — | informational text only |
Plugins with native enforcement translate the canonical
.agents/permissions.yaml directly to their wire format and don't go
through perms-guard at all.
Both wrappers resolve the project root at runtime via:
${PRISM_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$(cd "${SCRIPT_DIR}/../../.." && pwd)}}
so the generated scripts survive mv of the project directory. Pass
--no-hook-wrappers to prism compile to disable wrapper generation
and emit project hooks as global instead.
prism add <name> resolves bare names through a JSON index instead of
requiring a full host/owner/repo URL:
prism add billing-skills # resolves via the registry index
prism add github.com/you/my-skills # explicit git URL still works
prism add ./local/path # local path still works
Default index URL is
https://raw.githubusercontent.com/os-tack/prism-registry/main/index.json;
override with --registry <url> or PRISM_REGISTRY env var. Cache lives
at os.UserCacheDir()/prism/registry-index.json with a 24h TTL. Flags:
--registry, --refresh-registry, --no-fetch.
- run: prism checkprism check exits non-zero if the committed projections do not match what
.agents/ would produce. Drives the "source-of-truth" invariant: edit
.agents/, never the generated files.
If you do edit a generated file manually, the next prism compile warns:
✓ AGENTS.md (agents-md, write, 1391 bytes)
⚠ [warn] AGENTS.md: manual edits detected; will be overwritten by agents-md
prism knows because the lockfile records the SHA-256 of every file it
wrote; the warning fires when the on-disk hash differs from both the
lockfile hash and the new planned content.
prism init [--from <tool>] [-i] # scaffold or import; -i for interactive
prism compile [--target X] [--dry-run] [--quiet]
prism check # CI-friendly; exits 1 on drift
prism diff # show what compile would change
prism which <projected-file> # reverse-trace: which .agents/ source produced this
prism watch # rebuild on .agents/ changes (debounced; clean SIGINT)
prism capabilities [--target X] # capability matrix
prism add <name|url|path> [--ref X] # install a package into .agents/
prism remove <name> # uninstall a package
prism list # show installed packages
Global flags:
--root <dir>— project root (default: current directory)--global <dir>— global layer parent (default:~/if~/.agents/exists)--no-global— skip the global layer--no-hook-wrappers— disable scope-guard / perms-guard wrapper generation--enable-preview-hooks— opt into Copilot preview hooks (.github/hooks/hooks.json + perms-guard wiring); off by default until GA
.agents/ + ~/.agents/ [ParseLayered → *model.Project]
│
▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Parse │───▶│ Plan │───▶│ Diff │───▶│ Apply │
│ + @incl │ │ per-plugin│ │ vs disk │ │ (or DR) │
└─────────┘ └──────────┘ └──────────┘ └────┬─────┘
│
▼
.agents/.lock
(sources, plugin, kind, hash)
Plugins are pure: given a *model.Project, they return []plugin.Operation.
The engine owns all filesystem I/O — including reading existing bytes for
OpMerge ops, which the plugin's Merger closure transforms into the merged
content. This contract means plugins are testable without a filesystem and
manual-edit detection is deterministic (the merge happens exactly once per
compile).
The lockfile tracks every file written: sources (with global: prefix for
the personal layer), plugin name, op kind, SHA-256 hash. Stale entries get
cleaned up on the next compile. Lockfile paths are forward-slashed
engine-side so they're portable across macOS/Linux/Windows.
See CHANGELOG.md for full release notes. Highlights:
- v0.9.0 — canonical schema v2: seven primitives, per-field
capability declarations, polymorphic skill activation,
dimension-aware permissions (
fs:/network:/mcp:/**/!), explicit MCP transport+auth+policy surface, 25 canonical hook events, project-wideextensions:block,Validate()pass with--strict, JSON Schema generation atschema/v2/, soak cycle for v1.0 - v0.8.2 — copilot HOOKS (preview, opt-in), copilot PERMS, cline PERMS; three more cells flipped to native (issues #2/#3/#4)
- v0.8.1 — importer parity for the v0.8.0 emissions; round-trip lock-in
- v0.8.0 — major plugin parity release: 17 capability cells flipped to native across cursor / gemini / cline / continue / copilot / windsurf
- v0.7.x — central registry,
--interactiveimporters, perms-guard wrappers, OpMerge contract refactor, round-trip test coverage, path-portable wrappers, version package, nit sweep - v0.6.0 — Windows path handling, per-file hashes in
packages.yaml, importer round-trip tests,--no-hook-wrappers - v0.5.0 — 8 importers,
@includedirective, skill registry, scoped capabilities - v0.4.0 — initial release: 8 plugins, layered config, drift detection
MIT. See LICENSE.