Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ path = "plugins/review-tools"
targets = ["claude", "cursor", "codex", "grok", "opencode", "pi"]
```

The canonical portable format is an [Agent Plugins](https://agent-plugins.org/) v1 bundle: required `plugin.json`, optional `skills/`, optional `mcp.json`, and reverse-domain client extensions. dotagents preserves those portable source files under `.agents/plugins/<name>/` and generates isolated target harnesses. OpenCode receives portable MCP servers under managed keys such as `plugin.<plugin>.<server>`; `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` are expanded into the installed bundle and persistent `.agents/plugin-data/` paths. Generated JSON uses adjacent ownership sidecars, while component symlinks use markers in reserved `.dotagents-managed/` directories, so client-owned JSON remains unchanged. Legacy generalized and native Claude/Cursor/Codex manifests remain discoverable during migration; native imports preserve their owning manifest and expose only core metadata and Agent Skills to other clients. Standard bundles reject legacy root components so client-specific behavior cannot leak across harnesses.
The canonical portable format is an [Agent Plugins](https://agent-plugins.org/) v1 bundle: required `plugin.json`, optional `skills/`, optional `mcp.json`, and reverse-domain client extensions. dotagents preserves those portable source files under `.agents/plugins/<name>/` and generates isolated target harnesses. OpenCode receives portable MCP servers under managed keys such as `plugin.<plugin>.<server>`; `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` are expanded into the installed bundle and persistent `.agents/plugin-data/` paths. Generated JSON uses adjacent ownership sidecars, while component symlinks use markers in reserved `.dotagents-managed/` directories, so client-owned JSON remains unchanged. Legacy generalized and native Claude/Cursor/Codex manifests remain discoverable during migration. A valid standard root may also coexist with authored native manifests as a hybrid compatibility bundle: the portable root remains the source of truth, reproducible native manifests are ignored in favor of portable generation, and manifests with behavior an adapter cannot represent are retained byte-for-byte only as matching-client fallbacks. Generated adapters are disposable output and are never imported back into the portable core. Native commands, agents, hooks, MCP, and other resources never leak into unrelated targets. Invalid standard roots still fail instead of falling back to legacy parsing.

Global plugins install canonical bundles under `~/.agents/plugins/`. Claude and Cursor marketplaces are generated under `~/.agents/`, the Codex marketplace is generated at `~/.agents/plugins/marketplace.json`, Grok plugins are copied into `~/.grok/plugins/`, OpenCode skills are linked into `~/.config/opencode/skills/`, portable MCP servers are merged into `~/.config/opencode/opencode.json`, and Pi skills are linked into `~/.agents/skills/`. `--user` remains a compatibility alias for `--global`.

Expand Down
6 changes: 3 additions & 3 deletions docs/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ Generated files include a dotagents header marker. `install` and `sync` overwrit

Each `[[plugins]]` entry requires `name` and `source`. Optional: `ref`, `path`, and `targets`. When `targets` is absent or empty, dotagents targets every agent listed in `agents`.

dotagents installs canonical plugin bundles under `.agents/plugins/<name>/`. New bundles use Agent Plugins v1: required `plugin.json`, optional `skills/`, optional `mcp.json`, and reverse-domain client extensions. Portable source files are preserved and target JSON remains client-native. OpenCode receives portable MCP servers under managed `plugin.<plugin>.<server>` keys, with `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` expanded into installed and persistent managed paths. Generated JSON uses adjacent ownership sidecars; component symlinks use marker files in reserved `.dotagents-managed/` directories. Standard bundles reject legacy root components so client-specific behavior cannot leak across harnesses. Native Claude/Cursor/Codex bundles may be imported conservatively: the owning native manifest is preserved, while other clients receive only core metadata and Agent Skills. Native MCP and client-specific components are not guessed or cross-translated.
dotagents installs canonical plugin bundles under `.agents/plugins/<name>/`. New bundles use Agent Plugins v1: required `plugin.json`, optional `skills/`, optional `mcp.json`, and reverse-domain client extensions. Portable source files are preserved and target JSON remains client-native. OpenCode receives portable MCP servers under managed `plugin.<plugin>.<server>` keys, with `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` expanded into installed and persistent managed paths. Generated JSON uses adjacent ownership sidecars; component symlinks use marker files in reserved `.dotagents-managed/` directories. Native Claude/Cursor/Codex bundles may be imported conservatively. A valid standard root may coexist with authored Claude, Cursor, or Codex manifests as a hybrid compatibility bundle: the portable root remains the source of truth, reproducible native manifests are ignored in favor of portable generation, and manifests with unrepresentable behavior are retained byte-for-byte only as matching-client fallbacks. Generated adapters are disposable and never become portable or fallback input. Native commands, agents, hooks, MCP, and other resources are not guessed or cross-translated. A malformed fallback fails preflight only when its client is selected; unselected fallbacks remain inert with warnings. Invalid standard roots never downgrade to native or generalized legacy parsing.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
Expand Down Expand Up @@ -470,7 +470,7 @@ Show trusted sources with their type. Use `--json` for machine-readable output.
npx @sentry/dotagents list [--json]
```

