ruby_llm-skills 0.1.0 → 0.3.0

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: 39bffc3152e2a10d3f8000006e50d45748c30d6a04a71189ff0d52c61ada7984
4
- data.tar.gz: 57a3d68ee3b3a1f47a9bbe7b18facc4ae52a12608d275428c37a02769d0a78db
3
+ metadata.gz: '048ac6f6a76a7fd48994d208799b967754b25ecc3959acd56d44f5ab707f9f8d'
4
+ data.tar.gz: 268827da6d73359d9451b159b1abe68dea7881419df6b473cceeb2d8c74bc073
5
5
  SHA512:
6
- metadata.gz: 6aa5b23f9bcaaafeb00b073e031645e72671414eaa744c06eafbd6d66ff23d8023c4b384b5240a42013ae07c5a36b562c3b849b6f57c5ceee5bcc88ee7dfdfde
7
- data.tar.gz: d1d4a88688857c09a4399dfcb336ea2b1191ba792689b27c796a7c803005900006b52a772656be3c1a6a6c21814bd3783392af538e4c8c2ca94103b399781487
6
+ metadata.gz: febe501ab666f7e1cdf06e043ab44305ea065ffe561508b6fdcb9a959a90eabad8d2cb9fcc90384536eaf0871f73f2e52c3c9e93b8d883fbe8823274f547f0f3
7
+ data.tar.gz: 73226c3f1eb42ba6073dbfdcccb9f334896e078ca04ab1814260feb4218d432a83cf92817a32bb6c1abe847cf7fcd0098259aa2bab5886ce933d2c258d4ae8f1
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ 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.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-02-17
9
+
10
+ ### Added
11
+
12
+ - `RubyLLM::Agent` integration via `RubyLLM::Skills::AgentExtensions`
13
+ - Class-level `skills` DSL on agent subclasses with source, `only:`, and proc support
14
+ - Instance-level `with_skills` for runtime agent skill configuration
15
+ - Agent-specific unit and integration test coverage
16
+ - Compatibility tests for delegate fallback behavior
17
+
18
+ ### Changed
19
+
20
+ - Tightened `ruby_llm` dependency from open lower bound to `~> 1.12`
21
+ - Added explicit runtime compatibility checks for required `RubyLLM::Agent` hooks
22
+ - Documented `agent.with_skills(...)` replacement semantics in README
23
+
24
+ ### Fixed
25
+
26
+ - Dynamic skill blocks resolving to `nil` or `[]` no longer silently load default skills
27
+ - Skill source normalization and validation now prevent nested/invalid source runtime crashes
28
+ - Delegate fallback now supports common delegation options (`prefix`, `allow_nil`, `private`)
29
+
8
30
  ## [0.1.0] - 2025-01-15
9
31
 
10
32
  ### Added
data/README.md CHANGED
@@ -2,15 +2,12 @@
2
2
 
