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.
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Shopify Inc.
5
+
6
+ require "fileutils"
7
+ require_relative "../definition"
8
+ require_relative "../errors"
9
+ require_relative "ownership"
10
+
11
+ module Agent
12
+ module Context
13
+ module Skills
14
+ # Represents a dependency skill declared by a context document and its resources.
15
+ class Definition < Agent::Context::Definition
16
+ # Build instructions from a parsed skill document and its provider.
17
+ def self.from_document(source, specification)
18
+ # Preserve authored names and derive missing names from the provider and file:
19
+ file = source.path
20
+ local_name = File.basename(file, File.extname(file))
21
+ installed_name = source.frontmatter.fetch("name"){"#{specification.name.downcase.tr("_", "-")}-#{local_name}"}
22
+
23
+ # Add the resolved provider identity to the authored front matter:
24
+ frontmatter = source.frontmatter.merge("name" => installed_name)
25
+ frontmatter["metadata"] = source.metadata.merge(
26
+ "ecosystem" => "gem",
27
+ "package" => specification.name,
28
+ "version" => specification.version.to_s,
29
+ )
30
+ validate_metadata(frontmatter, file)
31
+
32
+ # Resolve resources using the source filename:
33
+ assets = File.join(File.dirname(file), local_name)
34
+ if File.exist?(assets) || File.symlink?(assets)
35
+ unless File.lstat(assets).directory?
36
+ raise InvalidError, "Skill resources must be a regular directory: #{assets}"
37
+ end
38
+ else
39
+ assets = nil
40
+ end
41
+
42
+ installation_content = source.render(frontmatter)
43
+
44
+ new(source, specification, installed_name, frontmatter["description"], assets, installation_content)
45
+ end
46
+
47
+ # Validate an installed directory name.
48
+ def self.validate_name(name, file)
49
+ unless name.is_a?(String) && name.match?(Ownership::NAME_PATTERN) && name.length <= 64
50
+ raise InvalidError, "Invalid skill name in #{file}: #{name.inspect}"
51
+ end
52
+ end
53
+
54
+ # Validate the required authored skill fields.
55
+ def self.validate_metadata(frontmatter, file)
56
+ validate_name(frontmatter["name"], file)
57
+
58
+ description = frontmatter["description"]
59
+ unless description.is_a?(String)
60
+ raise InvalidError, "Invalid skill description in #{file}"
61
+ end
62
+
63
+ description = description.strip
64
+ if description.empty? || description.length > 1024
65
+ raise InvalidError, "Invalid skill description in #{file}"
66
+ end
67
+ end
68
+
69
+ # Retain the source document, specification, and prepared skill instructions.
70
+ def initialize(document, specification, name, description, resources, installation_content)
71
+ super(File.basename(document.path), document.path, document)
72
+ @specification = specification
73
+ @name = name
74
+ @description = description
75
+ @resources = resources
76
+ @installation_content = installation_content
77
+ end
78
+
79
+ # @attribute [Gem::Specification] The resolved package specification.
80
+ attr_reader :specification
81
+
82
+ # @attribute [String] The installed skill name.
83
+ attr_reader :name
84
+
85
+ # @attribute [String] When to use the skill.
86
+ attr_reader :description
87
+
88
+ # @attribute [String | Nil] The companion resource directory.
89
+ attr_reader :resources
90
+
91
+ # @attribute [String] The generated instructions with resolved provenance.
92
+ attr_reader :installation_content
93
+
94
+ # Whether this definition installs as an Agent Skill.
95
+ # @returns [bool] `true` for skill definitions.
96
+ def skill?
97
+ true
98
+ end
99
+
100
+ # Return the filename shown by the context list with its skill marker.
101
+ # @returns [String] The source filename and skill marker.
102
+ def display_name
103
+ "#{File.basename(@path, File.extname(@path))} (skill)"
104
+ end
105
+
106
+ # Write instructions before resources so ownership is available during refresh.
107
+ # @parameter destination [String] The installed skill directory.
108
+ def install(destination)
109
+ FileUtils.mkdir_p(destination)
110
+ begin
111
+ File.write(File.join(destination, "SKILL.md"), @installation_content)
112
+ rescue
113
+ FileUtils.rm_rf(destination)
114
+ raise
115
+ end
116
+ copy_resources(@resources, destination, true) if @resources
117
+ end
118
+
119
+ private
120
+
121
+ def copy_resources(source, destination, top_level)
122
+ Dir.children(source).sort.each do |name|
123
+ from = File.join(source, name)
124
+ to = File.join(destination, name)
125
+ metadata = File.lstat(from)
126
+
127
+ if metadata.symlink? || (!metadata.file? && !metadata.directory?)
128
+ raise InvalidError, "Skill resources must be regular files or directories: #{from}"
129
+ end
130
+
131
+ if top_level && name.downcase == "skill.md"
132
+ raise InvalidError, "#{name} is reserved for generated skill files: #{from}"
133
+ end
134
+
135
+ if metadata.directory?
136
+ FileUtils.mkdir_p(to)
137
+ copy_resources(from, to, false)
138
+ else
139
+ FileUtils.copy_file(from, to, true)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Shopify Inc.
5
+
6
+ require_relative "../document"
7
+ require_relative "../errors"
8
+
9
+ module Agent
10
+ module Context
11
+ module Skills
12
+ # Represents installation ownership recorded in each skill directory.
13
+ class Ownership
14
+ FILE_NAME = "SKILL.md"
15
+ NAME_PATTERN = /\A[a-z0-9]+(?:-[a-z0-9]+)*\z/
16
+
17
+ # Read a skill's installation ownership.
18
+ # @parameter directory [String] The installed skill directory.
19
+ # @returns [Hash | Nil] Ownership when the skill has an installation record.
20
+ def self.load(directory)
21
+ path = File.join(directory, FILE_NAME)
22
+
23
+ begin
24
+ stat = File.lstat(path)
25
+ rescue Errno::ENOENT
26
+ return
27
+ end
28
+
29
+ raise InvalidError, "Skill ownership must be a regular file: #{path}" unless stat.file?
30
+
31
+ metadata = Document.load(path).metadata
32
+ fields = ["ecosystem", "package", "version"]
33
+ return unless metadata["type"] == "skill" && metadata.key?("ecosystem")
34
+
35
+ unless fields.all?{|field| metadata[field].is_a?(String) && !metadata[field].strip.empty?}
36
+ raise InvalidError, "Invalid skill ownership: #{path}"
37
+ end
38
+
39
+ metadata.slice(*fields)
40
+ end
41
+
42
+ # Collect ownership from installed skill directories.
43
+ # @parameter directory [String] The installed skills root.
44
+ # @returns [Hash(String, Hash)] Skill names and their installation records.
45
+ def self.scan(directory)
46
+ begin
47
+ stat = File.lstat(directory)
48
+ rescue Errno::ENOENT
49
+ return {}
50
+ end
51
+
52
+ raise InvalidError, "Skill installation path must be a regular directory: #{directory}" unless stat.directory?
53
+
54
+ Dir.children(directory).sort.each_with_object({}) do |name, owners|
55
+ path = File.join(directory, name)
56
+ next unless File.lstat(path).directory?
57
+ next unless owner = load(path)
58
+
59
+ unless name.match?(NAME_PATTERN) && name.length <= 64
60
+ raise InvalidError, "Invalid installed skill name: #{name.inspect}"
61
+ end
62
+
63
+ owners[name] = owner
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Shopify Inc.
5
+
6
+ require_relative "skills/definition"
7
+ require_relative "skills/ownership"
8
+
9
+ module Agent
10
+ module Context
11
+ # Skill definitions and installation ownership for dependency context.
12
+ module Skills
13
+ end
14
+ end
15
+ end
@@ -6,6 +6,6 @@
6
6
 
