agent-context 0.3.0 → 0.4.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 +4 -4
- checksums.yaml.gz.sig +6 -1
- data/bake/agent/context.rb +18 -43
- data/context/getting-started.md +48 -12
- data/context/index.yaml +5 -3
- data/context/usage.md +73 -0
- data/design.md +78 -0
- data/lib/agent/context/definition.rb +58 -0
- data/lib/agent/context/document.rb +88 -0
- data/lib/agent/context/errors.rb +20 -0
- data/lib/agent/context/exclusion.rb +90 -0
- data/lib/agent/context/installer.rb +76 -210
- data/lib/agent/context/package.rb +116 -0
- data/lib/agent/context/paths.rb +12 -0
- data/lib/agent/context/skills/definition.rb +146 -0
- data/lib/agent/context/skills/ownership.rb +69 -0
- data/lib/agent/context/skills.rb +15 -0
- data/lib/agent/context/version.rb +1 -1
- data/lib/agent/context.rb +3 -2
- data/license.md +2 -2
- data/post.md +5 -3
- data/readme.md +71 -32
- data/releases.md +9 -0
- data/specification.md +97 -18
- data.tar.gz.sig +4 -3
- metadata +53 -31
- metadata.gz.sig +0 -0
- data/lib/agent/context/index.rb +0 -325
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '002181a9d06d9d2521d09d29a8bddb03ef5ffa46aefd7bea19dc36246db4c53b'
|
|
4
|
+
data.tar.gz: 4e495d06b1988bd25da07a298e16f9e857081d17a726755fc90d12bcaa8376f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc5ef49aaba4d1997680396829585ec70b7f3a37911f8b7fc39900acf51e62134a68cde878ee6475cafba7afe2edb72a7d3425f26e6eb7ea0ce15b4964f684ad
|
|
7
|
+
data.tar.gz: 824601f7c4d450ba62e05b8973f5d8ea39ecc16c6e399118abef78c08b57ef0f8216c5ee2dfc81f9b40502a5a763c884a13aecc94ecac521625d8281fb612825
|
checksums.yaml.gz.sig
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
&��b
|
|
2
|
+
H{�9���h~���S�4�֍�n"��ת�Ė���(Wz��F0/��:�
|
|
3
|
+
y�\����h�K���k��L���1��J�y�=��,ε�mO���a$�R���Եs�����ɵ�N���q����1�}:F-m��'�c��{����O�w՝��[���4|7[�q���͠: (�g�
|
|
4
|
+
�|I ��5������� qw�7j/& ��D#O��:�+h����z!G�yu_V �M�S�Z_��YEv՜�����b���?j*�u��F�\?��q���6d�ͪ���������C�dž���
|
|
5
|
+
�H���\���X�brK�
|
|
6
|
+
�=j�H�
|
data/bake/agent/context.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Shopify Inc.
|
|
5
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Shopify Inc.
|
|
5
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
6
6
|
|
|
7
7
|
require_relative "../../lib/agent/context/installer"
|
|
8
|
-
require_relative "../../lib/agent/context/index"
|
|
9
8
|
|
|
10
9
|
include Agent::Context
|
|
11
10
|
|
|
@@ -17,26 +16,25 @@ end
|
|
|
17
16
|
|
|
18
17
|
attr :installer
|
|
19
18
|
|
|
20
|
-
# List
|
|
21
|
-
# @parameter gem [String
|
|
19
|
+
# List providers and their context definitions, including skills.
|
|
20
|
+
# @parameter gem [String | Nil] An optional provider gem.
|
|
22
21
|
def list(gem: nil)
|
|
23
22
|
if gem
|
|
24
|
-
|
|
25
|
-
if
|
|
23
|
+
provider = @installer.find_provider(gem)
|
|
24
|
+
if provider && !provider.definitions.empty?
|
|
26
25
|
puts "Context files for gem '#{gem}':"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
puts " #{relative_path}"
|
|
26
|
+
provider.definitions.each do |definition|
|
|
27
|
+
puts " #{definition.display_name}"
|
|
30
28
|
end
|
|
31
29
|
else
|
|
32
30
|
puts "No context found for gem '#{gem}'"
|
|
33
31
|
end
|
|
34
32
|
else
|
|
35
|
-
gems = @installer.
|
|
33
|
+
gems = @installer.providers.reject{|provider| provider.definitions.empty?}
|
|
36
34
|
if gems.any?
|
|
37
35
|
puts "Gems with context available:"
|
|
38
|
-
gems.each do |
|
|
39
|
-
puts " #{
|
|
36
|
+
gems.each do |provider|
|
|
37
|
+
puts " #{provider.name} (#{provider.version})"
|
|
40
38
|
end
|
|
41
39
|
else
|
|
42
40
|
puts "No gems with context found"
|
|
@@ -44,9 +42,9 @@ def list(gem: nil)
|
|
|
44
42
|
end
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
# Show
|
|
45
|
+
# Show one context definition from a provider.
|
|
48
46
|
# @parameter gem [String] The gem name.
|
|
49
|
-
# @parameter file [String] The
|
|
47
|
+
# @parameter file [String] The source filename or skill name.
|
|
50
48
|
def show(gem:, file:)
|
|
51
49
|
content = @installer.show_context_file(gem, file)
|
|
52
50
|
if content
|
|
@@ -56,33 +54,10 @@ def show(gem:, file:)
|
|
|
56
54
|
end
|
|
57
55
|
end
|
|
58
56
|
|
|
59
|
-
# Install context
|
|
60
|
-
# @parameter gem [String
|
|
57
|
+
# Install ordinary context and skills.
|
|
58
|
+
# @parameter gem [String | Nil] An optional provider gem.
|
|
61
59
|
def install(gem: nil)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
else
|
|
66
|
-
puts "No context found for gem '#{gem}'"
|
|
67
|
-
end
|
|
68
|
-
else
|
|
69
|
-
installed = @installer.install_all_context
|
|
70
|
-
if installed.any?
|
|
71
|
-
puts "Installed context from #{installed.length} gems:"
|
|
72
|
-
installed.each {|gem_name| puts " #{gem_name}"}
|
|
73
|
-
else
|
|
74
|
-
puts "No gems with context found"
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Update agents.md after installing context
|
|
79
|
-
index = Agent::Context::Index.new(@installer.context_path)
|
|
80
|
-
index.update_agents_md
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Update or create AGENTS.md in the project root with context section
|
|
84
|
-
# This follows the AGENTS.md specification for agentic coding tools
|
|
85
|
-
def agents_md(path = "agents.md")
|
|
86
|
-
index = Agent::Context::Index.new(@installer.context_path)
|
|
87
|
-
index.update_agents_md(path)
|
|
60
|
+
installed = @installer.install(gem: gem)
|
|
61
|
+
puts "Installed context from: #{installed[:context].join(", ")}" unless installed[:context].empty?
|
|
62
|
+
puts "Installed skills: #{installed[:skills].join(", ")}" unless installed[:skills].empty?
|
|
88
63
|
end
|
data/context/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
This guide explains how to
|
|
3
|
+
This guide explains how to install package guidance and skills using `agent-context`.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -37,26 +37,35 @@ bake agent:context:list --gem async
|
|
|
37
37
|
|
|
38
38
|
# View a specific context file
|
|
39
39
|
bake agent:context:show --gem async --file thread-safety
|
|
40
|
+
|
|
41
|
+
# List, show, or install a skill through the context tasks
|
|
42
|
+
bake agent:context:list --gem provider
|
|
43
|
+
bake agent:context:show --gem provider --file workflow
|
|
44
|
+
bake agent:context:install --gem provider
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
## Understanding context/ vs .context/
|
|
47
|
+
## Understanding context/ vs .agents/context/
|
|
43
48
|
|
|
44
49
|
**Important distinction:**
|
|
45
50
|
- **`context/`** (no dot) = Directory in gems that contains context files to share.
|
|
46
|
-
- **`.context/`** (with dot) = Directory in your project where context gets installed.
|
|
51
|
+
- **`.agents/context/`** (with dot) = Directory in your project where context gets installed.
|
|
47
52
|
|
|
48
53
|
### What happens when you install context?
|
|
49
54
|
|
|
50
55
|
When you run `bake agent:context:install`, the tool:
|
|
51
56
|
|
|
52
|
-
1. Scans
|
|
53
|
-
2. Creates a `.context/` directory in your current project.
|
|
54
|
-
3. Copies context files organized by gem name.
|
|
57
|
+
1. Scans resolved project gems for `context/` directories (in the gem's root).
|
|
58
|
+
2. Creates a `.agents/context/` directory in your current project.
|
|
59
|
+
3. Copies ordinary context files organized by gem name.
|
|
60
|
+
4. Installs metadata-declared skills and their resources into `.agents/skills/`.
|
|
61
|
+
5. Updates local Git exclusions for installed context and dependency skills.
|
|
62
|
+
|
|
63
|
+
Discover installed Markdown guides by listing files under `.agents/context/GEM/`. Repository instructions can direct agents to read the guides relevant to their task.
|
|
55
64
|
|
|
56
65
|
For example:
|
|
57
66
|
```
|
|
58
67
|
your-project/
|
|
59
|
-
├── .context/ # ← Installed context (with dot)
|
|
68
|
+
├── .agents/context/ # ← Installed context (with dot)
|
|
60
69
|
│ ├── async/ # ← From the 'async' gem's context/ directory
|
|
61
70
|
│ │ ├── thread-safety.md
|
|
62
71
|
│ │ └── performance.md
|
|
@@ -87,9 +96,10 @@ async-gem/
|
|
|
87
96
|
|
|
88
97
|
### Key Points for Users
|
|
89
98
|
|
|
90
|
-
- Run `bake agent:context:install` to copy context to `.context/` (with dot).
|
|
91
|
-
- The `.context/` directory is where installed context lives in your project.
|
|
92
|
-
-
|
|
99
|
+
- Run `bake agent:context:install` to copy context to `.agents/context/` (with dot).
|
|
100
|
+
- The `.agents/context/` directory is where installed context lives in your project.
|
|
101
|
+
- Installation maintains local Git exclusions for generated context and dependency-owned skills. Track project-owned instructions and skills under `.agents/` and remove blanket `/.agents/` ignore rules.
|
|
102
|
+
- Edit provider sources and reinstall to update generated files in `.agents/context/`.
|
|
93
103
|
|
|
94
104
|
## Providing Context (For Gem Authors)
|
|
95
105
|
|
|
@@ -109,7 +119,7 @@ your-gem/
|
|
|
109
119
|
└── your-gem.gemspec
|
|
110
120
|
```
|
|
111
121
|
|
|
112
|
-
**Important:** This is different from `.context/` (with dot) which is where context gets installed in user projects.
|
|
122
|
+
**Important:** This is different from `.agents/context/` (with dot) which is where context gets installed in user projects.
|
|
113
123
|
|
|
114
124
|
#### 2. Add context files
|
|
115
125
|
|
|
@@ -141,6 +151,8 @@ Available context files:
|
|
|
141
151
|
|
|
142
152
|
#### 4. File format and content guidelines
|
|
143
153
|
|
|
154
|
+
Use descriptive filenames, a clear heading, and an introduction for each Markdown guide. Link related guides so agents can follow them when they need more detail.
|
|
155
|
+
|
|
144
156
|
Context files can be in any format, but `.md` is commonly used for documentation. The content should be:
|
|
145
157
|
|
|
146
158
|
- **Practical** - Include real examples and working code.
|
|
@@ -155,6 +167,30 @@ Context files can be in any format, but `.md` is commonly used for documentation
|
|
|
155
167
|
- Put helpful guides for users of your gem there.
|
|
156
168
|
- Focus on practical usage, not API documentation.
|
|
157
169
|
|
|
170
|
+
## Providing Skills
|
|
171
|
+
|
|
172
|
+
To provide a skill, put a Markdown document directly inside `context/`:
|
|
173
|
+
|
|
174
|
+
```markdown
|
|
175
|
+
---
|
|
176
|
+
metadata:
|
|
177
|
+
type: skill
|
|
178
|
+
description: Follow the workflow when setting up this package.
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
# Workflow
|
|
182
|
+
|
|
183
|
+
Follow the setup instructions.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`context/workflow.md` in gem `provider` defaults to `provider-workflow/SKILL.md`. An authored `name` is preserved and used as the installed directory name. The matching `context/workflow/` directory supplies resources. The generated instructions include additional skill metadata. Installed names must use lowercase ASCII letters, digits and single hyphens, with at most 64 characters. Descriptions must contain 1–1,024 characters.
|
|
187
|
+
|
|
188
|
+
Include the complete source directories in your gem's packaged files. Skill documents and resource trees are installed under `.agents/skills/`.
|
|
189
|
+
|
|
190
|
+
## Ownership and Updates
|
|
191
|
+
|
|
192
|
+
Installed `SKILL.md` front matter records `metadata.type: skill` and the resolved `metadata.ecosystem`, `metadata.package`, and `metadata.version`. Ruby refreshes reconcile skills within their selected ecosystem and package scope. A full refresh also removes skills from removed or empty providers. After resolving an installation error, rerun the command to refresh generated files.
|
|
193
|
+
|
|
158
194
|
## Example Context Files
|
|
159
195
|
|
|
160
196
|
For examples of well-structured context files, see the existing files in this directory:
|
|
@@ -186,4 +222,4 @@ Before publishing, test your context files:
|
|
|
186
222
|
## Summary
|
|
187
223
|
|
|
188
224
|
- **`context/`** = source (in gems).
|
|
189
|
-
- **`.context/`** = destination (in your project).
|
|
225
|
+
- **`.agents/context/`** = destination (in your project).
|
data/context/index.yaml
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Automatically generated context index for Utopia::Project guides.
|
|
2
2
|
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
3
|
---
|
|
4
|
-
description: Install and manage context files from Ruby gems.
|
|
4
|
+
description: Install and manage context files and skills from Ruby gems.
|
|
5
5
|
metadata:
|
|
6
|
+
bug_tracker_uri: https://github.com/ioquatix/agent-context/issues
|
|
7
|
+
changelog_uri: https://github.com/ioquatix/agent-context/blob/main/releases.md
|
|
6
8
|
documentation_uri: https://ioquatix.github.io/agent-context/
|
|
7
9
|
funding_uri: https://github.com/sponsors/ioquatix/
|
|
8
10
|
source_code_uri: https://github.com/ioquatix/agent-context.git
|
|
9
11
|
files:
|
|
10
12
|
- path: getting-started.md
|
|
11
13
|
title: Getting Started
|
|
12
|
-
description: This guide explains how to
|
|
13
|
-
|
|
14
|
+
description: This guide explains how to install package guidance and skills using
|
|
15
|
+
`agent-context`.
|
data/context/usage.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
metadata:
|
|
3
|
+
type: skill
|
|
4
|
+
description: Install and navigate guidance and skills from Ruby gem dependencies, or publish context and skills from a gem using agent-context.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Using and Providing Context
|
|
8
|
+
|
|
9
|
+
Use `agent-context` to find practical guidance shipped by Ruby gem dependencies and make package guidance available to downstream agents.
|
|
10
|
+
|
|
11
|
+
## Use Dependency Guidance
|
|
12
|
+
|
|
13
|
+
Follow the repository's agent instructions and discover installed dependency guidance by listing Markdown files under `.agents/context/GEM/`. Use descriptive filenames to select relevant guides, then read them before changing code that uses the package. For example, `rg --files --hidden --no-ignore .agents/context -g "*.md"` lists the installed Markdown files.
|
|
14
|
+
|
|
15
|
+
If relevant installed files are missing, run `bundle exec bake agent:context:install` from the consuming project. Run it again after changing dependencies. It installs ordinary context and skills from resolved dependency gems.
|
|
16
|
+
|
|
17
|
+
To inspect or install guidance from a specific provider:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle exec bake agent:context:list
|
|
21
|
+
bundle exec bake agent:context:list --gem async
|
|
22
|
+
bundle exec bake agent:context:show --gem async --file thread-safety
|
|
23
|
+
bundle exec bake agent:context:install --gem async
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`show` reads the provider's source file; the `.md` extension is optional. `install --gem NAME` installs both ordinary context and skills from that provider.
|
|
27
|
+
|
|
28
|
+
Ordinary context is copied to `.agents/context/GEM/`. Repository instructions can direct agents to discover and read relevant guides there.
|
|
29
|
+
|
|
30
|
+
Source filenames take precedence over installed skill names. The `show` command returns authored content, including for skills; generated installation metadata is available in the installed `SKILL.md`.
|
|
31
|
+
|
|
32
|
+
## Use Dependency Skills
|
|
33
|
+
|
|
34
|
+
Skills contain procedures for particular tasks and are installed under `.agents/skills/`. Use the matching skill's instructions and resources when its description applies to the work.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle exec bake agent:context:list --gem agent-context
|
|
38
|
+
bundle exec bake agent:context:show --gem agent-context --file usage
|
|
39
|
+
bundle exec bake agent:context:install
|
|
40
|
+
bundle exec bake agent:context:install --gem agent-context
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The context list marks skill documents with `(skill)`. Show them by their source filename, and install them together with ordinary context. An authored skill name determines its installed directory; a missing name defaults to `<gem>-<filename>`. This document installs as `agent-context-usage/SKILL.md`.
|
|
44
|
+
|
|
45
|
+
## Keep Sources and Installed Files Separate
|
|
46
|
+
|
|
47
|
+
The provider's tracked `context/` directory is source content. Installed dependency copies under `.agents/context/` and `.agents/skills/` are generated; edit provider sources and reinstall to update them. Keep repository-only instructions and project-owned skills in the consuming repository.
|
|
48
|
+
|
|
49
|
+
Installation maintains local Git exclusions for generated context and exact dependency-installed skill directories. Track project-owned skills under `.agents/skills/` and remove blanket `/.agents/` ignore rules when adopting this layout.
|
|
50
|
+
|
|
51
|
+
Installed `SKILL.md` front matter records `metadata.type: skill` and the resolved `metadata.ecosystem`, `metadata.package`, and `metadata.version`. Ruby refreshes reconcile skills within their selected ecosystem and package scope. A full refresh also removes skills from removed or empty providers. After resolving an installation error, rerun the command to refresh generated files.
|
|
52
|
+
|
|
53
|
+
## Publish Context and Skills
|
|
54
|
+
|
|
55
|
+
Put focused Markdown guides in the gem's `context/` directory, using descriptive lowercase filenames. Give each guide a clear heading and introduction, and link related guides together.
|
|
56
|
+
|
|
57
|
+
To publish a skill, add `context/workflow.md` with metadata describing when to use it:
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
---
|
|
61
|
+
metadata:
|
|
62
|
+
type: skill
|
|
63
|
+
description: Set up a project using this gem's conventions.
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Workflow
|
|
67
|
+
|
|
68
|
+
Follow the setup instructions.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For this example, gem `provider` derives the missing name and installs the document as `.agents/skills/provider-workflow/SKILL.md`. Put companion resources in `context/workflow/`; they are copied alongside the generated instructions. The installer preserves an authored `name` and derives a missing name from the provider package and filename. It retains the description, additional metadata, and original Markdown body, and adds resolved provenance under `metadata`. The top-level resource name `SKILL.md` is reserved for generated instructions.
|
|
72
|
+
|
|
73
|
+
Include `context/**/*` in the gemspec's packaged files. Installed skill names must use lowercase ASCII letters, digits, and single hyphens, with at most 64 characters. Descriptions must contain 1–1,024 characters and explain the task that should activate the skill.
|
data/design.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Agent Context Design
|
|
2
|
+
|
|
3
|
+
The Ruby `agent-context` gem and Rust `bake-agent-context` crate share a portable installation contract. The [specification](specification.md) defines file formats and observable behavior; this guide maps the implementation responsibilities.
|
|
4
|
+
|
|
5
|
+
Choose the simplest structure that makes the behavior, state, and responsibilities easy to follow.
|
|
6
|
+
|
|
7
|
+
## Responsibilities
|
|
8
|
+
|
|
9
|
+
| Concern | Ruby | Rust |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Resolved package specification | `Gem::Specification`, retained by `Package` | `Specification`, resolved by `Installer::discover` |
|
|
12
|
+
| Parsed Markdown | `Document` | `Document` in `document.rs` |
|
|
13
|
+
| Loaded provider | `Package` wrapping `Gem::Specification` | `Package` wrapping `Specification` |
|
|
14
|
+
| Provider lookup | `Installer#providers`, `#find_provider` | `Installer::providers`, `find_provider` |
|
|
15
|
+
| Definition lookup | `Package#find_definition` | `Package::find_definition` |
|
|
16
|
+
| Ordinary file | `Definition` | `FileDefinition` |
|
|
17
|
+
| All definition variants | `Definition` and `Skills::Definition < Definition` | `Definition::File(FileDefinition)` or `Definition::Skill(SkillDefinition)` |
|
|
18
|
+
| Installation workflow | `Installer#install` | `Installer::install` |
|
|
19
|
+
| Installed provenance | `Skills::Ownership` | `ownership` module |
|
|
20
|
+
| Local Git exclusions | `Exclusion` | `exclude` module |
|
|
21
|
+
| Command presentation | Bake task adapters | Bake task adapters |
|
|
22
|
+
| Errors | `Agent::Context::Error`, `InvalidError`, `ConflictError` | `bake::Error` returned through `Result` |
|
|
23
|
+
|
|
24
|
+
Each parser, metadata rule, and file format has one owner. Document parsing interprets Markdown and YAML; skill definitions apply name and description rules; ownership interprets installed provenance; installers select destinations and change files. Constructors retain supplied state. Reading, parsing, discovery, and installation have explicit entry points.
|
|
25
|
+
|
|
26
|
+
Ruby's loaded `Package` retains its `Gem::Specification` and definitions. Rust's resolved `Specification` carries Cargo metadata and the context path; loaded `Package` wraps that specification with its definitions. Both apply the same document and installation rules.
|
|
27
|
+
|
|
28
|
+
Ruby uses the gem name as its package selector. Rust uses the crate name when unique and `crate@version` when multiple versions provide context. These selectors identify providers in commands and installed context directories. “Provider” is the domain role of a package that supplies context. Both installers expose `providers` and `find_provider`, returning loaded packages. A provider with an existing empty `context/` directory has an empty definition collection; a dependency without that directory is absent from provider lookup. Listing commands show providers with available definitions. Rust additionally exposes resolved Cargo metadata through `specifications` and `find_specification`.
|
|
29
|
+
|
|
30
|
+
## Documents and Front Matter
|
|
31
|
+
|
|
32
|
+
`Document.load(path)` and `Document::load(path)` read the source and delegate to `parse`. The parsed value retains the source path, original Markdown, decoded YAML mapping, and the Markdown parser's front matter node or source boundary.
|
|
33
|
+
|
|
34
|
+
Both implementations use their Markdown parser's front matter node to read and serialize front matter: Markly in Ruby and `socketry-markdown` in Rust. The document layer decodes and encodes YAML through the node's raw value and classifies context using the nested `metadata.type` field. The skill predicate matches `skill`; all other values follow the ordinary context path. Only a `metadata` mapping is interpreted as context metadata; other authored values remain intact in the source. Ordinary guides can carry their own top-level fields. Front matter uses YAML by default or with the explicit `yaml` language hint. Other front matter languages remain opaque.
|
|
35
|
+
|
|
36
|
+
A skill definition validates its top-level name and description. It preserves an authored name and uses it for the installed directory. When the name is omitted, it derives `<package>-<filename>`. Resource directories follow the source filename. Description validation trims whitespace to measure the value; the authored value is retained.
|
|
37
|
+
|
|
38
|
+
To generate installed instructions, the definition merges resolved `ecosystem`, `package`, and `version` into the nested metadata mapping and supplies a missing name. The document writes the YAML into a copy of its front matter AST node and asks the Markdown library to serialize that node. It appends the original body using the parser's source boundary. Links, code blocks, spacing, Unicode, and body line endings retain their original bytes.
|
|
39
|
+
|
|
40
|
+
The portable YAML form uses `---` delimiters. Rust also recognizes the additional front matter forms supported by `socketry-markdown`, such as a leading YAML code fence, and retains their delimiter style. Markly's supported syntax defines the Ruby parsing boundary.
|
|
41
|
+
|
|
42
|
+
## Discovery and Loading
|
|
43
|
+
|
|
44
|
+
For each provider, load Markdown documents before descending into subdirectories. Root-level skill declarations identify companion resource directories. Keep those resources opaque, including Markdown-looking assets.
|
|
45
|
+
|
|
46
|
+
The loaded package stores ordinary definitions and skills in one collection. Ruby uses `Definition` for ordinary files and `Skills::Definition < Definition` for skills. Rust represents the same alternatives with a `Definition` enum: `File(FileDefinition)` carries the provider-relative destination path, source path, and optional parsed `Document`; `Skill(SkillDefinition)` carries skill-specific metadata and resources. Both implementations use `Document` for parsed Markdown.
|
|
47
|
+
|
|
48
|
+
Listing, showing, and installation use this classification. Each definition's `path` is relative to the provider's `context/` directory; `source_path` locates the authored file. A skill's `name` identifies its installed directory. Definition lists are ordered by source path.
|
|
49
|
+
|
|
50
|
+
`Package.find_definition(file)` checks the exact source path, then appends `.md` when no extension was given, and finally checks installed skill names. The lookup uses the loaded definitions. `content` and the `show` command return authored content for every definition. Skill definitions retain their parsed `Document` and resolved specification, and expose generated instructions as `installation_content`.
|
|
51
|
+
|
|
52
|
+
The main installation operation resolves providers and loads each provider once, then applies the ordinary or skill destination rules to each definition. `Package.install_files(destination)` refreshes ordinary files in a package directory. A skill definition's `install(destination)` writes generated instructions and resources into its skill directory. Ruby's ordinary `Definition#write_to(path)` writes authored content to a file path, including when called on a skill definition.
|
|
53
|
+
|
|
54
|
+
## Installation Flow
|
|
55
|
+
|
|
56
|
+
The library owns orchestration. Bake tasks translate arguments, call the library, and present installed provider and skill names. Installation results contain package selectors under `context` and plain installed skill names under `skills`; presentation formatting belongs in task adapters.
|
|
57
|
+
|
|
58
|
+
1. Resolve the selected providers and load their definitions.
|
|
59
|
+
2. Validate and install skills under `.agents/skills/`, reconcile stale entries within the selected scope, and refresh Git exclusions.
|
|
60
|
+
3. Install ordinary definitions under `.agents/context/`.
|
|
61
|
+
|
|
62
|
+
Generated files use direct `File.write` or `std::fs::write`. Completed steps remain in place if a later operation fails. Report the relevant path and let the caller resolve the failure and rerun. Cleanup around a failed instruction write removes the incomplete destination; a later resource-copy failure retains the completed instructions and their provenance.
|
|
63
|
+
|
|
64
|
+
Loading and writing are separate operations over a loaded snapshot. `Document.load(path).write(destination)` expresses that contract. Installation is a local, rerunnable workflow with incremental progress.
|
|
65
|
+
|
|
66
|
+
## Ownership and Git Exclusions
|
|
67
|
+
|
|
68
|
+
Installed `SKILL.md` metadata carries the resolved ecosystem, package, and version. Scanning direct skill directories supplies the names used for reconciliation and local Git exclusions. Ownership recognition requires `metadata.type: skill` and an explicit ecosystem before validating the complete tuple. Project skills can use unrelated metadata such as a version without claiming installation ownership.
|
|
69
|
+
|
|
70
|
+
A full refresh selects one ecosystem; a package refresh selects one ecosystem and package. Destination collisions are reported. Other owned scopes and project skills remain available.
|
|
71
|
+
|
|
72
|
+
Git exclusion handling locates and reads the local exclusion file, replaces the consuming project's marked block, and writes the result directly. Prefixes distinguish nested projects sharing a repository. The block lists generated context and exact installed skill directories from all ecosystems.
|
|
73
|
+
|
|
74
|
+
## Readability and Verification
|
|
75
|
+
|
|
76
|
+
Multi-step operations group related calculations and effects under short behavioral comments, separated by blank lines. Method documentation explains single-step operations. Helpers represent distinct responsibilities or independently meaningful operations.
|
|
77
|
+
|
|
78
|
+
Both implementations exercise the shared `SKILL.md` fixture. Compare decoded metadata for format rules and exact body bytes for preservation. Tests cover classification, authored and derived names, metadata preservation, ownership scopes, opaque resources, Git exclusions, failures, and a successful retry. Keep the specification and this component mapping aligned when behavior or responsibilities change.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Shopify Inc.
|
|
5
|
+
|
|
6
|
+
require "fileutils"
|
|
7
|
+
|
|
8
|
+
module Agent
|
|
9
|
+
module Context
|
|
10
|
+
# Represents one ordinary file provided in a package's context directory.
|
|
11
|
+
class Definition
|
|
12
|
+
# Initialize a context definition from its paths and parsed document.
|
|
13
|
+
# @parameter path [String] The path relative to the provider's context directory.
|
|
14
|
+
# @parameter source_path [String] The source file path.
|
|
15
|
+
# @parameter document [Document | Nil] The parsed Markdown document, when applicable.
|
|
16
|
+
def initialize(path, source_path, document = nil)
|
|
17
|
+
@path = path
|
|
18
|
+
@source_path = source_path
|
|
19
|
+
@document = document
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @attribute [String] The path relative to the provider's context directory.
|
|
23
|
+
attr_reader :path
|
|
24
|
+
|
|
25
|
+
# @attribute [String] The source file path.
|
|
26
|
+
attr_reader :source_path
|
|
27
|
+
|
|
28
|
+
# Whether this definition installs as an Agent Skill.
|
|
29
|
+
# @returns [bool] `false` for ordinary context definitions.
|
|
30
|
+
def skill?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Read the definition's Markdown content or opaque file contents.
|
|
35
|
+
# @returns [String] The source content.
|
|
36
|
+
def content
|
|
37
|
+
@document ? @document.content : File.read(@source_path)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Return the relative name displayed by the context list.
|
|
41
|
+
# @returns [String] The path relative to the provider context directory.
|
|
42
|
+
def display_name
|
|
43
|
+
@path
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Write the definition to its installation destination.
|
|
47
|
+
# @parameter destination [String] The target file path.
|
|
48
|
+
def write_to(destination)
|
|
49
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
50
|
+
if @document
|
|
51
|
+
@document.write(destination)
|
|
52
|
+
else
|
|
53
|
+
FileUtils.copy_file(@source_path, destination, true)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Shopify Inc.
|
|
5
|
+
|
|
6
|
+
require "markly"
|
|
7
|
+
require "yaml"
|
|
8
|
+
require "date"
|
|
9
|
+
require_relative "errors"
|
|
10
|
+
|
|
11
|
+
module Agent
|
|
12
|
+
module Context
|
|
13
|
+
# Represents a Markdown source and its parsed front matter.
|
|
14
|
+
class Document
|
|
15
|
+
# Read and parse a Markdown document.
|
|
16
|
+
def self.load(path)
|
|
17
|
+
parse(File.read(path), path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Parse YAML front matter using Markdown's native syntax tree.
|
|
21
|
+
def self.parse(content, path)
|
|
22
|
+
# Let Markly identify the front matter and its source boundary:
|
|
23
|
+
first = Markly.parse(content, flags: Markly::FRONT_MATTER).first_child
|
|
24
|
+
node = first if first&.type == :front_matter
|
|
25
|
+
language = node&.fence_info&.split&.first
|
|
26
|
+
|
|
27
|
+
# Interpret YAML metadata, keeping other formats opaque:
|
|
28
|
+
frontmatter = {}
|
|
29
|
+
if node && [nil, "yaml"].include?(language)
|
|
30
|
+
frontmatter = YAML.safe_load(node.string_content, permitted_classes: [Date, Time], aliases: false, fallback: {})
|
|
31
|
+
raise InvalidError, "Context frontmatter must be a mapping: #{path}" unless frontmatter.is_a?(Hash)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Interpret only mappings as context metadata, retaining other authored values:
|
|
35
|
+
metadata = frontmatter["metadata"]
|
|
36
|
+
metadata = {} unless metadata.is_a?(Hash)
|
|
37
|
+
new(path, content, node, frontmatter, metadata)
|
|
38
|
+
rescue Psych::Exception => error
|
|
39
|
+
raise InvalidError, "Invalid YAML frontmatter in #{path}: #{error.message}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Retain the source and its parsed representation.
|
|
43
|
+
def initialize(path, content, node, frontmatter, metadata)
|
|
44
|
+
@path = path
|
|
45
|
+
@content = content
|
|
46
|
+
@node = node
|
|
47
|
+
@frontmatter = frontmatter
|
|
48
|
+
@metadata = metadata
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @attribute [String] The document source path.
|
|
52
|
+
attr_reader :path
|
|
53
|
+
|
|
54
|
+
# @attribute [String] The original Markdown source.
|
|
55
|
+
attr_reader :content
|
|
56
|
+
|
|
57
|
+
# @attribute [Hash] The decoded YAML front matter.
|
|
58
|
+
attr_reader :frontmatter
|
|
59
|
+
|
|
60
|
+
# The extensible metadata mapping within front matter.
|
|
61
|
+
attr_reader :metadata
|
|
62
|
+
|
|
63
|
+
# Whether the document declares a skill.
|
|
64
|
+
def skill?
|
|
65
|
+
metadata["type"] == "skill"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The original body following the parser's front matter boundary.
|
|
69
|
+
def body
|
|
70
|
+
return @content unless @node
|
|
71
|
+
|
|
72
|
+
@content.lines.drop(@node.source_position[:end_line]).join
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Serialize edited front matter through its AST node, preserving the body.
|
|
76
|
+
def render(frontmatter)
|
|
77
|
+
node = @node.dup
|
|
78
|
+
node.string_content = YAML.dump(frontmatter).delete_prefix("---\n")
|
|
79
|
+
node.to_markdown + body
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Write the loaded source to its destination.
|
|
83
|
+
def write(path)
|
|
84
|
+
File.write(path, @content)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Shopify Inc.
|
|
5
|
+
|
|
6
|
+
module Agent
|
|
7
|
+
module Context
|
|
8
|
+
# Base class for errors raised while loading or installing context.
|
|
9
|
+
class Error < StandardError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Raised when provider content or installation metadata is invalid.
|
|
13
|
+
class InvalidError < Error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Raised when providers or installation destinations conflict.
|
|
17
|
+
class ConflictError < Error
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|