-
Notifications
You must be signed in to change notification settings - Fork 18
Add shell-based predicates for plugin/hook/skill/mcp use #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # Shell predicates | ||
|
|
||
| A **shell predicate** is a shell command that decides whether a plugin, skill group, skill, hook, or MCP server should be active. Each predicate is run via `sh -c <command>`: | ||
|
|
||
| - exit `0` → the predicate **holds** | ||
| - any other exit (including spawn failure) → the predicate **fails**, and the enclosing item is skipped | ||
|
|
||
| Shell predicates compose with **AND** semantics within a list: every entry must hold. They compose with **AND** semantics across levels too, alongside the existing [crate predicates](./crate-predicates.md). Both kinds can be set independently. | ||
|
|
||
| ## When predicates are evaluated | ||
|
|
||
| Shell predicates are evaluated at the same point the workspace's crate predicates are evaluated for that item: | ||
|
|
||
| | Level | Evaluated | | ||
| |-------|-----------| | ||
| | Plugin `shell_predicates` | At sync (gates skills & MCP) and at every hook dispatch | | ||
| | Skill group `shell_predicates` | At sync, before any git/crates source is fetched | | ||
| | Skill frontmatter `shell_predicates` | At sync, after the skill loads | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand what "after the skill loads" means. My expectation is that these predicates would be used to decide whether to "install" the skill and make it available to the agent. After the skill loads sounds to me like it would be used when the agent is activating the skill or something like that. This suggests to me that we should make a 'lifecycle' page in the reference that gives standard terms we can reference. |
||
| | Hook `shell_predicates` | At hook dispatch, after the matcher passes | | ||
| | MCP server `shell_predicates` | At sync, when collecting servers to register | | ||
|
|
||
| Hook-level predicates run at dispatch (not sync) so they observe live state — e.g. a hook gated on `command -v jq` will silently disable itself if `jq` was uninstalled since the last sync, without forcing a re-sync. | ||
|
|
||
| > **Tip:** keep predicates **fast** and **side-effect free** (`command -v foo`, `test -f bar`, `test -d .git`). Plugin- and hook-level predicates fire on every hook dispatch. | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Plugin manifests (TOML) | ||
|
|
||
| ```toml | ||
| name = "my-plugin" | ||
| crates = ["*"] | ||
| shell_predicates = ["command -v rg", "test -f Cargo.toml"] | ||
|
|
||
| [[skills]] | ||
| crates = ["serde"] | ||
| shell_predicates = ["command -v jq"] | ||
| source = "crate" | ||
|
|
||
| [[hooks]] | ||
| name = "h" | ||
| event = "PreToolUse" | ||
| command = { script = "scripts/x.sh" } | ||
| shell_predicates = ["test -d .git"] | ||
|
|
||
| [[mcp_servers]] | ||
| name = "tool" | ||
| command = "/usr/local/bin/tool" | ||
| args = [] | ||
| env = [] | ||
| shell_predicates = ["command -v tool"] | ||
| ``` | ||
|
|
||
| ### Skill frontmatter (YAML) | ||
|
|
||
| Like `crates`, `shell_predicates` is **comma-separated** on a single line in SKILL.md frontmatter: | ||
|
|
||
| ```yaml | ||
| --- | ||
| name: my-skill | ||
| description: Skill that depends on ripgrep | ||
| crates: serde | ||
| shell_predicates: command -v rg, test -f Cargo.toml | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: If front-matter is YAML, should we make this a YAML array? (I suppose the same could apply to crates) It seems more natural to me. Perhaps the answer is that you should have an option. |
||
| --- | ||
| ``` | ||
|
|
||
| If you need commas inside a single command, declare the skill via a plugin manifest instead — the TOML array form supports arbitrary strings. | ||
|
|
||
| ## Example: gating a plugin on tool availability | ||
|
|
||
| ```toml | ||
| name = "uses-jq" | ||
| crates = ["*"] | ||
| shell_predicates = ["command -v jq"] | ||
|
|
||
| [[hooks]] | ||
| name = "format-json" | ||
| event = "PreToolUse" | ||
| command = { script = "scripts/format.sh" } | ||
| ``` | ||
|
|
||
| The hook here only registers if `jq` is on the user's `$PATH`. No error, no warning — symposium just skips this plugin's contributions while `jq` is missing. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is more "TOML-idiomatic"?