ask-skills 0.1.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 +7 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE +21 -0
- data/README.md +192 -0
- data/lib/ask/skills/formatter.rb +43 -0
- data/lib/ask/skills/registry.rb +43 -0
- data/lib/ask/skills/skill.compose/SKILL.md +64 -0
- data/lib/ask/skills/skill.design/SKILL.md +72 -0
- data/lib/ask/skills/skill.rb +13 -0
- data/lib/ask/skills/sources/base.rb +15 -0
- data/lib/ask/skills/sources/filesystem.rb +67 -0
- data/lib/ask/skills/sources/gems.rb +62 -0
- data/lib/ask/skills/validator.rb +37 -0
- data/lib/ask/skills/version.rb +5 -0
- data/lib/ask/skills.rb +41 -0
- data/lib/ask-skills.rb +1 -0
- metadata +103 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1a4229cd07f55413b7de34a7463c2f582aac94898d804e8017cb9c0c02f2117b
|
|
4
|
+
data.tar.gz: d5c96783d0bbd7b5a9a79af85a1c33bdc59f5ffa41f9a612ff24fd61ccea37ef
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 10c8342db4297f6d9417be6b63beb7962cd93a0476b421e15c736660a51ccde2c93141ecf6740f53755be02c0371c914ccf1a617759037964810dca38ab37c42
|
|
7
|
+
data.tar.gz: b5f26a5d36086ab1b918375672215fc9e7875ad5a58d43e149a4f744bf4841478bf2bdc1e20a29f9bd8d5f32769680605fe5fd4181cea1cd7b9e4e7d46c5b40b
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-06-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release
|
|
7
|
+
- `Ask::Skills::Skill` — Data.define with name, description, instructions, source
|
|
8
|
+
- `to_s` and `to_prompt_entry` formatting methods
|
|
9
|
+
- `Ask::Skills::Registry` — holds discovered skills with:
|
|
10
|
+
- `[]` lookup, `names` list, `format_for_prompt` markdown output
|
|
11
|
+
- Priority resolution: first source wins
|
|
12
|
+
- `Ask::Skills::Formatter` — generates markdown and XML output
|
|
13
|
+
- `Ask::Skills::Validator` — validates required fields, name format, duplicates
|
|
14
|
+
- `Ask::Skills.discover` — discover skills from all configured sources:
|
|
15
|
+
1. Project-local (`.agents/skills/`) — highest priority
|
|
16
|
+
2. User-global (`~/.config/ask/skills/`)
|
|
17
|
+
3. Installed gems (via `Gem.find_files`)
|
|
18
|
+
4. Built-in (skill.design, skill.compose) — lowest priority
|
|
19
|
+
- `Ask::Skills::Source::Filesystem` — scan a directory for `*/SKILL.md` files
|
|
20
|
+
- `Ask::Skills::Source::Gems` — discover skills from all installed gems
|
|
21
|
+
- Built-in skills:
|
|
22
|
+
- `skill.design` — How to design and write effective skills
|
|
23
|
+
- `skill.compose` — How skills interact, combine, and resolve
|
|
24
|
+
- Full test suite with 68 tests
|
|
25
|
+
- Frontmatter parsing (name, description, optional metadata)
|
|
26
|
+
- YAML frontmatter support with quoted value handling
|
|
27
|
+
- Error handling for missing directories, malformed skills
|
|
28
|
+
- Thread-safe read-only registry access
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kaka Ruto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# ask-skills
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/ask-skills)
|
|
4
|
+
|
|
5
|
+
Discover, validate, and load agent skills from project directories, user config,
|
|
6
|
+
and installed gems. Ships built-in skills for codebase exploration and debugging
|
|
7
|
+
methodology.
|
|
8
|
+
|
|
9
|
+
A **skill** is a markdown file containing step-by-step methodology for a specific
|
|
10
|
+
domain task. It's listed in the agent's system prompt (just name + description)
|
|
11
|
+
and loaded on-demand when the agent decides it needs domain guidance.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "ask-skills"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "ask/skills"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# Discover all available skills
|
|
29
|
+
registry = Ask::Skills.discover
|
|
30
|
+
# => Finds skills from:
|
|
31
|
+
# - Built-in skills (skill.design, skill.compose)
|
|
32
|
+
# - Installed gems (ask-rails, ask-github, etc.)
|
|
33
|
+
# - .agents/skills/*/ in the project
|
|
34
|
+
# - ~/.config/ask/skills/*/ in home dir
|
|
35
|
+
|
|
36
|
+
# List available skills
|
|
37
|
+
registry.names
|
|
38
|
+
# => ["skill.compose", "skill.design"]
|
|
39
|
+
|
|
40
|
+
# Get a skill by name
|
|
41
|
+
skill = registry["skill.design"]
|
|
42
|
+
skill.name # => "skill.design"
|
|
43
|
+
skill.description # => "How to design and write effective skills for the ask-rb ecosystem"
|
|
44
|
+
skill.instructions # => markdown body with step-by-step methodology
|
|
45
|
+
|
|
46
|
+
# Format for system prompt
|
|
47
|
+
registry.format_for_prompt
|
|
48
|
+
# => "## Available Skills\n\n- **skill.design**: How to design..."
|
|
49
|
+
|
|
50
|
+
# XML format for machine parsing
|
|
51
|
+
formatter = Ask::Skills::Formatter.new(registry)
|
|
52
|
+
formatter.to_xml
|
|
53
|
+
# => "<available_skills><skill><name>skill.design</name>..."
|
|
54
|
+
|
|
55
|
+
# Validate skills
|
|
56
|
+
errors = Ask::Skills::Validator.new(registry.skills.values).validate_all
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Priority Resolution
|
|
60
|
+
|
|
61
|
+
When the same skill name exists in multiple places, priority determines which
|
|
62
|
+
one is used. **First source wins:**
|
|
63
|
+
|
|
64
|
+
| Priority | Source | Location |
|
|
65
|
+
|----------|--------|----------|
|
|
66
|
+
| 1 (highest) | Project-local | `.agents/skills/<name>/SKILL.md` |
|
|
67
|
+
| 2 | User-global | `~/.config/ask/skills/<name>/SKILL.md` |
|
|
68
|
+
| 3 | Installed gems | `Gem.find_files("ask/skills/*/SKILL.md")` |
|
|
69
|
+
| 4 (lowest) | Built-in | Shipped with ask-skills gem |
|
|
70
|
+
|
|
71
|
+
This means you can override any skill by placing a file with the same name in
|
|
72
|
+
your project's `.agents/skills/` directory.
|
|
73
|
+
|
|
74
|
+
## Skill Directory Convention
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
.agents/skills/
|
|
78
|
+
├── db_debug/
|
|
79
|
+
│ └── SKILL.md ← project-local skill
|
|
80
|
+
├── deploy/
|
|
81
|
+
│ └── SKILL.md
|
|
82
|
+
└── custom_check/
|
|
83
|
+
└── SKILL.md
|
|
84
|
+
|
|
85
|
+
~/.config/ask/skills/
|
|
86
|
+
├── my_workflow/
|
|
87
|
+
│ └── SKILL.md ← user-global skill
|
|
88
|
+
└── team_patterns/
|
|
89
|
+
└── SKILL.md
|
|
90
|
+
|
|
91
|
+
# From installed gems:
|
|
92
|
+
ask-rails-0.2.0/lib/ask/skills/
|
|
93
|
+
├── rails.db_debug/SKILL.md
|
|
94
|
+
└── rails.deploy_pipeline/SKILL.md
|
|
95
|
+
|
|
96
|
+
ask-github-0.1.0/lib/ask/skills/
|
|
97
|
+
├── github.pr_review/SKILL.md
|
|
98
|
+
└── github.issue_triage/SKILL.md
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Skill Format
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
---
|
|
105
|
+
name: rails.db_debug
|
|
106
|
+
description: Step-by-step methodology for debugging database issues in Rails
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
When investigating database performance issues, follow these steps:
|
|
110
|
+
|
|
111
|
+
1. **Understand the Schema** — Use ReadModel to inspect...
|
|
112
|
+
2. **Check Indexes** — Query pg_indexes for missing indexes...
|
|
113
|
+
3. **Explain Slow Queries** — Use EXPLAIN ANALYZE on...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Built-in Skills
|
|
117
|
+
|
|
118
|
+
| Skill | Description |
|
|
119
|
+
|-------|-------------|
|
|
120
|
+
| `skill.design` | How to design and write effective skills for the ask-rb ecosystem |
|
|
121
|
+
| `skill.compose` | How skills interact, combine, and resolve in the ask-rb ecosystem |
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### `Ask::Skills.discover(sources: nil)`
|
|
126
|
+
|
|
127
|
+
Returns a `Registry` with skills from all sources, in priority order.
|
|
128
|
+
Pass `sources:` to override with custom sources.
|
|
129
|
+
|
|
130
|
+
### `Ask::Skills::Registry`
|
|
131
|
+
|
|
132
|
+
| Method | Description |
|
|
133
|
+
|--------|-------------|
|
|
134
|
+
| `[]` | Lookup skill by name |
|
|
135
|
+
| `names` | List all skill names |
|
|
136
|
+
| `skills` | Hash of name → Skill |
|
|
137
|
+
| `format_for_prompt` | Generate markdown section |
|
|
138
|
+
|
|
139
|
+
### `Ask::Skills::Skill` (Data.define)
|
|
140
|
+
|
|
141
|
+
| Attribute | Description |
|
|
142
|
+
|-----------|-------------|
|
|
143
|
+
| `name` | Unique identifier (e.g. `rails.db_debug`) |
|
|
144
|
+
| `description` | One-line summary for system prompt |
|
|
145
|
+
| `instructions` | Full markdown methodology body |
|
|
146
|
+
| `source` | File path the skill was loaded from |
|
|
147
|
+
|
|
148
|
+
### `Ask::Skills::Formatter`
|
|
149
|
+
|
|
150
|
+
| Method | Description |
|
|
151
|
+
|--------|-------------|
|
|
152
|
+
| `to_prompt_section` | Markdown format for system prompt |
|
|
153
|
+
| `to_xml` | XML format for machine parsing |
|
|
154
|
+
|
|
155
|
+
### `Ask::Skills::Validator`
|
|
156
|
+
|
|
157
|
+
| Method | Description |
|
|
158
|
+
|--------|-------------|
|
|
159
|
+
| `validate_all` | Validate all skills, return errors |
|
|
160
|
+
| `validate` | Validate a single skill |
|
|
161
|
+
|
|
162
|
+
## Custom Sources
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
require "ask/skills"
|
|
166
|
+
|
|
167
|
+
# Custom filesystem source
|
|
168
|
+
custom = Ask::Skills::Source::Filesystem.new(dir: "/path/to/skills")
|
|
169
|
+
registry = Ask::Skills.discover(sources: [custom])
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Gems That Ship Skills
|
|
173
|
+
|
|
174
|
+
| Gem | Planned Skills |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `ask-skills` (built-in) | skill.design, skill.compose |
|
|
177
|
+
| `ask-rails` | rails.db_debug, rails.route_trouble, rails.deploy_pipeline |
|
|
178
|
+
| `ask-github` | github.pr_review, github.issue_triage |
|
|
179
|
+
| `ask-slack` | slack.compose |
|
|
180
|
+
| `ask-tools-shell` | shell.patterns |
|
|
181
|
+
| `ask-llm-providers` | providers.model_select |
|
|
182
|
+
|
|
183
|
+
## Development
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
bundle install
|
|
187
|
+
bundle exec rake test
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Ask
|
|
2
|
+
module Skills
|
|
3
|
+
class Formatter
|
|
4
|
+
def initialize(skills)
|
|
5
|
+
@skills = skills.respond_to?(:skills) ? skills.skills : skills
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_prompt_section
|
|
9
|
+
return "" if @skills.empty? || @skills.values.all? { |s| s.is_a?(Array) && s.empty? }
|
|
10
|
+
|
|
11
|
+
lines = ["", "## Available Skills", ""]
|
|
12
|
+
@skills.each_value do |skill|
|
|
13
|
+
lines << skill.to_prompt_entry
|
|
14
|
+
end
|
|
15
|
+
lines << ""
|
|
16
|
+
lines << "When a task matches a skill's description, load it for step-by-step methodology."
|
|
17
|
+
lines << ""
|
|
18
|
+
lines.join("\n")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_xml
|
|
22
|
+
return "" if @skills.empty?
|
|
23
|
+
|
|
24
|
+
lines = ["", "<available_skills>"]
|
|
25
|
+
@skills.each_value do |skill|
|
|
26
|
+
lines << " <skill>"
|
|
27
|
+
lines << " <name>#{escape_xml(skill.name)}</name>"
|
|
28
|
+
lines << " <description>#{escape_xml(skill.description)}</description>"
|
|
29
|
+
lines << " </skill>"
|
|
30
|
+
end
|
|
31
|
+
lines << "</available_skills>"
|
|
32
|
+
lines.join("\n")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def escape_xml(str)
|
|
38
|
+
str.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
|
39
|
+
.gsub('"', """).gsub("'", "'")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Ask
|
|
2
|
+
module Skills
|
|
3
|
+
class Registry
|
|
4
|
+
attr_reader :skills
|
|
5
|
+
|
|
6
|
+
def initialize(sources)
|
|
7
|
+
@skills = {}
|
|
8
|
+
@sources = sources
|
|
9
|
+
load_all
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def [](name)
|
|
13
|
+
@skills[name]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def names
|
|
17
|
+
@skills.keys
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def format_for_prompt
|
|
21
|
+
return "" if @skills.empty?
|
|
22
|
+
lines = ["", "## Available Skills", ""]
|
|
23
|
+
@skills.each_value do |skill|
|
|
24
|
+
lines << skill.to_prompt_entry
|
|
25
|
+
end
|
|
26
|
+
lines << ""
|
|
27
|
+
lines.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def load_all
|
|
33
|
+
@sources.each do |source|
|
|
34
|
+
source.load.each do |skill|
|
|
35
|
+
next unless skill
|
|
36
|
+
# First source wins (project overrides gems, user overrides project)
|
|
37
|
+
@skills[skill.name] ||= skill
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill.compose
|
|
3
|
+
description: How skills interact, combine, and resolve in the ask-rb ecosystem
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Skill Resolution Priority
|
|
7
|
+
|
|
8
|
+
Skills are discovered from multiple sources. When the same skill name exists in
|
|
9
|
+
multiple places, the first source wins:
|
|
10
|
+
|
|
11
|
+
1. **Built-in** (shipped with ask-skills gem) — lowest priority
|
|
12
|
+
2. **Installed gems** (ask-rails, ask-github, etc.)
|
|
13
|
+
3. **User-global** (`~/.config/ask/skills/`)
|
|
14
|
+
4. **Project-local** (`.agents/skills/` in the project) — highest priority
|
|
15
|
+
|
|
16
|
+
This means you can override any skill by placing a file with the same name in
|
|
17
|
+
your project's `.agents/skills/` directory. Or provide personal defaults in
|
|
18
|
+
`~/.config/ask/skills/`.
|
|
19
|
+
|
|
20
|
+
## How Skills Appear in the System Prompt
|
|
21
|
+
|
|
22
|
+
The agent's system prompt includes a section listing all available skills:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
## Available Skills
|
|
26
|
+
|
|
27
|
+
- **skill.design**: How to design and write effective skills
|
|
28
|
+
- **rails.db_debug**: Step-by-step database debugging in Rails
|
|
29
|
+
- **github.pr_review**: PR review workflow methodology
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Only the **name and description** appear. The full instructions are NOT in the
|
|
33
|
+
system prompt — they're loaded on-demand when the agent calls the skill.
|
|
34
|
+
|
|
35
|
+
## When to Load a Skill
|
|
36
|
+
|
|
37
|
+
Skills should be loaded when the task matches the skill's description. For example,
|
|
38
|
+
if an investigation involves a slow database query, load `rails.db_debug`.
|
|
39
|
+
|
|
40
|
+
If no skill's description matches the current task, proceed without loading any.
|
|
41
|
+
Skills are optional — they provide methodology, not required capabilities.
|
|
42
|
+
|
|
43
|
+
## Skills Can Reference Other Skills
|
|
44
|
+
|
|
45
|
+
A skill's instructions can mention other skills:
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
Step 3: If you find the query is slow, load `rails.db_debug` for
|
|
49
|
+
detailed database investigation methodology.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This creates a hierarchy of methodology — a deploy skill might reference both
|
|
53
|
+
a database migration skill and a monitoring skill.
|
|
54
|
+
|
|
55
|
+
## Common Patterns
|
|
56
|
+
|
|
57
|
+
**Sequential composition**: Load one skill, follow its steps, then load another
|
|
58
|
+
when you reach a step that references it.
|
|
59
|
+
|
|
60
|
+
**Alternative composition**: If a skill's first step fails, load the troubleshooting
|
|
61
|
+
variant of that skill instead.
|
|
62
|
+
|
|
63
|
+
**Reporting back**: After completing a skill's methodology, summarize what you found
|
|
64
|
+
and what you did. The skill guided your process, but the results go back to the user.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill.design
|
|
3
|
+
description: How to design and write effective skills for the ask-rb ecosystem
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
A skill is a markdown file that teaches step-by-step methodology for a domain task.
|
|
7
|
+
It differs from a tool (which provides a capability) in that it guides *how* to
|
|
8
|
+
approach the work — the reasoning process, the order of operations, the pitfalls to avoid.
|
|
9
|
+
|
|
10
|
+
## When to Create a Skill
|
|
11
|
+
|
|
12
|
+
Create a skill when you have domain knowledge that:
|
|
13
|
+
1. Follows a repeatable process (always debug DB this way)
|
|
14
|
+
2. Requires tool composition (use these 3 tools in sequence)
|
|
15
|
+
3. Is too long for the system prompt but too valuable to leave out
|
|
16
|
+
|
|
17
|
+
Do NOT create a skill for:
|
|
18
|
+
- Simple tool knowledge (just update the system prompt or tool description)
|
|
19
|
+
- API reference documentation (that's what context.rb is for in service gems)
|
|
20
|
+
- One-time tasks that won't repeat
|
|
21
|
+
|
|
22
|
+
## Skill File Format
|
|
23
|
+
|
|
24
|
+
Each skill is a directory with a `SKILL.md` file:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
lib/ask/skills/<name>/
|
|
28
|
+
└── SKILL.md
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The `SKILL.md` has YAML frontmatter followed by markdown body:
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
---
|
|
35
|
+
name: domain.skill_name # Unique identifier (lowercase, dots allowed)
|
|
36
|
+
description: One-line summary # Shown in system prompt skill list
|
|
37
|
+
version: 1 # Optional
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
Instructions body...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Writing Good Skill Content
|
|
44
|
+
|
|
45
|
+
1. **Start with context** — When would someone use this skill? What problem does it solve?
|
|
46
|
+
2. **Use numbered steps** — Clear progression, one concept per step
|
|
47
|
+
3. **Reference tools by name** — "Use `ReadModel.new.call(name: "User")`" not "check the model"
|
|
48
|
+
4. **Show examples** — Concrete tool invocations the agent can copy
|
|
49
|
+
5. **Explain reasoning** — Not just what to do, but why this order matters
|
|
50
|
+
6. **Handle failure** — "If this step fails, try..." for common pitfalls
|
|
51
|
+
7. **Keep focused** — One skill, one domain procedure. If it's too long, split into multiple skills.
|
|
52
|
+
|
|
53
|
+
## Skill Naming Convention
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
<domain>.<name>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
- `rails.db_debug` — Rails-specific database debugging
|
|
61
|
+
- `github.pr_review` — GitHub PR review workflow
|
|
62
|
+
- `shell.patterns` — Shell tool composition patterns
|
|
63
|
+
|
|
64
|
+
## Testing Your Skill
|
|
65
|
+
|
|
66
|
+
After creating a skill file, verify:
|
|
67
|
+
1. Frontmatter has both `name` and `description`
|
|
68
|
+
2. Name is lowercase with only letters, numbers, dots, hyphens
|
|
69
|
+
3. Description is a complete sentence
|
|
70
|
+
4. Instructions have numbered steps
|
|
71
|
+
5. Tools are referenced by their full Ruby class names
|
|
72
|
+
6. The skill directory contains `SKILL.md` (not `skill.md` or `README.md`)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Ask
|
|
2
|
+
module Skills
|
|
3
|
+
module Source
|
|
4
|
+
class Filesystem < Base
|
|
5
|
+
def initialize(dir: nil, project_dir: nil, user_dir: nil)
|
|
6
|
+
@path = dir || project_dir || (user_dir ? File.expand_path(user_dir) : nil)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def load
|
|
10
|
+
return [] unless @path && Dir.exist?(@path)
|
|
11
|
+
skills = []
|
|
12
|
+
Dir.entries(@path).each do |entry|
|
|
13
|
+
next if entry.start_with?(".")
|
|
14
|
+
skill_dir = File.join(@path, entry)
|
|
15
|
+
next unless File.directory?(skill_dir)
|
|
16
|
+
skill_file = File.join(skill_dir, "SKILL.md")
|
|
17
|
+
next unless File.exist?(skill_file)
|
|
18
|
+
next if File.directory?(skill_file)
|
|
19
|
+
if (skill = parse_skill(skill_file))
|
|
20
|
+
skills << skill
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
skills
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def parse_skill(path)
|
|
29
|
+
content = File.read(path)
|
|
30
|
+
frontmatter = parse_frontmatter(content)
|
|
31
|
+
body = extract_body(content)
|
|
32
|
+
|
|
33
|
+
name = frontmatter["name"] || File.basename(File.dirname(path))
|
|
34
|
+
description = frontmatter["description"] || ""
|
|
35
|
+
|
|
36
|
+
return nil if description.empty?
|
|
37
|
+
|
|
38
|
+
Skill.new(name: name, description: description, instructions: body, source: path)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parse_frontmatter(content)
|
|
42
|
+
return {} unless content.start_with?("---\n")
|
|
43
|
+
end_idx = content.index("\n---\n", 4)
|
|
44
|
+
return {} unless end_idx
|
|
45
|
+
yaml_str = content[4...end_idx]
|
|
46
|
+
yaml = {}
|
|
47
|
+
yaml_str.split("\n").each do |line|
|
|
48
|
+
if (m = line.match(/\A(\w+):\s*(.+)\z/))
|
|
49
|
+
value = m[2].strip
|
|
50
|
+
value = value.gsub(/\A"|"\z/, "").gsub(/\A'|'\z/, "")
|
|
51
|
+
yaml[m[1]] = value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
yaml
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def extract_body(content)
|
|
58
|
+
return content unless content.start_with?("---\n")
|
|
59
|
+
end_idx = content.index("\n---\n", 4)
|
|
60
|
+
return content unless end_idx
|
|
61
|
+
body = content[(end_idx + 5)..] || ""
|
|
62
|
+
body.sub(/\A\n/, "").strip
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Ask
|
|
2
|
+
module Skills
|
|
3
|
+
module Source
|
|
4
|
+
class Gems < Base
|
|
5
|
+
GLOB = "ask/skills/*/SKILL.md"
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
"Gems"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def load
|
|
12
|
+
skills = []
|
|
13
|
+
Gem.find_files(GLOB).each do |path|
|
|
14
|
+
if (skill = parse_skill(path))
|
|
15
|
+
skills << skill
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
skills
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def parse_skill(path)
|
|
24
|
+
content = File.read(path)
|
|
25
|
+
frontmatter = parse_frontmatter(content)
|
|
26
|
+
body = extract_body(content)
|
|
27
|
+
|
|
28
|
+
name = frontmatter["name"] || File.basename(File.dirname(path))
|
|
29
|
+
description = frontmatter["description"] || ""
|
|
30
|
+
|
|
31
|
+
return nil if description.empty?
|
|
32
|
+
|
|
33
|
+
Skill.new(name: name, description: description, instructions: body, source: path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_frontmatter(content)
|
|
37
|
+
return {} unless content.start_with?("---\n")
|
|
38
|
+
end_idx = content.index("\n---\n", 4)
|
|
39
|
+
return {} unless end_idx
|
|
40
|
+
yaml_str = content[4...end_idx]
|
|
41
|
+
yaml = {}
|
|
42
|
+
yaml_str.split("\n").each do |line|
|
|
43
|
+
if (m = line.match(/\A(\w+):\s*(.+)\z/))
|
|
44
|
+
value = m[2].strip
|
|
45
|
+
value = value.gsub(/\A"|"\z/, "").gsub(/\A'|'\z/, "")
|
|
46
|
+
yaml[m[1]] = value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
yaml
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def extract_body(content)
|
|
53
|
+
return content unless content.start_with?("---\n")
|
|
54
|
+
end_idx = content.index("\n---\n", 4)
|
|
55
|
+
return content unless end_idx
|
|
56
|
+
body = content[(end_idx + 5)..] || ""
|
|
57
|
+
body.sub(/\A\n/, "")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Ask
|
|
2
|
+
module Skills
|
|
3
|
+
class Validator
|
|
4
|
+
ValidationError = Data.define(:skill_name, :message)
|
|
5
|
+
|
|
6
|
+
NAME_PATTERN = /\A[a-z0-9_.-]+\z/
|
|
7
|
+
|
|
8
|
+
def initialize(skills)
|
|
9
|
+
@skills = skills
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validate_all
|
|
13
|
+
errors = []
|
|
14
|
+
names = {}
|
|
15
|
+
@skills.each do |skill|
|
|
16
|
+
errors.concat(validate(skill))
|
|
17
|
+
if names[skill.name]
|
|
18
|
+
errors << ValidationError.new(skill.name, "Duplicate skill name from #{names[skill.name]} and #{skill.source}")
|
|
19
|
+
end
|
|
20
|
+
names[skill.name] = skill.source
|
|
21
|
+
end
|
|
22
|
+
errors
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate(skill)
|
|
26
|
+
errors = []
|
|
27
|
+
errors << ValidationError.new(skill.name, "Name is empty") if skill.name.empty?
|
|
28
|
+
errors << ValidationError.new(skill.name, "Description is empty") if skill.description.empty?
|
|
29
|
+
errors << ValidationError.new(skill.name, "Instructions are empty") if skill.instructions.strip.empty?
|
|
30
|
+
if !skill.name.empty? && skill.name !~ NAME_PATTERN
|
|
31
|
+
errors << ValidationError.new(skill.name, "Name must be lowercase, with only letters, numbers, dots, hyphens, underscores")
|
|
32
|
+
end
|
|
33
|
+
errors
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/ask/skills.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require_relative "skills/version"
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Skills
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
autoload :Skill, "ask/skills/skill"
|
|
8
|
+
autoload :Registry, "ask/skills/registry"
|
|
9
|
+
autoload :Formatter, "ask/skills/formatter"
|
|
10
|
+
autoload :Validator, "ask/skills/validator"
|
|
11
|
+
|
|
12
|
+
module Source
|
|
13
|
+
autoload :Base, "ask/skills/sources/base"
|
|
14
|
+
autoload :Filesystem, "ask/skills/sources/filesystem"
|
|
15
|
+
autoload :Gems, "ask/skills/sources/gems"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def discover(sources: nil)
|
|
20
|
+
Registry.new(sources || default_sources)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def default_sources
|
|
24
|
+
[
|
|
25
|
+
# Highest priority first — first source wins in Registry
|
|
26
|
+
Source::Filesystem.new(project_dir: ".agents/skills"),
|
|
27
|
+
Source::Filesystem.new(user_dir: "~/.config/ask/skills"),
|
|
28
|
+
Source::Gems.new,
|
|
29
|
+
Source::Filesystem.new(dir: builtin_skills_dir),
|
|
30
|
+
]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def builtin_skills_dir
|
|
34
|
+
# Skills live in lib/ask/skills/<skill_name>/SKILL.md
|
|
35
|
+
# __dir__ in this file (lib/ask/skills.rb) is lib/ask/
|
|
36
|
+
# The skill directories are in lib/ask/skills/
|
|
37
|
+
File.join(__dir__, "skills")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/ask-skills.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative "ask/skills"
|
metadata
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ask-skills
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.25'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.25'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: mocha
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.1'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
description: Discovers, validates, and formats agent skills from project directories,
|
|
55
|
+
user config, and installed gems. Ships built-in skills (codebase exploration, debugging
|
|
56
|
+
methodology). Each skill is a markdown file with step-by-step instructions that
|
|
57
|
+
the agent loads on demand.
|
|
58
|
+
email:
|
|
59
|
+
- kaka@myrrlabs.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- lib/ask-skills.rb
|
|
68
|
+
- lib/ask/skills.rb
|
|
69
|
+
- lib/ask/skills/formatter.rb
|
|
70
|
+
- lib/ask/skills/registry.rb
|
|
71
|
+
- lib/ask/skills/skill.compose/SKILL.md
|
|
72
|
+
- lib/ask/skills/skill.design/SKILL.md
|
|
73
|
+
- lib/ask/skills/skill.rb
|
|
74
|
+
- lib/ask/skills/sources/base.rb
|
|
75
|
+
- lib/ask/skills/sources/filesystem.rb
|
|
76
|
+
- lib/ask/skills/sources/gems.rb
|
|
77
|
+
- lib/ask/skills/validator.rb
|
|
78
|
+
- lib/ask/skills/version.rb
|
|
79
|
+
homepage: https://github.com/ask-rb/ask-skills
|
|
80
|
+
licenses:
|
|
81
|
+
- MIT
|
|
82
|
+
metadata:
|
|
83
|
+
homepage_uri: https://github.com/ask-rb/ask-skills
|
|
84
|
+
source_code_uri: https://github.com/ask-rb/ask-skills
|
|
85
|
+
changelog_uri: https://github.com/ask-rb/ask-skills/blob/master/CHANGELOG.md
|
|
86
|
+
rdoc_options: []
|
|
87
|
+
require_paths:
|
|
88
|
+
- lib
|
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '3.2'
|
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: '0'
|
|
99
|
+
requirements: []
|
|
100
|
+
rubygems_version: 4.0.3
|
|
101
|
+
specification_version: 4
|
|
102
|
+
summary: Skill discovery and management for the ask-rb ecosystem
|
|
103
|
+
test_files: []
|