riffer 0.32.0 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cddafa66f3f24980037c23be5e8584ec888b93c40b2b780c99e7205558d2593
4
- data.tar.gz: 5aea2edfeb9d47a4246b15d3f4e16a7a3c1d7941a473355495267b9eb93bc3cc
3
+ metadata.gz: 06d5acfa86b1320573aeb4dd2135a8540dbda454871a1e49cca8ee7b30e43336
4
+ data.tar.gz: b216a00806b2f08516b197cf03c4bc23766c756174ab43fc3a5e45fb5a4f83d2
5
5
  SHA512:
6
- metadata.gz: fad79dcfd8036c3249ea17be785cb6acb193e3a1677f23ee052c89daf39fe97840369a4718e65ebb515bbddeb866608a532b8076a89e2f6c724ddd74d2131e85
7
- data.tar.gz: 0c7b1bdb7383c6f9a2b053d2d811a7c4aa29142f8a44aa3fd07071d88b0dac722268f036c9f45887bbf3b10591cb3067193540c2cff766d6ab91aa4644f0dcb8
6
+ metadata.gz: 6408ae651fb7944a4618eced7dcc19658262560748a6fedf96cab6989cd7497acde23beb1962a4117e67e595f23e870e241d511c60329f10dd2aafa4f2cca8ff
7
+ data.tar.gz: ce8962451448533b266b8411172e49311616b6b81a3c54a71c4484a249420f16ad474c5bf06e40d7d4e811ec37edf131db150e25b15e001e7970fa56f0ea711c
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.32.0"
2
+ ".": "0.32.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.32.1](https://github.com/janeapp/riffer/compare/riffer/v0.32.0...riffer/v0.32.1) (2026-06-10)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * respect disable-model-invocation in skills ([#303](https://github.com/janeapp/riffer/issues/303)) ([2cf8719](https://github.com/janeapp/riffer/commit/2cf8719ecc36d748fabf7f03c8427e2b3043d30c))
14
+
8
15
  ## [0.32.0](https://github.com/janeapp/riffer/compare/riffer/v0.31.0...riffer/v0.32.0) (2026-06-08)
9
16
 
10
17
 
data/docs/13_SKILLS.md CHANGED
@@ -36,7 +36,11 @@ Review the code for:
36
36
  - `name` — lowercase alphanumeric with hyphens, 1-64 chars (must match directory name)
37
37
  - `description` — 1-1024 chars, helps the LLM decide when to activate
38
38
 
