Skip to content
Merged
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
6 changes: 4 additions & 2 deletions md/design/module-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Implements `cargo agents init`. Prompts for agents (or accepts `--add-agent`/`--

### `sync.rs` — synchronization command

Implements `cargo agents sync`. Scans workspace dependencies, finds applicable skills from plugin sources, and synchronizes them into each configured agent's skill directory. The core primitive is `sync_skill_dir(source_dir, dest_dir, project_root)`, shared by both the plugin-skill and user-authored-skill code paths. It copies the entire source directory (not just `SKILL.md`) and is change-aware: it compares source and destination content, only performing the delete-and-recopy when files actually differ, so the disk shows no modifications when nothing changed. A configurable debounce (`sync-debounce-secs`, default 5s, keyed on the `.symposium` marker's mtime) skips even the comparison for recently-synced skills. On each sync, scans every agent's skills parent directory and reaps any marker-bearing subdirectory it didn't install this time, leaving user-managed skills (which lack the marker) untouched. Writes a `.gitignore` with `*` only into individual skill directories (not parent directories like `.claude/` or `.claude/skills/`). Also provides `register_hooks()` for use by `init`, which registers only symposium's own global hook handler — individual plugin hooks are never written into agent configs.
Implements `cargo agents sync`. Scans workspace dependencies, finds applicable skills from plugin sources, and synchronizes them into each configured agent's skill directory. The core primitive is `sync_skill_dir(source_dir, dest_dir, project_root)`. It copies the entire source directory (not just `SKILL.md`) and is change-aware: it compares source and destination content, only performing the delete-and-recopy when files actually differ, so the disk shows no modifications when nothing changed. A configurable debounce (`sync-debounce-secs`, default 5s, keyed on the `.symposium` marker's mtime) skips even the comparison for recently-synced skills. On each sync, scans every agent's skills parent directory and reaps any marker-bearing subdirectory it didn't install this time, leaving user-managed skills (which lack the marker) untouched. Writes a `.gitignore` with `*` only into individual skill directories (not parent directories like `.claude/` or `.claude/skills/`). Also provides `register_hooks()` for use by `init`, which registers only symposium's own global hook handler — individual plugin hooks are never written into agent configs.

Two entry points: `sync(sym, cwd)` for standalone CLI use (creates its own `WorkspaceDeps`) and `sync_with_deps(sym, deps)` for the hook pipeline (shares the cached workspace resolution with other hook stages).

Expand All @@ -33,6 +33,8 @@ Scans configured plugin source directories for TOML manifests and parses them in

Also discovers standalone `SKILL.md` files not wrapped in a plugin. Returns a `PluginRegistry` — a table of contents that doesn't load skill content.

Workspace-scoped callers use `load_registry_with_workspace`, which additionally loads *workspace plugins* (`workspace_plugins`): the workspace root and every member directory each define a plugin when they carry a `SYMPOSIUM.toml` (validated with `ManifestOrigin::WorkspaceMember` — `name` defaults to the directory name, the every-plugin-must-mention-a-dependency rule is waived, and the default groups are appended unless `[defaults] skills = false`: `[[skills]] source.path = "skills"` plus, when the `agents-syncing` config is on, a `workspace-member()`-gated `[[skills]] source.path = ".agents/skills"` — the maintainer-skills convention, unified into the ordinary pipeline) or a bare `skills/` or `.agents/skills/` directory (an all-defaults manifest-less plugin). Workspace plugins are stamped `workspace_member = true` — the producer of the `workspace-member()` predicate — and attributed to the `"(workspace)"` source with skill paths relative to the workspace root.

### `installation.rs` — sources and acquisition

Defines `Source` (the `source = "..."`-tagged enum: `cargo`, `github`) and `acquire_source`, which downloads / installs / clones the source and returns an `AcquiredSource` whose `resolve_executable` / `resolve_script` methods turn a relative `executable`/`script` name into a concrete path. The `Runnable` enum (`Exec(PathBuf)` or `Script(PathBuf)`) is the final form a hook command resolves to. The `git` submodule handles GitHub tarball acquisition and caching.
Expand All @@ -54,7 +56,7 @@ Parses `[package.metadata.symposium]` from crate `Cargo.toml` files. Crate autho
Defines one `Predicate` enum covering both dependency-graph matching and runtime/environment gating, plus `PredicateSet` (a list ANDed together) and `PredicateContext` (the workspace dependency list it evaluates against). Two surface syntaxes lower to the same tree:

- The **`depends-on`** field uses dependency-atom syntax (`serde`, `serde>=1.0`, `*`) and lowers, via `DependsOnList`, to `depends-on(...)` / `depends-on(*)` predicates OR-combined into a single `any(...)` that is appended to the same list. So `depends-on` is sugar — there is no separate dependency-predicate type.
- The **`predicates`** field uses function-call syntax: `depends-on(<atom>)`, `shell(<cmd>)` (verbatim arg, `sh -c`, exit 0 holds), `path_exists(<arg>)` (disk, then `$PATH` for bare names), `env(<name>[=<value>])`, and the combinators `not(<p>)`, `any(<p>, …)`, `all(<p>, …)`. The retired `crate(...)` spelling is rejected with a migration hint, as are the old `crates` fields.
- The **`predicates`** field uses function-call syntax: `depends-on(<atom>)`, `shell(<cmd>)` (verbatim arg, `sh -c`, exit 0 holds), `path_exists(<arg>)` (disk, then `$PATH` for bare names), `env(<name>[=<value>])`, `workspace-member()` (the plugin is defined by a member of the active workspace — provenance stamped per plugin into `PredicateContext` via `ParsedPlugin::applies`; registry loading stamps false, workspace-plugin loading stamps true), and the combinators `not(<p>)`, `any(<p>, …)`, `all(<p>, …)`. The retired `crate(...)` spelling is rejected with a migration hint, as are the old `crates` fields.

Each gated struct (plugin, skill group, skill, hook, MCP server, subcommand) stores a single merged `predicates: PredicateSet`. Evaluation is `PredicateSet::evaluate(ctx) -> bool`. For `source = "crate"`, `witness` / `union_matched_packages` return the concrete crates that participate in a *satisfying* evaluation (the fetch set): `depends-on(c)` contributes `c` when present, `any` unions its true children, `all` unions all children when all hold, and `not` contributes nothing. `collect_dep_names` (crates.io validation) walks all positions regardless. Plugin/group/skill/MCP predicates are evaluated at sync time; hook dispatch evaluates the plugin-level set (so a plugin's `depends-on` now gates its hooks) plus the hook-level set. Hook dispatch threads in the workspace crate list, but resolves it (running cargo) only when some plugin- or hook-level predicate references a *concrete* `depends-on(...)` — wildcard and env/shell/path predicates dispatch without a cargo query. See the [predicates reference](../reference/predicates.md).

Expand Down
2 changes: 1 addition & 1 deletion md/design/sync-agent-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Scans workspace dependencies, installs applicable skills into agent directories,
- Drop a `.symposium` marker file into each installed skill directory so future syncs (and other tools) can recognize it as symposium-managed.
- For every skill directory symposium creates along the way (the skill directory itself or its `skills/` parent), write a `.gitignore` containing a single `*` so symposium-managed files stay out of version control.

6. **Propagate user-authored skills (agents-syncing)** — if `agents-syncing` is enabled in the user config, mirror skills the user placed in `<workspace>/.agents/skills/` into each configured agent's own skill directory. A skill is "user-authored" when its directory contains `SKILL.md` but lacks the `.symposium` marker (symposium never writes markers into source skills). Propagated destinations receive the same marker and `*` `.gitignore` as plugin-installed skills, so they participate in the normal stale-skill reap: removing the source — or disabling `agents-syncing` — causes the destinations to be cleaned up on the next sync. A destination directory without a marker is user-managed and is never overwritten.
6. **Workspace `.agents/skills/` (agents-syncing)** — not a separate step: when `agents-syncing` is enabled, each workspace plugin carries a `workspace-member()`-gated default group for `.agents/skills/`, so maintainer skills resolve and install through the same pipeline as everything else. Two marker guards make `.agents/skills/` safe as both a source and (for vendor-neutral agents) a destination: discovery skips `.symposium`-marked directories (installed copies are never sources), and a skill whose source already sits at an agent's install slot is skipped for that agent (no self-copy, no suffixed duplicate).

7. **Reap stale skills** — across every known agent's skills parent directory, remove any subdirectory that contains the `.symposium` marker but wasn't installed this sync. Directories without the marker (user-managed) are left untouched.

Expand Down
24 changes: 15 additions & 9 deletions md/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,30 @@ path = "my-plugins"
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `auto-sync` | bool | `true` | Automatically run `cargo agents sync` during hook invocations. When enabled, skills are kept in sync with workspace dependencies without manual intervention. |
| `agents-syncing` | bool | `true` | Propagate user-authored skills from `.agents/skills/` into the per-agent skill directories of any configured agent that does not natively use `.agents/skills/` (such as `.claude/skills/` or `.kiro/skills/`). Skills that symposium itself installed — identified by the `.symposium` marker file — are not propagated. See [Workspace skills](../workspace-skills.md) for the user-guide overview, or [Agents syncing](#agents-syncing-mirror-user-authored-skills) below for details. |
| `agents-syncing` | bool | `true` | Include each workspace plugin's `.agents/skills/` default skill group, so skills you author there install into every configured agent's skill directory (such as `.claude/skills/` or `.kiro/skills/`). Skills that symposium itself installed — identified by the `.symposium` marker file — are never treated as sources. See [Workspace skills](../workspace-skills.md) for the user-guide overview, or [Agents syncing](#agents-syncing-mirror-user-authored-skills) below for details. |
| `hook-scope` | string | `"global"` | Where agent hooks are installed. `"global"` writes to the user's home directory (e.g., `~/`). `"project"` writes to the project directory, keeping hooks local to the workspace. |
| `auto-update` | string | `"on"` | Controls automatic update behavior. `"off"` disables update checks entirely. `"warn"` checks the registry (at most once per 24 hours) and prints a message when a newer version is available. `"on"` automatically installs the update via `cargo install` and re-executes the command with the new binary. |

### Agents syncing: mirror user-authored skills

Agents such as Copilot, Gemini, Codex, Goose, and OpenCode all read skills from the vendor-neutral `.agents/skills/` directory, but Claude Code and Kiro use their own paths (`.claude/skills/` and `.kiro/skills/`). When `agents-syncing` is enabled, `cargo agents sync` mirrors any skill that *you* put in `.agents/skills/` into each configured agent's own skill directory, so a single authored copy is visible to every agent.
Agents such as Copilot, Gemini, Codex, Goose, and OpenCode all read skills from the vendor-neutral `.agents/skills/` directory, but Claude Code and Kiro use their own paths (`.claude/skills/` and `.kiro/skills/`). When `agents-syncing` is enabled, every [workspace plugin](../workspace-skills.md) — the workspace root and each member directory — carries a second default skill group, gated by the `workspace-member()` predicate:

A skill is treated as user-authored when its directory contains `SKILL.md` but does *not* contain the `.symposium` marker. Symposium never writes a marker into source skills, so the distinction between "user content" and "a copy symposium made" is unambiguous.
```toml
[[skills]]
predicates = ["workspace-member()"]
source.path = ".agents/skills"
```

Skills you author in `.agents/skills/` therefore flow through the same pipeline as every other skill and install into each configured agent's own skill directory, so a single authored copy is visible to every agent. The `workspace-member()` gate is what keeps these maintainer skills from installing for *dependents* of a published crate — they apply only while working in the workspace itself.

Two `.symposium`-marker rules keep sources and copies distinct (symposium never writes a marker into a source, only into directories it installs):

Propagated destinations receive the same `.symposium` marker and `*` `.gitignore` that plugin-installed skills get, which means:
- Skill discovery skips marker-bearing directories, so copies symposium installed into `.agents/skills/` (for agents that read it natively) are never re-discovered as sources.
- For an agent whose skill directory *is* `.agents/skills/`, a skill whose source already sits at its install slot is left in place — nothing is copied.

- Updates to the source (`.agents/skills/<name>/`) are re-copied on each sync.
- Removing the source removes the propagated copies on the next sync (via the normal stale-skill reap).
- Disabling `agents-syncing = false` also removes previously propagated copies on the next sync.
- A pre-existing, user-managed file in the target directory (no marker) is never overwritten.
Installed copies receive the same marker and `*` `.gitignore` that plugin-installed skills get, which means: updates to the source are re-copied on each sync; removing the source removes the copies on the next sync (the normal stale-skill reap); disabling `agents-syncing = false` does the same; and a pre-existing user-managed directory in a target is never overwritten (the skill installs under a suffixed name instead).

When the only configured agents use `.agents/skills/` directly, the feature is a no-op (the source and target are the same directory).
Because these are real skills now, `SKILL.md` frontmatter must carry `name` and `description` like any other [skill definition](./skill-definition.md).

### Hook scope: control whether Symposium activates in all projects or only those you select

Expand Down
1 change: 1 addition & 0 deletions md/reference/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The available predicate functions are:
| `path_exists(<arg>)` | `<arg>` resolves to an existing path. An argument with a path separator is checked on the filesystem (cwd-relative or absolute). A bare name with no separator is checked against the cwd and then searched on `$PATH`, so it matches either a local entry (`path_exists(.git)`) or an installed binary (`path_exists(rg)`). |
| `env(<name>)` | The environment variable `<name>` is set (to any value). |
| `env(<name>=<value>)` | `<name>` is set and equals `<value>` exactly. Only the first `=` separates name from value, so `env(KEY=a=b)` matches the value `a=b`. |
| `workspace-member()` | The plugin this predicate belongs to is defined by a member of the active workspace (a [workspace plugin](../workspace-skills.md)). Takes no argument. |
| `not(<predicate>)` | The inner predicate does **not** hold. The only way to express absence. |
| `any(<p>, <p>, …)` | At least one inner predicate holds (logical **OR**). |
| `all(<p>, <p>, …)` | Every inner predicate holds (logical **AND**). |
Expand Down
35 changes: 32 additions & 3 deletions md/workspace-skills.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Workspace skills

In addition to adding skills based on your dependencies, Symposium will also copy any additional skills found in `.agents/skills` into the directory appropriate for your configured agent(s).
In addition to adding skills based on your dependencies, Symposium will also install skills your workspace defines for itself: skills found in `skills/` or `.agents/skills` in the workspace root or any member crate install into the directory appropriate for your configured agent(s).

This "skill-syncing" feature allows your project to add skills in one central location that will work for all developers, regardless of which agent they use (for example, Claude Code users will have the skills synced to `.claude/skills`).
This allows your project to add skills in one central location that will work for all developers, regardless of which agent they use (for example, Claude Code users will have the skills synced to `.claude/skills`).

The default skill location therefore varies depending on the intended audience:

Expand All @@ -11,10 +11,39 @@ The default skill location therefore varies depending on the intended audience:
| Maintaining your crate | `.agents/skills` |
| [Using your crate](./crate-authors/supporting-your-crate.md) | `skills/` |

## Workspace plugins

The workspace root and every member crate directory can define a *workspace plugin*: add a `SYMPOSIUM.toml` manifest (see the [plugin definition](./reference/plugin-definition.md)), or just a bare `skills/` directory — a directory with skills and no manifest counts as a plugin whose only content is those skills.

Workspace plugins are always active while you work in that workspace — no plugin source configuration or `depends-on` gate is needed. A `skills/` directory in a member crate serves double duty: it installs for everyone working in the workspace *and*, once published, for projects that depend on the crate.

Every workspace plugin gets two default skill groups (unless disabled with `[defaults] skills = false`):

```toml
[[skills]]
source.path = "skills"

[[skills]]
predicates = ["workspace-member()"]
source.path = ".agents/skills"
```

The second group is how the `.agents/skills` convention works: it is gated by the [`workspace-member()` predicate](./reference/predicates.md), so maintainer skills apply while working in the workspace but never install for dependents of a published crate. (The group can also be turned off globally with `agents-syncing = false` in the [user config](./reference/configuration.md).)

Two details specific to workspace manifests:

- `name` may be omitted; it defaults to the directory name.

Components that should apply only to people developing the workspace (not to dependents of a published crate) can be gated with the [`workspace-member()` predicate](./reference/predicates.md).

## Informal skills

Workspace skills are your own notes, so the [skill frontmatter](./reference/skill-definition.md) requirements are relaxed: the `name` and `description` fields — and the frontmatter block itself — are optional. A `SKILL.md` that is just plain markdown works; its name defaults to the directory that contains it. Skills distributed through a registry or a published crate still require the full frontmatter.

## Recommended git setup

We recommend you commit your `.agents/skills` or `skills/` into the repository. Symposium installs a `.gitignore` file into every skill that it creates, so automatically copied and installed skills should not dirty your git status.

## Pre-existing files

Symposium never touches skills in `.claude/skills/`, `.kiro/skills/` etc. that it did not put there itself. If you previously hand-wrote a skill with the same name as one in `.agents/skills/`, propagation is skipped for that name and a warning is printed — your existing file stays in place.
Symposium never touches skills in `.claude/skills/`, `.kiro/skills/` etc. that it did not put there itself. If you previously hand-wrote a skill with the same name as one in `.agents/skills/`, your existing directory stays in place and the workspace skill installs under a suffixed name (`<name>-<hash>`) alongside it.
Loading
Loading