7
7
  module Agent
8
8
  module Context
9
- VERSION = "0.3.0"
9
+ VERSION = "0.4.0"
10
10
  end
11
11
  end
data/lib/agent/context.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Shopify Inc.
4
+ # Copyright, 2025-2026, by Shopify Inc.
5
5
  # Copyright, 2025, by Samuel Williams.
6
6
 
7
7
  require_relative "context/version"
8
+ require_relative "context/paths"
9
+ require_relative "context/skills"
8
10
  require_relative "context/installer"
9
- require_relative "context/index"
10
11
 
11
12
  # @namespace
12
13
  module Agent
data/license.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2025, by Shopify Inc.
4
- Copyright, 2025, by Samuel Williams.
3
+ Copyright, 2025-2026, by Shopify Inc.
4
+ Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
data/post.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Ruby and Claude's Great Adventure: How We Made Gems AI-Friendly
2
2
 
3
+ This article describes the original release. See the [current usage guide](guides/getting-started/readme.md) for dependency context and skill installation.
4
+
3
5
  *Once upon a time, in the bustling world of software development, Ruby and Claude were having a conversation that would change everything...*
4
6
 
5
7
  ## The Problem: A Tale of Miscommunication
@@ -28,7 +30,7 @@
28
30
 
29
31
  **Claude:** "So how does this work in practice?"