3
3
  Agent Skills for [RubyLLM](https://github.com/crmne/ruby_llm). Teach your AI how to do things your way.
4
4
 
5
- Skills are folders of instructions, scripts, and resources that extend LLM capabilities for specialized tasks. This gem implements the [Agent Skills specification](https://agentskills.io/specification) for RubyLLM.
6
-
7
5
  [![Gem Version](https://badge.fury.io/rb/ruby_llm-skills.svg)](https://badge.fury.io/rb/ruby_llm-skills)
8
6
  [![CI](https://github.com/kieranklaassen/ruby_llm-skills/actions/workflows/ci.yml/badge.svg)](https://github.com/kieranklaassen/ruby_llm-skills/actions)
7
+ [![Compound Engineered](https://img.shields.io/badge/Compound-Engineered-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
9
8
 
10
9
  ## Installation
11
10
 
12
- Add this line to your application's Gemfile:
13
-
14
11
  ```ruby
15
12
  gem "ruby_llm-skills"
16
13
  ```
@@ -19,378 +16,142 @@ gem "ruby_llm-skills"
19
16
 
20
17
  ```ruby
21
18
  chat = RubyLLM.chat
22
- chat.with_skills # Load skills from app/skills
23
- chat.ask "Create a PDF report from this data"
24
- ```
25
-
26
- That's it. Skills are discovered automatically and injected into the system prompt.
27
-
28
- ## How It Works
29
-
30
- Skills follow a three-level progressive disclosure pattern:
31
-
32
- 1. **Metadata** - Name and description loaded at startup (~100 tokens per skill)
33
- 2. **Instructions** - Full SKILL.md loaded when skill triggers
34
- 3. **Resources** - Scripts and references loaded on demand
35
-
36
- This keeps context lean while making capabilities available.
37
-
38
- ## Configuration
39
-
40
- ```ruby
41
- RubyLLM::Skills.default_path = "lib/skills" # Default: app/skills
42
- RubyLLM::Skills.logger = Rails.logger # Default: nil
43
- ```
44
-
45
- ## Loading Skills
46
-
47
- ### From Filesystem (Default)
48
-
49
- ```ruby
50
- # Load from default path (app/skills)
51
19
  chat.with_skills
52
-
53
- # Load from specific directory
54
- chat.with_skills(from: "lib/skills")
55
-
56
- # Load specific skills only
57
- chat.with_skills(only: [:pdf_report, :data_analysis])
58
- ```
59
-
60
- ### From Multiple Sources
61
-
62
- Pass an array to load from multiple locations:
63
-
64
- ```ruby
65
- chat.with_skills(from: [
66
- "app/skills", # Directory
67
- "extras/skills.zip", # Zip file
68
- current_user.skills # ActiveRecord relation
69
- ])
70
- ```
71
-
72
- Sources are loaded in order. Later skills with the same name override earlier ones.
73
-
74
- ### From Database
75
-
76
- Store complete skills in your database for per-user customization. Two storage formats are supported:
77
-
78
- **Option A: Store as text (SKILL.md content)**
79
-
80
- ```ruby
81
- # Migration
82
- create_table :skills do |t|
83
- t.string :name, null: false
84
- t.text :description, null: false
85
- t.text :content, null: false # Full SKILL.md content
86
- t.references :user
87
- t.timestamps
88
- end
89
-
90
- # Model
91
- class Skill < ApplicationRecord
92
- belongs_to :user, optional: true
93
-
94
- validates :name, format: { with: /\A[a-z0-9-]+\z/ }
95
- end
96
-
97
- # Create a skill
98
- current_user.skills.create!(
99
- name: "my-workflow",
100
- description: "Custom workflow for data processing",
101
- content: <<~SKILL
102
- # My Workflow
103
-
104
- ## Steps
105
- 1. Load the data
106
- 2. Process with custom rules
107
- 3. Export results
108
- SKILL
109
- )
110
- ```
111
-
112
- **Option B: Store as binary (zip file)**
113
-
114
- For skills with scripts, references, or assets:
115
-
116
- ```ruby
117
- # Migration
118
- create_table :skills do |t|
119
- t.string :name, null: false
120
- t.text :description, null: false
121
- t.binary :data, null: false # Zip file blob
122
- t.references :user
123
- t.timestamps
124
- end
125
-
126
- # Model
127
- class Skill < ApplicationRecord
128
- belongs_to :user, optional: true
129
- end
130
-
131
- # Upload a skill zip
132
- skill_zip = File.read("my-skill.zip")
133
- current_user.skills.create!(
134
- name: "pdf-report",
135
- description: "Generate PDF reports with charts",
136
- data: skill_zip
137
- )
138
- ```
139
-
140
- **Loading database skills**
141
-
142
- ```ruby
143
- # Combine app skills with user's custom skills
144
- chat.with_skills(from: [
145
- "app/skills", # Base skills from filesystem
146
- current_user.skills # User's skills from database
147
- ])
148
- ```
149
-
150
- The gem detects the storage format automatically:
151
- - Records with `content` field → parsed as SKILL.md text
152
- - Records with `data` field → extracted as zip
153
-
154
- ### From Zip Files
155
-
156
- ```ruby
157
- chat.with_skills(from: "skills.zip")
158
- chat.with_skills(from: ["core.zip", "custom.zip"])
20
+ chat.ask "Create a PDF report from this data"
159
21
  ```
160
22
 
161
- ### Source Detection
23
+ The LLM discovers skills, calls the skill tool, and gets instructions.
162
24
 
163
- The gem auto-detects source type:
164
-
165
- | Input | Type |
166
- |-------|------|
167
- | String ending in `/` or directory path | Filesystem |
168
- | String ending in `.zip` | Zip file |
169
- | ActiveRecord relation or array of objects | Database |
170
-
171
- ## Rails Integration
172
-
173
- Skills load automatically via Railtie. No configuration needed.
25
+ ## Usage
174
26
 
175
27
  ```ruby
176
- # app/skills/ is scanned at boot
177
- # Skills available on any RubyLLM chat
178
-
179
- class ReportsController < ApplicationController
180
- def create
181
- chat = RubyLLM.chat
182
- chat.with_skills # Already has app/skills loaded
183
- chat.ask "Generate quarterly report from #{@data}"
184
- end
185
- end
28
+ chat.with_skills # app/skills (default)
29
+ chat.with_skills("lib/skills") # custom path
30
+ chat.with_skills("app/skills", "app/commands") # multiple paths
31
+ chat.with_skills("app/skills", user.skills) # with database records
186
32
  ```
187
33
 
188
- ### Per-User Skills with ActiveRecord
34
+ ### With RubyLLM::Agent (v1.12+)
189
35
 
190
36
  ```ruby
191
- class User < ApplicationRecord
192
- has_many :skills
37
+ class SupportAgent < RubyLLM::Agent
38
+ model "gpt-5-nano"
39
+ instructions "You are a support assistant."
40
+ skills "app/skills", only: [:faq, :troubleshooting]
193
41
  end
194
42
 
195
- class Skill < ApplicationRecord
196
- belongs_to :user, optional: true
197
-
198
- validates :name, presence: true,
199
- format: { with: /\A[a-z0-9-]+\z/ },
200
- length: { maximum: 64 }
201
- validates :description, presence: true,
202
- length: { maximum: 1024 }
203
- validates :content, presence: true
204
- end
205
- ```
206
-
207
- ## Skill Discovery
208
-
209
- Skills are injected into the system prompt as available tools:
43
+ chat = SupportAgent.chat
44
+ chat.ask("How do I reset my password?")
210
45
 
211
- ```xml
212
- <available_skills>
213
- <skill>
214
- <name>pdf-report</name>
215
- <description>Generate PDF reports with charts...</description>
216
- <location>app/skills/pdf-report</location>
217
- </skill>
218
- </available_skills>
46
+ agent = SupportAgent.new
47
+ agent.with_skills("extra/skills")
48
+ agent.ask("What can you help with?")
219
49
  ```
220
50
 
221
- When the LLM determines a skill is relevant, it reads the full `SKILL.md` into context.
222
-
223
- ## API Reference
224
-
225
- ### RubyLLM::Skills
226
-
227
- ```ruby
228
- RubyLLM::Skills.default_path # Get/set default skills directory
229
- RubyLLM::Skills.logger # Get/set logger
230
- RubyLLM::Skills.load(from:) # Load skills from path/database/zip
231
- RubyLLM::Skills.validate(skill) # Validate skill structure
232
- ```
233
-
234
- ### RubyLLM::Skills::Skill
235
-
236
- ```ruby
237
- skill = RubyLLM::Skills.find("pdf-report")
238
-
239
- skill.name # "pdf-report"
240
- skill.description # "Generate PDF reports..."
241
- skill.content # Full SKILL.md content
242
- skill.path # Filesystem path
243
- skill.metadata # Parsed frontmatter hash
244
- skill.references # Array of reference files
245
- skill.scripts # Array of script files
246
- skill.assets # Array of asset files
247
- skill.valid? # Validates structure
248
- ```
249
-
250
- ### Chat Integration
251
-
252
- ```ruby
253
- chat = RubyLLM.chat
254
-
255
- chat.with_skills # Load default skills
256
- chat.with_skills(only: [:name]) # Load specific skills
257
- chat.with_skills(except: [:name]) # Exclude skills
258
- chat.with_skills(from: records) # Load from database
259
- chat.skills # List loaded skills
260
- chat.skill_metadata # Get metadata for prompt
261
- ```
262
-
263
- ## Validation
264
-
265
- Validate skills match the specification:
266
-
267
- ```ruby
268
- skill = RubyLLM::Skills.find("my-skill")
269
- skill.valid? # => true/false
270
- skill.errors # => ["name contains uppercase"]
271
-
272
- # Validate all skills
273
- RubyLLM::Skills.validate_all
274
- # => { valid: [...], invalid: [...] }
275
- ```
276
-
277
- ## Provider Support
278
-
279
- Skills work with any RubyLLM provider. The skill metadata is injected into the system prompt, so any model that supports system prompts can use skills.
280
-
281
- Tested with: OpenAI, Anthropic, Google Gemini, AWS Bedrock, Ollama.
282
-
283
- ## Comparison with MCP
284
-
285
- | Feature | Skills | MCP |
286
- |---------|--------|-----|
287
- | Execution | Prompt-based | Tool-based |
288
- | Setup | Drop in folder | Server config |
289
- | Context | Progressive disclosure | Always available |
290
- | Best for | Domain knowledge | External integrations |
291
-
292
- Use skills for specialized instructions. Use [ruby_llm-mcp](https://github.com/patvice/ruby_llm-mcp) for external tool integrations. They compose well together.
51
+ `agent.with_skills(...)` replaces the current skill tool configuration.
52
+ To combine sources, pass all sources in a single `skills`/`with_skills` call.
293
53
 
294
54
  ## Creating Skills
295
55
 
296
- Create a folder with a `SKILL.md` file:
297
-
298
56
  ```
299
57
  app/skills/
300
58
  └── pdf-report/
301
59
  ├── SKILL.md
302
60
  ├── scripts/
303
- │ └── generate.rb
304
61
  └── references/
305
- └── templates.md
306
62
  ```
307
63
 
308
- The `SKILL.md` requires YAML frontmatter:
64
+ SKILL.md requires frontmatter:
309
65
 
310
- ```yaml
66
+ ```markdown
311
67
  ---
312
68
  name: pdf-report
313
- description: Generate PDF reports with charts and tables. Use when asked to create reports, export data to PDF, or generate printable documents.
69
+ description: Generate PDF reports. Use when asked to create reports or export to PDF.
314
70
  ---
315
71
 
316
72
  # PDF Report Generator
317
73
 
318
- ## Quick Start
74
+ Instructions here...
75
+ ```
319
76
 
320
- Use the bundled script for generation:
77
+ ## Slash Commands
321
78
 
322
- ```bash
323
- ruby scripts/generate.rb --input data.json --output report.pdf
324
- ```
79
+ Single-file skills work as commands:
325
80
 
326
- ## Guidelines
81
+ ```
82
+ app/commands/
83
+ ├── write-poem.md
84
+ └── review-code.md
85
+ ```
327
86
 
328
- - Always include page numbers
329
- - Use company logo from assets/
87
+ ```ruby
88
+ chat.with_skills("app/skills", "app/commands")
89
+ chat.ask "/write-poem about robots"
330
90
  ```
331
91
 
332
- ### Frontmatter Fields
92
+ ## Database Skills
333
93
 
334
- | Field | Required | Description |
335
- |-------|----------|-------------|
336
- | `name` | Yes | Lowercase, hyphens only. Max 64 chars. |
337
- | `description` | Yes | What it does AND when to use it. Max 1024 chars. |
338
- | `license` | No | License identifier |
339
- | `compatibility` | No | Environment requirements |
340
- | `metadata` | No | Custom key-value pairs |
94
+ Store skills or commands in your database:
341
95
 
342
- ### Skill Directories
96
+ ```ruby
97
+ create_table :skills do |t|
98
+ t.string :name, null: false
99
+ t.text :description, null: false
100
+ t.text :content, null: false # SKILL.md body
101
+ t.references :user
102
+ t.timestamps
103
+ end
343
104
 
105
+ chat.with_skills(user.skills)
106
+ chat.ask "/my-command args" # works as command too
344
107
  ```
345
- skill-name/
346
- ├── SKILL.md # Required - instructions
347
- ├── scripts/ # Optional - executable code
348
- ├── references/ # Optional - additional docs
349
- └── assets/ # Optional - templates, images
350
- ```
351
-
352
- ### Best Practices
353
108
 
354
- **Keep SKILL.md under 500 lines.** Move detailed content to `references/`.
109
+ Records must respond to `#name`, `#description`, and `#content`. For skills with scripts/references, use filesystem skills.
355
110
 
356
- **Write good descriptions.** Include both what the skill does AND when to use it:
111
+ ## Rails
357
112
 
358
- ```yaml
359
- # Good
360
- description: Extract text and tables from PDF files. Use when working with PDFs, forms, or document extraction.
113
+ Default path auto-configured to `Rails.root/app/skills`.
361
114
 
362
- # Bad
363
- description: PDF helper.
115
+ ```bash
116
+ rails generate skill pdf-report --description "Generate PDF reports"
364
117
  ```
365
118
 
366
- **Use scripts for deterministic operations.** Scripts execute without loading into context.
119
+ ## Development
367
120
 
368
- **One level of references.** Avoid deeply nested file chains.
121
+ ### Setup
369
122
 
370
- ## Development
123
+ ```bash
124
+ bin/setup
125
+ ```
126
+
127
+ ### Running Tests
371
128
 
372
129
  ```bash
373
- git clone https://github.com/kieranklaassen/ruby_llm-skills.git
374
- cd ruby_llm-skills
375
- bundle install
376
- bundle exec rake test
130
+ bundle exec rake test # Unit tests (151 tests)
131
+ bundle exec rake test_rails # Rails integration tests (25+ tests)
132
+ bundle exec rake test_all # Both
133
+ bundle exec rake # Tests + linting
377
134
  ```
378
135
 
379
- ## Contributing
136
+ ### Dummy Rails App
380
137
 
381
- 1. Fork it
382
- 2. Create your feature branch (`git checkout -b my-feature`)
383
- 3. Commit your changes (`git commit -am 'Add feature'`)
384
- 4. Push to the branch (`git push origin my-feature`)
385
- 5. Create a Pull Request
138
+ A minimal Rails 8 app at `test/dummy/` tests Rails integration:
386
139
 
387
- ## License
140
+ - **Filesystem skills**: `app/skills/greeting/` tests directory-based loading
141
+ - **Database skills**: `Skill` model tests ActiveRecord-based loading
142
+ - **Generator tests**: Tests for `rails generate skill`
143
+ - **Composite loading**: Tests combining filesystem + database sources
388
144
 
389
- MIT License. See [LICENSE](LICENSE) for details.
145
+ ```bash
146
+ cd test/dummy
147
+ bundle exec rails test # Run Rails tests directly
148
+ ```
390
149
 
391
150
  ## Resources
392
151
 
393
152
  - [Agent Skills Specification](https://agentskills.io/specification)
394
- - [Anthropic Skills Repository](https://github.com/anthropics/skills)
395
153
  - [RubyLLM](https://github.com/crmne/ruby_llm)
396
- - [RubyLLM::MCP](https://github.com/patvice/ruby_llm-mcp)
154
+
155
+ ## License
156
+
157
+ MIT
@@ -0,0 +1,29 @@
1
+ Description:
2
+ Creates a new Agent Skill following the agentskills.io specification.
3
+ Skills are stored in app/skills/ and contain a SKILL.md file with
4
+ YAML frontmatter (name, description) and markdown instructions.
5
+
6
+ Example:
7
+ rails generate skill pdf-report --description "Generate PDF reports"
8
+
9
+ This creates:
10
+ app/skills/pdf-report/
11
+ app/skills/pdf-report/SKILL.md
12
+
13
+ With optional directories:
14
+ --scripts Creates scripts/ directory for executable code
15
+ --references Creates references/ directory for documentation
16
+ --assets Creates assets/ directory for templates/images
17
+
18
+ Full Example:
19
+ rails generate skill data-export -d "Export data to CSV/JSON. Use when asked to export or download data." --scripts --assets
20
+
21
+ This creates:
22
+ app/skills/data-export/
23
+ app/skills/data-export/SKILL.md
24
+ app/skills/data-export/scripts/.keep
25
+ app/skills/data-export/assets/.keep
26
+
27
+ Note:
28
+ The description should include both what the skill does AND when to use it.
29
+ Example: "Generate PDF reports from data. Use when asked to create reports or export to PDF."
@@ -5,11 +5,16 @@ require "rails/generators"
5
5
  class SkillGenerator < Rails::Generators::NamedBase
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
- class_option :description, type: :string, default: "Description of what this skill does"
9
- class_option :license, type: :string, default: nil
10
- class_option :scripts, type: :boolean, default: false
11
- class_option :references, type: :boolean, default: false
12
- class_option :assets, type: :boolean, default: false
8
+ class_option :description, type: :string, default: "Description of what this skill does. Use when...",
9
+ aliases: "-d", desc: "Short description of the skill (max 1024 chars)"
10
+ class_option :license, type: :string, default: nil,
11
+ aliases: "-l", desc: "License identifier (e.g., MIT, Apache-2.0)"
12
+ class_option :scripts, type: :boolean, default: false,
13
+ desc: "Create scripts/ directory for executable code"
14
+ class_option :references, type: :boolean, default: false,
15
+ desc: "Create references/ directory for documentation"
16
+ class_option :assets, type: :boolean, default: false,
17
+ desc: "Create assets/ directory for templates/images"
13
18
 
14
19
  def create_skill_directory
15
20
  empty_directory skill_path
@@ -57,4 +62,8 @@ class SkillGenerator < Rails::Generators::NamedBase
57
62
  def skill_license
58
63
  options[:license]
59
64
  end
65
+
66
+ def skill_title
67
+ skill_name.split("-").map(&:capitalize).join(" ")
68
+ end
60
69
  end
@@ -6,16 +6,52 @@ license: <%= skill_license %>
6
6
  <% end -%>
7
7
  ---
8
8
 
9
- # <%= skill_name.split("-").map(&:capitalize).join(" ") %>
9
+ # <%= skill_title %>
10
10
 
11
- Instructions for using this skill.
11
+ <%= skill_description %>
12
12
 
13
13
  ## When to Use
14
14
 
15
- Use this skill when...
15
+ Use this skill when:
16
16
 
17
- ## Steps
17
+ - [Trigger condition 1 - be specific about keywords/phrases]
18
+ - [Trigger condition 2]
18
19
 
19
- 1. First step
20
- 2. Second step
21
- 3. Third step
20
+ ## Instructions
21
+
22
+ ### Step 1: [Action]
23
+
24
+ [Clear instruction with expected outcome]
25
+
26
+ ### Step 2: [Action]
27
+
28
+ [Clear instruction with expected outcome]
29
+
30
+ ### Step 3: [Action]
31
+
32
+ [Clear instruction with expected outcome]
33
+
34
+ ## Examples
35
+
36
+ ### Example 1: [Scenario]
37
+
38
+ **Input:**
39
+ ```
40
+ [Example input or command]
41
+ ```
42
+
43
+ **Output:**
44
+ ```
45
+ [Expected result]
46
+ ```
47
+
48
+ ## Edge Cases
49
+
50
+ - **[Scenario]**: [How to handle it]
51
+ - **[Error condition]**: [Recovery steps]
52
+
53
+ ## Notes
54
+
55
+ - Keep this file under 500 lines for optimal token usage
56
+ - Move detailed documentation to `references/` directory
57
+ - Move executable code to `scripts/` directory