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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ cargo agents sync

Symposium reads the workspace dependency graph and evaluates each plugin's and skill's predicates against it. An extension is installed only when its predicates match.

- The `crates` field matches by crate name and version requirement (`serde`, `serde >= 1.0`, `*`).
- The `predicates` field uses function-call syntax (`crate(...)`, `shell(...)`, `path_exists(...)`, `env(...)`), combined with `not`, `any`, and `all`.
- The `depends-on` field matches by crate name and version requirement (`serde`, `serde >= 1.0`, `*`).
- The `predicates` field uses function-call syntax (`depends-on(...)`, `shell(...)`, `path_exists(...)`, `env(...)`), combined with `not`, `any`, and `all`.

When a skill group declares `source = "crate"`, Symposium fetches the matched crate's source (from the local path, the cargo registry cache, or crates.io), reads `[package.metadata.symposium]` from its `Cargo.toml` to locate the skills, and follows crate-to-crate redirects. See the [predicates reference](https://symposium.dev/reference/predicates.html).

Expand Down
2 changes: 1 addition & 1 deletion md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
- [Plugin definition](./reference/plugin-definition.md)
- [Symposium hook events](./reference/hook-events.md)
- [Skill definition](./reference/skill-definition.md)
- [Crate predicates](./reference/crate-predicates.md)
- [Dependency predicates (`depends-on`)](./reference/depends-on.md)
- [Predicates](./reference/predicates.md)
- [Contribution guide](./design/welcome.md)
- [Tenets](./design/tenets.md)
Expand Down
12 changes: 6 additions & 6 deletions md/crate-authors/authoring-a-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ Every plugin starts with a `SYMPOSIUM.toml` manifest uploaded to the [central re
```toml
# `my-crate/SYMPOSIUM.toml` on the symposium-dev/recommendations repository
name = "my-crate"
crates = ["my-crate"]
depends-on = ["my-crate"]
```

The `crates` field controls when the plugin is active — it will only load for projects that depend on the listed crates. Use `["*"]` to apply to all projects.
The `depends-on` field controls when the plugin is active — it will only load for projects that depend on the listed crates. Use `["*"]` to apply to all projects.

See the [plugin definition reference](../reference/plugin-definition.md) for the full manifest schema.

Expand Down Expand Up @@ -63,7 +63,7 @@ my-crate/
```toml
# `my-crate/SYMPOSIUM.toml` on the symposium-dev/recommendations repository
name = "my-crate"
crates = ["my-crate"]
depends-on = ["my-crate"]

[[skills]]
source = "crate"
Expand Down Expand Up @@ -102,19 +102,19 @@ And point the manifest at the local directory:

```toml
name = "my-crate"
crates = ["my-crate"]
depends-on = ["my-crate"]

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

Standalone skills **must** include `crates` in their frontmatter so Symposium knows which crate they apply to:
Standalone skills **must** include `depends-on` in their frontmatter so Symposium knows which crate they apply to:

```markdown
---
name: widgetlib-basics
description: Basic guidance for widgetlib usage
crates: widgetlib=1.0
depends-on: widgetlib=1.0
---

Guidance body here.
Expand Down
2 changes: 1 addition & 1 deletion md/crate-authors/writing-a-hook-handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ In your `SYMPOSIUM.toml`, reference the built binary as a hook command:

```toml
name = "my-crate"
crates = ["my-crate"]
depends-on = ["my-crate"]

[[hooks]]
name = "check-usage"
Expand Down
6 changes: 3 additions & 3 deletions md/design/important-flows.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This section describes the logic of each `cargo agents` command.

When a skill group uses `source = "crate"`, the sync flow takes an additional path:

1. `predicate::union_matched_crates()` resolves plugin-level and group-level predicates against the workspace to produce a set of concrete crate name/version pairs.
1. `predicate::union_matched_packages()` resolves plugin-level and group-level predicates against the workspace to produce a set of concrete crate name/version pairs.
2. For each crate in the set, `RustCrateFetch` fetches the source — checking path overrides (for local path deps), then the cargo registry cache, then crates.io.
3. `crate_metadata::parse_crate_metadata()` reads `[package.metadata.symposium]` from the crate's `Cargo.toml`:
- **No metadata** — fall back to the default `skills/` subdirectory.
Expand All @@ -15,7 +15,7 @@ When a skill group uses `source = "crate"`, the sync flow takes an additional pa
- **`crate = { name, version? }` entries** — redirect: fetch the target crate and follow its metadata recursively (with cycle detection and a depth limit of 10).
4. `discover_skills()` scans each resolved directory for `SKILL.md` files.

The key code paths are in `skills.rs` (`load_crate_skills`, `fetch_and_resolve_skills`), `crate_metadata.rs` (`parse_crate_metadata`), `predicate.rs` (`matched_crates`, `union_matched_crates`), and `crate_sources/mod.rs` (`RustCrateFetch`, `WorkspaceCrate`).
The key code paths are in `skills.rs` (`load_crate_skills`, `fetch_and_resolve_skills`), `crate_metadata.rs` (`parse_crate_metadata`), `predicate.rs` (`witness`, `union_matched_packages`), and `crate_sources/mod.rs` (`RustCrateFetch`, `WorkspaceCrate`).

## Help rendering

Expand All @@ -33,7 +33,7 @@ clap's auto help flag and help subcommand are disabled in `cli::Cli`; `--help`/`
When the user runs `cargo agents <name>` for a name not built into the binary, clap's `allow_external_subcommands` routes it to `Commands::External(argv)`.

1. The binary (or library `cli::run`) calls `subcommand_dispatch::dispatch_external(sym, cwd, argv)`.
2. `find_subcommand` walks the plugin registry. For each plugin it applies the plugin-level `crates` predicate against the workspace, then looks up `argv[0]` in `plugin.subcommands`. If the entry has its own `crates` predicate, that must also match. Two or more matches → error.
2. `find_subcommand` walks the plugin registry. For each plugin it applies the plugin-level `depends-on` predicate against the workspace, then looks up `argv[0]` in `plugin.subcommands`. If the entry has its own `depends-on` predicate, that must also match. Two or more matches → error.
3. The matched subcommand's `command` field names an `Installation` on the same plugin. `installation::resolve_runnable` acquires the source if any, runs `install_commands`, and picks the `Runnable` (`Exec` for binaries, `Script` for shell scripts).
4. The child is spawned with stdio inherited. Its exit code is collapsed to a `u8` — the binary wraps it in `ExitCode::from`; the library treats non-zero as an error so the test harness can assert on success/failure.

Expand Down
14 changes: 7 additions & 7 deletions md/design/module-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Two entry points: `sync(sym, cwd)` for standalone CLI use (creates its own `Work
Scans configured plugin source directories for TOML manifests and parses them into `Plugin` structs. Validation here turns the raw TOML into:
- `Installation` entries (optional `source`, optional `executable`/`script`, optional `args`, plus `requirements` and `install_commands`) collected on `Plugin.installations`. Inline installation references on hooks or other installations are *promoted* into synthetic `Installation` entries with derived names (`<hook>` for an inline `command`, `<owner>__req_<i>` for an inline requirement), so all references in the validated form are plain names.
- `Hook` entries with `command: String` (the name of an `Installation`) plus optional hook-level `executable` / `script` / `args`. Validation guarantees at most one of `executable`/`script` is set across hook + installation, and at most one layer sets `args`.
- `SkillGroup` and `PluginMcpServer` entries whose `crates` sugar and `predicates` list are merged into one runtime `PredicateSet`. Skill group `source` syntax is deserialized as raw string/table forms, then validated into `PluginSource`.
- `SkillGroup` and `PluginMcpServer` entries whose `depends-on` sugar and `predicates` list are merged into one runtime `PredicateSet`. Skill group `source` syntax is deserialized as raw string/table forms, then validated into `PluginSource`.

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

Expand All @@ -51,16 +51,16 @@ Parses `[package.metadata.symposium]` from crate `Cargo.toml` files. Crate autho

### `predicate.rs` — unified activation predicates

Defines one `Predicate` enum covering both crate-graph matching and runtime/environment gating, plus `PredicateSet` (a list ANDed together) and `PredicateContext` (the workspace crate list it evaluates against). Two surface syntaxes lower to the same tree:
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 **`crates`** field uses crate-atom syntax (`serde`, `serde>=1.0`, `*`) and lowers, via `CrateList`, to `crate(...)` / `crate(*)` predicates OR-combined into a single `any(...)` that is appended to the same list. So `crates` is sugar — there is no separate crate-predicate type.
- The **`predicates`** field uses function-call syntax: `crate(<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 **`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.

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_crates` return the concrete crates that participate in a *satisfying* evaluation (the fetch set): `crate(c)` contributes `c` when present, `any` unions its true children, `all` unions all children when all hold, and `not` contributes nothing. `collect_crate_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 `crates` now gate 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* `crate(...)` — wildcard and env/shell/path predicates dispatch without a cargo query. See the [predicates reference](../reference/predicates.md).
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).

### `skills.rs` — skill resolution and matching

Given a `PluginRegistry` and workspace dependencies, this module resolves skill group sources (fetching from git if needed), discovers `SKILL.md` files, and evaluates crate predicates at each level (plugin, group, skill) to determine which skills apply. For `source = "crate"` groups, resolves predicates to a matched crate set, fetches each crate's source via `RustCrateFetch`, reads `[package.metadata.symposium]` to determine skill paths, and follows redirects recursively with cycle detection and a depth limit of 10.
Given a `PluginRegistry` and workspace dependencies, this module resolves skill group sources (fetching from git if needed), discovers `SKILL.md` files, and evaluates dependency predicates at each level (plugin, group, skill) to determine which skills apply. For `source = "crate"` groups, resolves predicates to a matched crate set, fetches each crate's source via `RustCrateFetch`, reads `[package.metadata.symposium]` to determine skill paths, and follows redirects recursively with cycle detection and a depth limit of 10.

Each applicable skill carries a `SkillOrigin` describing *where its bytes live*, used at sync time for dedup and install-path disambiguation. What matters for identity is the on-disk location of the skill, not which plugin manifest pointed at it — two plugins in the same source pointing at the same skill bundle dedupe.
- `Crate { name, version }` — from a crate-source resolution (`source = "crate"`). Two `Crate` origins with the same `(name, version)` are the same logical skill, regardless of which plugin pointed at them.
Expand All @@ -71,7 +71,7 @@ Each applicable skill carries a `SkillOrigin` describing *where its bytes live*,

### `subcommand_dispatch.rs` — plugin-vended subcommands

Routes the `Commands::External` arm of clap's `allow_external_subcommands`. `find_subcommand` walks the `PluginRegistry`, applying plugin-level and subcommand-level crate predicates against the workspace, and returns the matched `(Plugin, Subcommand)` (or an error if more than one plugin claims the name). `dispatch_external` then looks up the named `Installation`, resolves it via `installation::resolve_runnable`, and spawns the child with stdio inherited — propagating the exit code as a `u8` so callers can convert to `ExitCode` (binary) or treat non-zero as an error (library). `applicable_subcommands` is the shared iterator over workspace-applicable plugin subcommands, reused by help rendering.
Routes the `Commands::External` arm of clap's `allow_external_subcommands`. `find_subcommand` walks the `PluginRegistry`, applying plugin-level and subcommand-level dependency predicates against the workspace, and returns the matched `(Plugin, Subcommand)` (or an error if more than one plugin claims the name). `dispatch_external` then looks up the named `Installation`, resolves it via `installation::resolve_runnable`, and spawns the child with stdio inherited — propagating the exit code as a `u8` so callers can convert to `ExitCode` (binary) or treat non-zero as an error (library). `applicable_subcommands` is the shared iterator over workspace-applicable plugin subcommands, reused by help rendering.

### `help_render.rs` — `--help` rendering

Expand Down
Loading
Loading