30
32
 
31
- **Ruby:** "Let me show you! When a developer runs `bake agent:context:install`, it scans all my installed gems for `context/` directories, copies the files to a `.context/` folder in their project, and generates an `agents.md` file that gives you a comprehensive overview."
33
+ **Ruby:** "Let me show you! When a developer runs `bake agent:context:install`, it scans all my installed gems for `context/` directories, copies the files to a `.agents/context/` folder in their project, and generates an `agents.md` file that gives you a comprehensive overview."
32
34
 
33
35
  **Claude:** "That sounds perfect! What does this `agents.md` file look like?"
34
36
 
@@ -45,7 +47,7 @@ Context files from installed gems providing documentation and guidance for AI ag
45
47
 
46
48
  Code analysis for documentation generation.
47
49
 
48
- #### [Getting Started with Decode](.context/decode/getting-started.md)
50
+ #### [Getting Started with Decode](.agents/context/decode/getting-started.md)
49
51
 
50
52
  The Decode gem provides programmatic access to Ruby code structure...
51
53
 
@@ -53,7 +55,7 @@ The Decode gem provides programmatic access to Ruby code structure...
53
55
 
54
56
  A fast and scalable test runner.
55
57
 
56
- #### [Using Sus Testing Framework](.context/sus/usage.md)
58
+ #### [Using Sus Testing Framework](.agents/context/sus/usage.md)
57
59
 
58
60
  Sus is a modern Ruby testing framework...
59
61
  ```
data/readme.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Agent::Context
2
2
 
3
- Provides tools for installing and managing context files from Ruby gems for AI agents, and generating `agents.md` files following the <https://agents.md> specification.
3
+ Provides tools for installing and managing context files and skills from Ruby gems for AI agents.
4
4
 
5
- [![Development Status](https://github.com/ioquatix/agent-context/workflows/Test/badge.svg)](https://github.com/ioquatix/agent-context/actions?workflow=Test)
5
+ [![Development Status](https://github.com/socketry/agent-context/workflows/Test/badge.svg)](https://github.com/socketry/agent-context/actions?workflow=Test)
6
6
 
7
7
  ## Overview
8
8
 
9
9
  This gem allows you to install and manage context files from other gems. Gems can provide context files in a `context/` directory in their root, which can contain documentation, configuration examples, migration guides, and other contextual information for AI agents.
10
10
 
11
- When you install context from gems, they are placed in the `.context/` directory and an `agents.md` file is generated or updated to provide a comprehensive overview for AI agents.
11
+ When you install context from gems, ordinary guides are placed in `.agents/context/` and dependency skills in `.agents/skills/`. Browse `.agents/context/GEM/` or use the list/show commands to find relevant guides.
12
12
 
13
13
  ## Quick Start
14
14
 
@@ -22,23 +22,23 @@ $ bake agent:context:install
22
22
  This workflow:
23
23
 
24
24
  - Adds the `agent-context` gem to your project.
25
- - Installs context files from all gems into `.context/`.
26
- - Generates or updates `agents.md` with a comprehensive overview.
27
- - Follows the <https://agents.md> specification for agentic coding tools.
25
+ - Installs context files from all gems into `.agents/context/`.
26
+ - Installs metadata-declared skills into `.agents/skills/`.
28
27
 
29
28
  ## Context
30
29
 
31
30
  This gem provides its own context files in the `context/` directory, including:
32
31
 
33
- - `usage.md` - Comprehensive guide for using and providing context files.
32
+ - `getting-started.md` - Comprehensive guide for using and providing context files and skills.
33
+ - [`usage.md`](context/usage.md) - Agent instructions for finding dependency guidance and managing context and skills, installed as the `agent-context-usage` skill.
34
34
 
35
- When you install context from other gems, they will be placed in the `.context/` directory and referenced in `agents.md`.
35
+ When you install context from other gems, they will be placed in `.agents/context/`.
36
36
 
37
37
  ## Usage
38
38
 
39
39
  Please see the [project documentation](https://ioquatix.github.io/agent-context/) for more details.
40
40
 
41
- - [Getting Started](https://ioquatix.github.io/agent-context/guides/getting-started/index) - This guide explains how to use `agent-context`, a tool for discovering and installing contextual information from Ruby gems to help AI agents.
41
+ - [Getting Started](https://ioquatix.github.io/agent-context/guides/getting-started/index) - This guide explains how to install package guidance and skills using `agent-context`.
42
42
 
43
43
  ### Installation
44
44
 
@@ -52,7 +52,7 @@ $ bundle add agent-context
52
52
 
53
53
  #### Install Context (Primary Command)
54
54
 
55
- Install context from all available gems and update `agents.md`:
55
+ Install ordinary context and skills from all available gems:
56
56
 
57
57
  ``` bash