Show declared skills, plugins, and status. JSON output is an object with `skills` and `plugins` arrays.
Show declared skills, plugins, and status. Hybrid compatibility diagnostics appear as indented warning lines. JSON output is an object with `skills` and `plugins` arrays; plugin entries include a `warnings` array when diagnostics exist.

| Status | Meaning |
|--------|---------|
Expand All @@ -486,7 +486,7 @@ Skills from wildcard entries are marked with a wildcard indicator.
npx @sentry/dotagents doctor [--fix]
```

Check selected-scope health: gitignore setup where applicable, installed skills and plugins, plugin runtime projections, symlinks, legacy config fields, and legacy managed project hooks. Use `--fix` to auto-repair issues; project hook repair is `npx @sentry/dotagents --project doctor --fix`. Use `sync` in the same scope to repair generated runtime config drift.
Check selected-scope health: gitignore setup where applicable, installed skills and plugins, hybrid plugin compatibility diagnostics, plugin runtime projections, symlinks, legacy config fields, and legacy managed project hooks. Compatibility is reported as the `plugin compatibility` check. Use `--fix` to auto-repair issues; project hook repair is `npx @sentry/dotagents --project doctor --fix`. Use `sync` in the same scope to repair generated runtime config drift.

| Flag | Description |
|------|-------------|
Expand Down
31 changes: 24 additions & 7 deletions docs/src/content/docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,10 @@ dotagents doctor [--fix]
```

Check the selected scope and fix supported issues. Verifies installed skills
and plugins, plugin runtime projections, and legacy config fields. Project scope
also checks gitignore setup, symlinks, and managed hooks. Use `dotagents sync`
in the same scope to repair generated runtime configs.
and plugins, reports hybrid diagnostics in the `plugin compatibility` check,
checks plugin runtime projections, and detects legacy config fields. Project
scope also checks gitignore setup, symlinks, and managed hooks. Use
`dotagents sync` in the same scope to repair generated runtime configs.

Options:

Expand All @@ -336,9 +337,10 @@ dotagents doctor --fix # fix what it can
dotagents list [--json]
```

Show declared skills and plugins with install/lock status. Use `--json` for
Show declared skills and plugins with install/lock status. Hybrid plugin
diagnostics appear as warning lines below the plugin. Use `--json` for
machine-readable output. JSON output contains separate `skills` and `plugins`
arrays.
arrays; plugin entries include `warnings` when diagnostics exist.

Status output:

Expand Down Expand Up @@ -448,15 +450,30 @@ agents. Plugin sources support GitHub/GitLab shorthands, git URLs, and `path:`
sources; HTTPS well-known sources are not supported for plugins.

New plugin bundles follow Agent Plugins v1 with a required `plugin.json` and
optional `skills/` and `mcp.json`. dotagents preserves the raw bundle and
generates isolated target adapters from the portable core. Generated JSON uses
optional `skills/` and `mcp.json`. dotagents preserves the portable core and
generates isolated target adapters from it. Generated JSON uses
adjacent ownership sidecars; component symlinks use marker files in reserved
`.dotagents-managed/` directories. Client schemas receive no dotagents-only fields.
Legacy generalized and native Claude/Cursor/Codex manifests remain supported
during migration. Native imports preserve the owning manifest and expose only
core metadata and Agent Skills to other clients; native components are never
translated into unrelated clients' formats.

A valid standard root may coexist with authored `.claude-plugin`,
`.cursor-plugin`, or `.codex-plugin` manifests as a hybrid compatibility
bundle. Portable skills, MCP, and metadata remain shared; an authored manifest
is ignored when its behavior can be reproduced from the portable core. A
manifest with commands, hooks, custom fields, or other unrepresentable behavior
is retained byte-for-byte only as a matching-client fallback. Other clients
receive adapters generated solely from the portable core. Managed adapters are
disposable output and are never imported back into the portable bundle. Root
commands, agents, hooks, native MCP, and similar resources remain inert outside
their owning client. Invalid standard roots do not fall back to legacy parsing,
and a malformed native fallback blocks installation only when its client is
selected. `install` warns when redundant native input is normalized away;
`sync`, `list`, and `doctor` report durable retained-fallback state without
reinterpreting generated adapters as source input.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Plugin identifier. Lowercase letters, numbers, hyphens, and dots. |
Expand Down
6 changes: 6 additions & 0 deletions docs/src/content/docs/guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,11 @@ source = "getsentry/agent-pack"
targets = ["claude", "cursor", "codex", "opencode"]
```

