Add custom predicate extensions via [[predicate]]#238
Merged
Conversation
nikomatsakis
commented
Jun 9, 2026
Plugin authors can now define custom predicate functions that extend the predicate language. When a crates expression uses an unknown function name (e.g., `battery-pack(cli>=0.3)`), Symposium looks it up in the custom predicate registry and spawns the declared command with the raw argument appended. Key additions: - Predicate::Custom variant with balanced-paren parsing - [[predicate]] section in plugin TOML manifests - CustomPredicateEvaluator with per-sync caching - Witness support (stdout JSON with selectedCrates) - Runnable::spawn helper on symposium-install - Full sync integration (predicates resolved before evaluation) - Integration tests and reference documentation Co-authored-by: Claude <claude@anthropic.com>
9640139 to
80f057b
Compare
Move the CustomPredicateEvaluator's entries and cache directly into PredicateContext, making evaluate()/witness() take &mut self. This eliminates the separate _with_evaluator function variants and fixes a bug where group-level custom predicates were silently evaluated as false (because load_skills_for_group called evaluate() without the external evaluator). Co-authored-by: Claude <claude@anthropic.com>
- Extract `validate_custom_predicate_name` as a single public helper in predicate.rs, enforcing `[a-zA-Z][a-zA-Z0-9_]*` + no builtin collision. Used by both the expression parser and plugin manifest validation. - Fix CrateList::parse to use split_top_level (paren-aware comma split) so custom predicates like `bp(a, b)` in SKILL.md frontmatter are not incorrectly split on the inner comma. - Add collision tests for build_custom_predicate_registry (two-way and three-way). Co-authored-by: Claude <claude@anthropic.com>
The `crates` field should only accept crate-name atoms (e.g. `serde`, `serde>=1.0`, `*`). Full predicate expressions including custom predicates belong in the `predicates` field. This matches the design decision from the Zulip discussion: the fields serve distinct roles and should not accept each other's syntax. - AtomParser now errors on `(` instead of parsing function calls - Remove dead `consume_balanced_parens` from AtomParser - Fix test fixture to use `predicates` instead of `crates` - Update CrateList tests to assert rejection Co-authored-by: Claude <claude@anthropic.com>
Assert that the `crates` field rejects all function-call forms (all(), crate(), not(), shell()) and that the `predicates` field rejects bare crate-atom syntax (serde, tokio>=1.0, *). Co-authored-by: Claude <claude@anthropic.com>
The custom predicate tests used Runnable::Exec with hardcoded /bin/true and /bin/false paths which may not exist on all CI environments. Replace with Runnable::Script using tempfile shell scripts that explicitly `exit 0` or `exit 1`. Co-authored-by: Claude <claude@anthropic.com>
nikomatsakis
commented
Jun 9, 2026
…ource
Address PR review feedback:
- Add comment to fixture explaining the script is test-generated
- Add test that verifies the predicate argument ("cli") is passed correctly
- Add test showing wrong argument causes predicate failure
- Add witness integration test: custom predicate returns selectedCrates
JSON, which drives `source = "crate"` skill resolution from a path dep
Co-authored-by: Claude <claude@anthropic.com>
nikomatsakis
commented
Jun 9, 2026
Previously, invalid JSON on stdout was silently ignored (predicate still passed with no witness). Now non-empty invalid stdout causes the predicate to evaluate as false, since the command likely has a bug. Empty stdout remains valid (pass with no witness crates). Co-authored-by: Claude <claude@anthropic.com>
Any invalid entry in selectedCrates (e.g. bad semver) now fails the whole predicate rather than silently skipping it. Consistent with the principle that non-empty invalid stdout means the command has a bug. Co-authored-by: Claude <claude@anthropic.com>
nikomatsakis
commented
Jun 9, 2026
- `foo()` and `foo( )` do not append any argument to the command - Leading/trailing whitespace is stripped from the argument - Add unit tests verifying parsing and runtime behavior Co-authored-by: Claude <claude@anthropic.com>
…ource - Add cross-plugin predicate tests: one plugin defines [[predicate]], another plugin uses it in predicates = [...]. Demonstrates that custom predicates are always registered regardless of which plugin defines them and can be used by any other plugin. - Tests both pass and fail paths for cross-plugin usage. Co-authored-by: Claude <claude@anthropic.com>
Replace the raw HashMap<String, ResolvedCustomPredicate> with a dedicated CustomPredicateRegistry struct that exposes only the operations we need (get, iter, contains_key, len, is_empty). Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude <claude@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Plugin authors can now define custom predicate functions that extend the predicate language. When a crates expression uses an unknown function name (e.g.,
battery-pack(cli>=0.3)), Symposium looks it up in the custom predicate registry and spawns the declared command with the raw argument appended.Key additions:
Disclosure questions
AI disclosure.
Questions for reviewers.