58
58
  $ bake agent:context:install
@@ -86,13 +86,13 @@ Show the content of a specific context file:
86
86
  $ bake agent:context:show --gem async --file thread-safety
87
87
  ```
88
88
 
89
+ The context list marks skill documents with `(skill)`, and the show and install tasks handle them alongside ordinary context. `agent-context` includes skill discovery and installation directly. Use it for Ruby dependency skills; the standalone `agent-skills` gem is deprecated.
90
+
89
91
  ## Version Control
90
92
 
91
- Both `.context/` and `agents.md` should be committed to git:
93
+ In your repository instructions, direct agents to read relevant Markdown guides under `.agents/context/GEM/` and apply matching skills from `.agents/skills/`. Run installation after changing dependencies.
92
94
 
93
- - `agents.md` is user-facing documentation that should be versioned.
94
- - `.context/` files are referenced by `agents.md` and needed for AI agents to function properly.
95
- - This ensures AI agents in CI have access to the full context.
95
+ Installation maintains a marked block in the local Git exclude file, discovered through `git rev-parse --git-path info/exclude`. It excludes generated context, ownership files, and exact dependency-installed skill directories. Track project-owned instructions and skills under `.agents/`. Remove any blanket `/.agents/` rule from your project's `.gitignore` when adopting this layout. Re-run installation in each checkout.
96
96
 
97
97
  ## Providing Context in Your Gem
98
98
 
@@ -102,33 +102,52 @@ To provide context files in your gem, create a `context/` directory in your gem'
102
102
  ├── context/
103
103
  │ ├── getting-started.md
104
104
  │ ├── usage.md
105
- │ ├── configuration.md
106
- │ └── index.yaml (optional)
105
+ │ └── configuration.md
107
106
  ├── lib/
108
107
  └── your-gem.gemspec
109
108
 
110
- ### Optional: Custom Index File
109
+ Use descriptive filenames and focused Markdown guides. Agents find the installed files through directory listing and the list/show commands.
110
+
111
+ ### Providing Skills
112
+
113
+ A root-level context document can declare a skill:
111
114
 
112
- You can provide a custom `index.yaml` file to control ordering and metadata:
115
+ ``` markdown
116
+ ---
117
+ metadata:
118
+ type: skill
119
+ description: Run the provider's workflow when preparing a new project.
120
+ license: MIT
121
+ ---
113
122
 
114
- ``` yaml
115
- description: "Your gem description from gemspec"
116
- version: "1.0.0"
117
- files:
118
- - path: getting-started.md
119
- title: "Getting Started"
120
- description: "Quick start guide"
121
- - path: usage.md
122
- title: "Usage Guide"
123
- description: "Detailed usage instructions"
123
+ # Workflow
124
+
125
+ Follow these instructions.
124
126
  ```