Portable Agent Plugins may coexist with authored Claude, Cursor, or Codex
manifests during migration. The portable root remains the source of truth.
Dotagents ignores reproducible native manifests and retains only behavior it
cannot generate as a matching-client fallback; unrelated targets receive only
portable skills and MCP.

See the [CLI reference](/cli/#configuration-agentstoml) for all fields and
options.
27 changes: 27 additions & 0 deletions packages/dotagents/src/cli/commands/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { join } from "node:path";
import { tmpdir } from "node:os";
import { runDoctor } from "./doctor.js";
import { resolveScope } from "../../scope.js";
import { DOTAGENTS_NATIVE_FALLBACKS_MARKER } from "../../plugins/store.js";

describe("runDoctor", () => {
let tmpDir: string;
Expand Down Expand Up @@ -175,6 +176,32 @@ source = "path:external-review-tools"
expect(check?.message).toContain("Run 'npx @sentry/dotagents --project install'");
});

it("reports hybrid compatibility separately from runtime drift", async () => {
const pluginDir = join(projectRoot, ".agents", "plugins", "hybrid-tools");
await mkdir(join(pluginDir, ".claude-plugin"), { recursive: true });
await writeFile(join(pluginDir, "plugin.json"), JSON.stringify({
$schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
name: "hybrid-tools",
}));
await writeFile(join(pluginDir, ".claude-plugin", "plugin.json"), '{"name":"hybrid-tools"}');
await writeFile(join(pluginDir, DOTAGENTS_NATIVE_FALLBACKS_MARKER), "claude\n");
await writeFile(join(projectRoot, "agents.toml"), `version = 1
agents = ["claude"]

[[plugins]]
name = "hybrid-tools"
source = "org/hybrid-tools"
`);
await writeFile(join(projectRoot, ".gitignore"), "agents.lock\n.agents/.gitignore\n");
await writeFile(join(projectRoot, ".agents", ".gitignore"), "# managed\n");

const result = await runDoctor({ scope: resolveScope("project", projectRoot) });
const compatibility = result.checks.find((check) => check.name === "plugin compatibility");

expect(compatibility?.status).toBe("warn");
expect(compatibility?.message).toContain("hybrid compatibility bundle");
});

it("detects a missing agent skill symlink", async () => {
await writeFile(
join(projectRoot, "agents.toml"),
Expand Down
18 changes: 17 additions & 1 deletion packages/dotagents/src/cli/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export async function runDoctor(opts: DoctorOptions): Promise<DoctorResult> {
scope.pluginsDir,
config.plugins.filter((plugin) => !isSameProjectPluginConfig(plugin, scope.pluginsDir, scope.root)),
`${cmd} install`,
config.agents,
);
await writeAgentsGitignore(
scope.agentsDir,
Expand Down Expand Up @@ -266,7 +267,22 @@ export async function runDoctor(opts: DoctorOptions): Promise<DoctorResult> {
}

if (config.plugins.length > 0 && pluginErrors.length === 0) {
const installed = await loadInstalledPlugins(scope.pluginsDir, config.plugins, `${cmd} install`);
const installed = await loadInstalledPlugins(
scope.pluginsDir,
config.plugins,
`${cmd} install`,
config.agents,
);
const compatibilityWarnings = installed.plugins.flatMap(
(plugin) => plugin.compatibilityWarnings ?? [],
);
if (compatibilityWarnings.length > 0) {
checks.push({
name: "plugin compatibility",
status: "warn",
message: compatibilityWarnings.join(" "),
});
}
const runtimeIssues = installed.issues.length === 0
? await verifyPluginOutputs(config.agents, installed.plugins, pluginRuntimeLayout(scope), {
reservedMcpNames: config.mcp.map((server) => server.name),
Expand Down
Loading
Loading