39
- Additional frontmatter keys are passed through as metadata.
39
+ **Optional frontmatter fields:**
40
+
41
+ - `disable-model-invocation` — when `true`, the skill is hidden from the catalog and the LLM cannot activate it via the `skill_activate` tool. It stays reachable through the programmatic `activate` config (see [Activated Skills](#activated-skills)), so you can inject it under your own logic instead of the model's. Only the literal value `true` disables invocation; any other value (or its absence) leaves the skill model-invocable.
42
+
43
+ Any other frontmatter keys are passed through as metadata.
40
44
 
41
45
  ## Configuring an Agent
42
46
 
@@ -81,7 +85,7 @@ end
81
85
 
82
86
  ### Activated Skills
83
87
 
84
- Load skill instructions into the system prompt at startup (no tool call needed):
88
+ Load skill instructions into the system prompt at startup (no tool call needed). This is also the only way to surface a skill marked `disable-model-invocation: true`, which the model can never activate on its own:
85
89
 
86
90
  ```ruby
87
91
  skills do
data/lib/riffer/agent.rb CHANGED
@@ -343,9 +343,9 @@ class Riffer::Agent
343
343
  #--
344
344
  #: () -> Riffer::Messages::System?
345
345
  def build_skills_message
346
- skills = @context.skills
347
- return nil unless skills&.system_prompt
348
- Riffer::Messages::System.new(skills.system_prompt)
346
+ content = @context.skills&.system_prompt
347
+ return nil if content.nil? || content.empty?
348
+ Riffer::Messages::System.new(content)
349
349
  end
350
350
 
351
351
  #--
@@ -415,7 +415,7 @@ class Riffer::Agent
415
415
 
416
416
  skills_config = @config.skills_config
417
417
 
418
- if skills_config
418
+ if skills_config && @context.skills&.activatable?
419
419
  skill_activate_tool_class = skills_config.activate_tool || Riffer.config.skills.default_activate_tool
420
420
 
421
421
  if tools.any? { |t| t.name == skill_activate_tool_class.name }
@@ -19,6 +19,7 @@ class Riffer::Skills::ActivateTool < Riffer::Tool
19
19
  def call(context:, name:)
20
20
  skills_context = context&.skills
21
21
  return error("Skills not configured") unless skills_context
22
+ return error("Unknown skill: '#{name}'") unless skills_context.model_invocable?(name)
22
23
 
23
24
  text(skills_context.activate(name))
24
25
  rescue Riffer::ArgumentError => e
@@ -48,6 +48,21 @@ class Riffer::Skills::Context
48
48
  @activated.key?(name)
49
49
  end
50
50
 
51
+ # Returns whether a skill exists and may be activated by the model.
52
+ #--
53
+ #: (String) -> bool
54
+ def model_invocable?(name)
55
+ skill = skills[name]
56
+ !skill.nil? && !skill.disable_model_invocation
57
+ end
58
+
59
+ # Returns whether any skill is available for the model to activate.
60
+ #--
61
+ #: () -> bool
62
+ def activatable?
63
+ available_skills.any?
64
+ end
65
+
51
66
  # Returns the complete skills section for the system prompt — the catalog plus
52
67
  # any pre-activated skill bodies.
53
68
  #--
@@ -65,6 +80,6 @@ class Riffer::Skills::Context
65
80
  #--
66
81
  #: () -> Array[Riffer::Skills::Frontmatter]
67
82
  def available_skills
68
- skills.values.reject { |skill| @activated.key?(skill.name) }
83
+ skills.values.reject { |skill| @activated.key?(skill.name) || skill.disable_model_invocation }
69
84
  end
70
85
  end
@@ -4,7 +4,8 @@
4
4
  require "yaml"
5
5
 
6
6
  # Immutable value object holding parsed SKILL.md YAML frontmatter. Required
7
- # fields: +name+ and +description+; unrecognized top-level keys are merged into
7
+ # fields: +name+ and +description+; the optional +disable-model-invocation+
8
+ # flag is recognized, and any other unrecognized top-level keys are merged into
8
9
  # +metadata+.
9
10
  class Riffer::Skills::Frontmatter
10
11
  NAME_PATTERN = /\A[a-z0-9]+(-[a-z0-9]+)*\z/ #: Regexp
@@ -17,6 +18,11 @@ class Riffer::Skills::Frontmatter
17
18
  # The skill description (1-1024 chars).
18
19
  attr_reader :description #: String
19
20
 
21
+ # Whether the skill opts out of model-driven activation. Hidden from the
22
+ # catalog and rejected at model activation; still reachable via programmatic
23
+ # activation.
24
+ attr_reader :disable_model_invocation #: bool
25
+
20
26
  # Metadata from the spec's +metadata+ field plus any unrecognized top-level
21
27
  # keys.
22
28
  attr_reader :metadata #: Hash[Symbol, untyped]
@@ -29,7 +35,7 @@ class Riffer::Skills::Frontmatter
29
35
  def self.parse(raw)
30
36
  yaml, body = split_frontmatter(raw)
31
37
  raise Riffer::ArgumentError, "missing YAML frontmatter (expected --- delimiters)" if yaml.empty?
32
- [new(name: yaml.delete(:name), description: yaml.delete(:description), metadata: yaml), body]
38
+ [new(name: yaml.delete(:name), description: yaml.delete(:description), disable_model_invocation: yaml.delete(:"disable-model-invocation"), metadata: yaml), body]
33
39
  end
34
40
 
35
41
  # Parses only the frontmatter from a raw SKILL.md string, ignoring the body.
@@ -39,7 +45,7 @@ class Riffer::Skills::Frontmatter
39
45
  def self.parse_frontmatter(raw)
40
46
  yaml, _ = split_frontmatter(raw)
41
47
  raise Riffer::ArgumentError, "missing YAML frontmatter (expected --- delimiters)" if yaml.empty?
42
- new(name: yaml.delete(:name), description: yaml.delete(:description), metadata: yaml)
48
+ new(name: yaml.delete(:name), description: yaml.delete(:description), disable_model_invocation: yaml.delete(:"disable-model-invocation"), metadata: yaml)
43
49
  end
44
50
 
45
51
  #--
@@ -58,13 +64,15 @@ class Riffer::Skills::Frontmatter
58
64
  private_class_method :split_frontmatter
59
65
 
60
66
  # Raises Riffer::ArgumentError if +name+ or +description+ is invalid.
67
+ # +disable_model_invocation+ is treated as set only when literally +true+.
61
68
  #--
62
- #: (name: String, description: String, ?metadata: Hash[Symbol, untyped]) -> void
63
- def initialize(name:, description:, metadata: {})
69
+ #: (name: String, description: String, ?disable_model_invocation: bool, ?metadata: Hash[Symbol, untyped]) -> void
70
+ def initialize(name:, description:, disable_model_invocation: false, metadata: {})
64
71
  validate_name!(name)
65
72
  validate_description!(description)
66
73
  @name = name.freeze
67
74
  @description = description.freeze
75
+ @disable_model_invocation = (disable_model_invocation == true)
68
76
  @metadata = metadata.freeze
69
77
  end
70
78
 
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.32.0" #: String
5
+ VERSION = "0.32.1" #: String
6
6
  end
@@ -35,6 +35,16 @@ class Riffer::Skills::Context
35
35
  # : (String) -> bool
36
36
  def activated?: (String) -> bool
37
37
 
38
+ # Returns whether a skill exists and may be activated by the model.
39
+ # --
40
+ # : (String) -> bool
41
+ def model_invocable?: (String) -> bool
42
+
43
+ # Returns whether any skill is available for the model to activate.
44
+ # --
45
+ # : () -> bool
46
+ def activatable?: () -> bool
47
+
38
48
  # Returns the complete skills section for the system prompt — the catalog plus
39
49
  # any pre-activated skill bodies.
40
50
  # --
@@ -1,7 +1,8 @@
1
1
  # Generated from lib/riffer/skills/frontmatter.rb with RBS::Inline
2
2
 
3
3
  # Immutable value object holding parsed SKILL.md YAML frontmatter. Required
4
- # fields: +name+ and +description+; unrecognized top-level keys are merged into
4
+ # fields: +name+ and +description+; the optional +disable-model-invocation+
5
+ # flag is recognized, and any other unrecognized top-level keys are merged into
5
6
  # +metadata+.
6
7
  class Riffer::Skills::Frontmatter
7
8
  NAME_PATTERN: Regexp
@@ -16,6 +17,11 @@ class Riffer::Skills::Frontmatter
16
17
  # The skill description (1-1024 chars).
17
18
  attr_reader description: String
18
19
 
20
+ # Whether the skill opts out of model-driven activation. Hidden from the
21
+ # catalog and rejected at model activation; still reachable via programmatic
22
+ # activation.
23
+ attr_reader disable_model_invocation: bool
24
+
19
25
  # Metadata from the spec's +metadata+ field plus any unrecognized top-level
20
26
  # keys.
21
27
  attr_reader metadata: Hash[Symbol, untyped]
@@ -38,9 +44,10 @@ class Riffer::Skills::Frontmatter
38
44
  def self.split_frontmatter: (String) -> [ Hash[Symbol, untyped], String ]
39
45
 
40
46
  # Raises Riffer::ArgumentError if +name+ or +description+ is invalid.
47
+ # +disable_model_invocation+ is treated as set only when literally +true+.
41
48
  # --
42
- # : (name: String, description: String, ?metadata: Hash[Symbol, untyped]) -> void
43
- def initialize: (name: String, description: String, ?metadata: Hash[Symbol, untyped]) -> void
49
+ # : (name: String, description: String, ?disable_model_invocation: bool, ?metadata: Hash[Symbol, untyped]) -> void
50
+ def initialize: (name: String, description: String, ?disable_model_invocation: bool, ?metadata: Hash[Symbol, untyped]) -> void
44
51
 
45
52
  private
46
53
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.0
4
+ version: 0.32.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall