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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11e3e6b44ae4b564cf5a70031206df479edcc02e716c0c6eadc74e36f8118bd8
4
- data.tar.gz: fe26f5e667e56ab362581df7b8e591d570a7f666638b12d31baa41daef4667f7
3
+ metadata.gz: '002181a9d06d9d2521d09d29a8bddb03ef5ffa46aefd7bea19dc36246db4c53b'
4
+ data.tar.gz: 4e495d06b1988bd25da07a298e16f9e857081d17a726755fc90d12bcaa8376f0
5
5
  SHA512:
6
- metadata.gz: 49995cbd0a102933c17760bf7d11b39d788e1bc91a1b0da6db3ff5cc70b6d047f5d806bf822a3a4e12073ba3cc244b60204ea84466b41d4155ffdb2da940fc83
7
- data.tar.gz: fdad0c51486de3e66bdd3a7e2eeb9dfd0485dbcaec56256f95e9942489d38270ad9d972640de11f6be3d3de21cdbf2e195e608381153281e0ef31bfe0f6cbd1e
6
+ metadata.gz: dc5ef49aaba4d1997680396829585ec70b7f3a37911f8b7fc39900acf51e62134a68cde878ee6475cafba7afe2edb72a7d3425f26e6eb7ea0ce15b4964f684ad
7
+ data.tar.gz: 824601f7c4d450ba62e05b8973f5d8ea39ecc16c6e399118abef78c08b57ef0f8216c5ee2dfc81f9b40502a5a763c884a13aecc94ecac521625d8281fb612825
checksums.yaml.gz.sig CHANGED
@@ -1 +1,6 @@
1
- �m���ѧ�h���Dv"ĵ'U��nn?{�)k���@�����������Cl^*��KєH��N#z�inho,z0�A-ӵp`M��D›G�dnDLc�q��.�/��$��=��=Ē�\x���fÇ�et„��ِ�O�.,T/�\�Q���ıVF�Hz,�\��&�i�b_@���6��X���]�`JN�*'������L�9/p8��b)�͆���+�Ė�W��+0*��{��o�#�T26_�$ؽo|��;��ǀ��Ԩf'�#�o�C�LEȒ���/�]E�aԻ���OLH���)��b�8�G�"!����|�o[>���یg�b`j�}�����D
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�
@@ -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 all gems that have context available.
21
- # @parameter gem [String] Optional specific gem name to list context files for.
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
- files = @installer.list_context_files(gem)
25
- if files
23
+ provider = @installer.find_provider(gem)
24
+ if provider && !provider.definitions.empty?
26
25
  puts "Context files for gem '#{gem}':"
27
- files.each do |file|
28
- relative_path = Pathname.new(file).relative_path_from(Pathname.new(@installer.find_gem_with_context(gem)[:path]))
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.find_gems_with_context
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 |gem_info|
39
- puts " #{gem_info[:name]} (#{gem_info[:version]})"
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 the content of a specific context file.
45
+ # Show one context definition from a provider.
48
46
  # @parameter gem [String] The gem name.
49
- # @parameter file [String] The context file name.
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 files from gems into the current project.
60
- # @parameter gem [String] Optional specific gem name to install context from.
57
+ # Install ordinary context and skills.
58
+ # @parameter gem [String | Nil] An optional provider gem.
61
59
  def install(gem: nil)
62
- if gem
63
- if @installer.install_gem_context(gem)
64
- puts "Installed context from gem '#{gem}'"
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
@@ -1,6 +1,6 @@
1
1
  # Getting Started
2
2
 
3
- This guide explains how to use `agent-context`, a tool for discovering and installing contextual information from Ruby gems to help AI agents.
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 all installed gems for `context/` directories (in the gem's root).
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
- - Don't edit files in `.context/` - they get completely replaced when you reinstall.
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 use `agent-context`, a tool for discovering
13
- and installing contextual information from Ruby gems to help AI agents.
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