125
127
 
126
- If no `index.yaml` is provided, one will be generated automatically from your gemspec and markdown files.
128
+ `context/workflow.md` from gem `provider` defaults to `.agents/skills/provider-workflow/SKILL.md`. Resources in `context/workflow/` are copied alongside the instructions. Installed names have a 64-character limit; descriptions have a 1,024-character limit. The generated instructions include additional skill metadata.
129
+
130
+ Skills are discovered through `metadata.type: skill` metadata in `context/*.md`, matching `bake-agent-context-rust`. Include `context/**/*` in your gem's file list. Authored skill names are preserved; missing names default to `<package>-<filename>`.
131
+
132
+ ## Ownership and Updates
133
+
134
+ 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.
135
+
136
+ See [the portable specification](specification.md) and [Getting Started](guides/getting-started/readme.md).
127
137
 
128
138
  ## Releases
129
139
 
130
140
  Please see the [project releases](https://ioquatix.github.io/agent-context/releases/index) for all releases.
131
141
 
142
+ ### v0.4.0
143
+
144
+ - Resolve context source filenames before installed skill names and show authored content consistently.
145
+ - Install unfamiliar context types as ordinary guidance so providers can introduce new types.
146
+ - Store resolved skill provenance in `SKILL.md` front matter under `metadata`, with `metadata.type: skill` declaring skills.
147
+ - Preserve authored names, descriptions, additional metadata, and the original Markdown body using native front matter AST nodes.
148
+ - Share loaded documents between context and skill installation, and use direct writes with rerunnable progress.
149
+ - Discover dependency guidance through installed Markdown files and the list/show commands. Remove generated indexes and the `agent:context:index` task.
150
+
132
151
  ### v0.3.0
133
152
 
134
153
  - Rename `agent.md` -\> `agents.md`.
@@ -152,11 +171,31 @@ Please see the [project releases](https://ioquatix.github.io/agent-context/relea
152
171
 
153
172
  We welcome contributions to this project.
154
173
 
155
- 1. Fork it.
174
+ Read the [architecture design](design.md) for the current Ruby and Rust boundaries and the proposed alignment work.
175
+
176
+ 1. Fork the repository.
156
177
  2. Create your feature branch (`git checkout -b my-new-feature`).
157
- 3. Commit your changes (`git commit -am 'Add some feature'`).
178
+ 3. Commit your changes (`git commit -am 'Add some feature.'`).
158
179
  4. Push to the branch (`git push origin my-new-feature`).
159
- 5. Create new Pull Request.
180
+ 5. Create a new pull request.
181
+
182
+ ### Running Tests
183
+
184
+ To run the test suite:
185
+
186
+ ``` bash
187
+ $ bundle exec sus
188
+ ```
189
+
190
+ ### Making Releases
191
+
192
+ To prepare a release branch and open a pull request from an up-to-date `main`:
193
+
194
+ ``` bash
195
+ $ bundle exec bake gem:github:release:patch # or minor or major
196
+ ```
197
+
198
+ See [bake-gem-github](https://github.com/socketry/bake-gem-github) for setup, remote releases, and recovery.
160
199
 
161
200
  ### Developer Certificate of Origin
162
201
 
data/releases.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Releases
2
2
 
3
+ ## v0.4.0
4
+
5
+ - Resolve context source filenames before installed skill names and show authored content consistently.
6
+ - Install unfamiliar context types as ordinary guidance so providers can introduce new types.
7
+ - Store resolved skill provenance in `SKILL.md` front matter under `metadata`, with `metadata.type: skill` declaring skills.
8
+ - Preserve authored names, descriptions, additional metadata, and the original Markdown body using native front matter AST nodes.
9
+ - Share loaded documents between context and skill installation, and use direct writes with rerunnable progress.
10
+ - Discover dependency guidance through installed Markdown files and the list/show commands. Remove generated indexes and the `agent:context:index` task.
11
+
3
12
  ## v0.3.0
4
13
 
5
14
  - Rename `agent.md` -\> `agents.md`.