agent-context 0.0.2 → 0.1.1

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,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Shopify Inc.
5
+ # Copyright, 2025, by Samuel Williams.
6
+
7
+ require "rubygems"
8
+ require "fileutils"
9
+ require "pathname"
10
+ require "yaml"
11
+
12
+ module Agent
13
+ module Context
14
+ # Installer class for managing context files from Ruby gems.
15
+ #
16
+ # This class provides methods to find, list, show, and install context files
17
+ # from gems that provide them in a `context/` directory.
18
+ class Installer
19
+ CANONICAL_ORDER = [
20
+ "getting-started",
21
+ "overview",
22
+ "usage",
23
+ "configuration",
24
+ "migration",
25
+ "troubleshooting",
26
+ "debugging"
27
+ ]
28
+
29
+ # Initialize a new Installer instance.
30
+ #
31
+ # @parameter root [String] The root directory to work from (default: current directory).
32
+ # @parameter specifications [Gem::Specification] The gem specifications to search (default: all installed gems).
33
+ def initialize(root: Dir.pwd, specifications: ::Gem::Specification)
34
+ @root = root
35
+ @context_path = ".context"
36
+ @specifications = specifications
37
+ end
38
+
39
+ attr_reader :context_path
40
+
41
+ # Find all gems that have a context directory
42
+ def find_gems_with_context(skip_local: true)
43
+ gems_with_context = []
44
+
45
+ @specifications.each do |spec|
46
+ # Skip gems loaded from current working directory if requested:
47
+ next if skip_local && spec.full_gem_path == @root
48
+
49
+ context_path = File.join(spec.full_gem_path, "context")
50
+ if Dir.exist?(context_path)
51
+ gems_with_context << {
52
+ name: spec.name,
53
+ version: spec.version.to_s,
54
+ summary: spec.summary,
55
+ metadata: spec.metadata,
56
+ path: context_path
57
+ }
58
+ end
59
+ end
60
+
61
+ gems_with_context
62
+ end
63
+
64
+ # Find a specific gem with context.
65
+ def find_gem_with_context(gem_name)
66
+ spec = @specifications.find {|spec| spec.name == gem_name}
67
+ return nil unless spec
68
+
69
+ context_path = File.join(spec.full_gem_path, "context")
70
+
71
+ if Dir.exist?(context_path)
72
+ {
73
+ name: spec.name,
74
+ version: spec.version.to_s,
75
+ summary: spec.summary,
76
+ metadata: spec.metadata,
77
+ path: context_path
78
+ }
79
+ else
80
+ nil
81
+ end
82
+ end
83
+
84
+ # List context files for a gem.
85
+ def list_context_files(gem_name)
86
+ gem = find_gem_with_context(gem_name)
87
+ return nil unless gem
88
+
89
+ Dir.glob(File.join(gem[:path], "**/*")).select {|f| File.file?(f)}
90
+ end
91
+
92
+ # Show content of a specific context file.
93
+ def show_context_file(gem_name, file_name)
94
+ gem = find_gem_with_context(gem_name)
95
+ return nil unless gem
96
+
97
+ # Try to find the file with or without extension:
98
+ possible_paths = [
99
+ File.join(gem[:path], file_name),
100
+ File.join(gem[:path], "#{file_name}.md"),
101
+ File.join(gem[:path], "#{file_name}.md")
102
+ ]
103
+
104
+ file_path = possible_paths.find {|path| File.exist?(path)}
105
+ return nil unless file_path
106
+
107
+ File.read(file_path)
108
+ end
109
+
110
+ # Install context from a specific gem.
111
+ def install_gem_context(gem_name)
112
+ gem = find_gem_with_context(gem_name)
113
+ return false unless gem
114
+
115
+ target_path = File.join(@context_path, gem_name)
116
+
117
+ # Remove old package directory if it exists to ensure clean install
118
+ FileUtils.rm_rf(target_path) if Dir.exist?(target_path)
119
+
120
+ FileUtils.mkdir_p(target_path)
121
+
122
+ # Copy all files from the gem's context directory:
123
+ FileUtils.cp_r(File.join(gem[:path], "."), target_path)
124
+
125
+ # Generate index.yaml if it doesn't exist, passing the full gem hash
126
+ ensure_gem_index(gem, target_path)
127
+
128
+ true
129
+ end
130
+
131
+ # Install context from all gems.
132
+ def install_all_context(skip_local: true)
133
+ gems = find_gems_with_context(skip_local: skip_local)
134
+ installed = []
135
+
136
+ gems.each do |gem|
137
+ if install_gem_context(gem[:name])
138
+ installed << gem[:name]
139
+ end
140
+ end
141
+
142
+ installed
143
+ end
144
+
145
+ private
146
+
147
+ # Generate a dynamic index from gemspec when no index.yaml is present
148
+ def generate_dynamic_index(gem, gem_directory)
149
+ # Collect all markdown files
150
+ markdown_files = Dir.glob(File.join(gem_directory, "**", "*.md")).sort
151
+
152
+ # Sort files: canonical first, then alpha
153
+ files_sorted = markdown_files.sort_by do |file_path|
154
+ base_filename = File.basename(file_path, ".md").downcase
155
+ canonical_index = CANONICAL_ORDER.index(base_filename)
156
+ [canonical_index ? CANONICAL_ORDER.index(base_filename) : CANONICAL_ORDER.length, base_filename]
157
+ end
158
+
159
+ files = []
160
+ files_sorted.each do |file_path|
161
+ next if File.basename(file_path) == "index.yaml" # Skip the index file itself
162
+ title, description = extract_content(file_path)
163
+ relative_path = file_path.sub("#{gem_directory}/", "")
164
+ files << {
165
+ "path" => relative_path,
166
+ "title" => title,
167
+ "description" => description
168
+ }
169
+ end
170
+
171
+ {
172
+ "description" => gem[:summary] || "Context files for #{gem[:name]}",
173
+ "version" => gem[:version],
174
+ "metadata" => gem[:metadata],
175
+ "files" => files
176
+ }
177
+ end
178
+
179
+ # Check if a gem has an index.yaml file, generate one if not
180
+ def ensure_gem_index(gem, gem_directory)
181
+ index_path = File.join(gem_directory, "index.yaml")
182
+
183
+ unless File.exist?(index_path)
184
+ # Generate dynamic index from gemspec
185
+ index = generate_dynamic_index(gem, gem_directory)
186
+
187
+ # Write the generated index
188
+ File.write(index_path, index.to_yaml)
189
+ Console.debug("Generated dynamic index for #{gem[:name]}: #{index_path}")
190
+ end
191
+
192
+ # Load and return the index
193
+ YAML.load_file(index_path)
194
+ rescue => error
195
+ Console.debug("Error generating index for #{gem[:name]}: #{error.message}")
196
+ # Return a fallback index
197
+ {
198
+ "description" => gem[:summary] || "Context files for #{gem[:name]}",
199
+ "version" => gem[:version],
200
+ "metadata" => gem[:metadata],
201
+ "files" => []
202
+ }
203
+ end
204
+
205
+ def extract_content(file_path)
206
+ content = File.read(file_path)
207
+ lines = content.lines.map(&:strip)
208
+
209
+ title = extract_title(lines)
210
+ description = extract_description(lines)
211
+
212
+ [title, description]
213
+ end
214
+
215
+ def extract_title(lines)
216
+ # Look for the first markdown header
217
+ header_line = lines.find {|line| line.start_with?("#")}
218
+ if header_line
219
+ # Remove markdown header syntax and clean up
220
+ header_line.sub(/^#+\s*/, "").strip
221
+ else
222
+ # If no header found, use a default
223
+ "Documentation"
224
+ end
225
+ end
226
+
227
+ def extract_description(lines)
228
+ # Skip empty lines and headers to find the first paragraph
229
+ content_start = false
230
+ description_lines = []
231
+
232
+ lines.each do |line|
233
+ # Skip headers
234
+ next if line.start_with?("#")
235
+
236
+ # Skip empty lines until we find content
237
+ if !content_start && line.empty?
238
+ next
239
+ end
240
+
241
+ # Mark that we've found content
242
+ content_start = true
243
+
244
+ # If we hit an empty line after finding content, we've reached the end of the first paragraph
245
+ if line.empty?
246
+ break
247
+ end
248
+
249
+ description_lines << line
250
+ end
251
+
252
+ # Join the lines and truncate if too long
253
+ description = description_lines.join(" ").strip
254
+ if description.length > 197
255
+ description = description[0..196] + "..."
256
+ end
257
+
258
+ description
259
+ end
260
+
261
+ def extract_gem_description(gem_name)
262
+ # Find the gem specification using Ruby's Gem API
263
+ spec = Gem::Specification.find_by_name(gem_name)
264
+ spec&.summary
265
+ rescue Gem::MissingSpecError
266
+ nil
267
+ end
268
+ end
269
+ end
270
+ end
@@ -6,6 +6,6 @@
6
6
 
7
7
  module Agent
8
8
  module Context
9
- VERSION = "0.0.2"
9
+ VERSION = "0.1.1"
10
10
  end
11
11
  end
data/lib/agent/context.rb CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025, by Shopify Inc.
5
+ # Copyright, 2025, by Samuel Williams.
5
6
 
6
7
  require_relative "context/version"
7
- require_relative "context/helper"
8
+ require_relative "context/installer"
9
+ require_relative "context/index"
10
+
11
+ # @namespace
12
+ module Agent
13
+ # @namespace
14
+ module Context
15
+ end
16
+ end
data/post.md ADDED
@@ -0,0 +1,149 @@
1
+ # Ruby and Claude's Great Adventure: How We Made Gems AI-Friendly
2
+
3
+ *Once upon a time, in the bustling world of software development, Ruby and Claude were having a conversation that would change everything...*
4
+
5
+ ## The Problem: A Tale of Miscommunication
6
+
7
+ **Ruby:** "Claude, I've been thinking... you're really good at helping developers write code, but sometimes I feel like you're not getting the full picture of what my gems can do."
8
+
9
+ **Claude:** "You're absolutely right, Ruby! I can see your method signatures and class definitions, but I'm missing the context. Like, when someone asks me to help with authentication, I know about your `User` class and `authenticate` method, but I don't know the best practices, common pitfalls, or how to configure things properly."
10
+
11
+ **Ruby:** "Exactly! My gems have so much wisdom to share - migration guides, performance tips, security considerations, real-world examples. But it's all scattered in READMEs, wikis, and blog posts that you can't easily access."
12
+
13
+ **Claude:** "And when I try to help developers, I end up giving generic advice instead of leveraging the specific expertise that your gem authors have already documented. It's frustrating for both of us!"
14
+
15
+ ## The Discovery: A Lightbulb Moment
16
+
17
+ **Ruby:** "What if we created a way for my gems to share their knowledge with you in a format you can actually use?"
18
+
19
+ **Claude:** "That sounds amazing! But how would we do it? I need structured, accessible information that I can understand and reference quickly."
20
+
21
+ **Ruby:** "Well, I was thinking... what if gems could have a special `context/` directory with guides specifically written for AI agents like you? Not just API docs, but practical wisdom about how to use them effectively."
22
+
23
+ **Claude:** "Like 'getting-started' guides, configuration examples, troubleshooting tips, and best practices?"
24
+
25
+ **Ruby:** "Exactly! And then we could have a tool that collects all this context from installed gems and presents it to you in a way that makes sense."
26
+
27
+ ## The Solution: `Agent::Context` is Born
28
+
29
+ **Claude:** "So how does this work in practice?"
30
+
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 `agent.md` file that gives you a comprehensive overview."
32
+
33
+ **Claude:** "That sounds perfect! What does this `agent.md` file look like?"
34
+
35
+ **Ruby:** "It's structured and organized, following the AGENT.md specification. Here's what it generates:"
36
+
37
+ ```markdown
38
+ # Agent
39
+
40
+ ## Context
41
+
42
+ Context files from installed gems providing documentation and guidance for AI agents.
43
+
44
+ ### decode
45
+
46
+ Code analysis for documentation generation.
47
+
48
+ #### [Getting Started with Decode](.context/decode/getting-started.md)
49
+
50
+ The Decode gem provides programmatic access to Ruby code structure...
51
+
52
+ ### sus
53
+
54
+ A fast and scalable test runner.
55
+
56
+ #### [Using Sus Testing Framework](.context/sus/usage.md)
57
+
58
+ Sus is a modern Ruby testing framework...
59
+ ```
60
+
61
+ **Claude:** "Wow! This is exactly what I need. I can see what gems are available, understand their purpose, and access detailed guides when I need them."
62
+
63
+ ## The Implementation: How Gems Share Their Wisdom
64
+
65
+ **Ruby:** "And here's the beautiful part - gem authors just need to create a `context/` directory with helpful guides:"
66
+
67
+ ```
68
+ my-awesome-gem/
69
+ ├── context/
70
+ │ ├── getting-started.md
71
+ │ ├── configuration.md
72
+ │ ├── troubleshooting.md
73
+ │ └── index.yaml (optional)
74
+ ├── lib/
75
+ └── my-awesome-gem.gemspec
76
+ ```
77
+
78
+ **Claude:** "That's so simple! And what's this `index.yaml` file?"
79
+
80
+ **Ruby:** "It's optional, but it lets gem authors control the ordering and metadata. If they don't provide one, we generate it automatically from their gemspec and markdown files."
81
+
82
+ **Claude:** "So it's really easy for gem authors to participate?"
83
+
84
+ **Ruby:** "Absolutely! They just focus on writing helpful guides for AI agents like you, and the tool handles all the technical details."
85
+
86
+ ## The Integration: Making It Work Everywhere
87
+
88
+ **Claude:** "This is great, but how do I actually access this information? Different AI tools expect different file names and locations."
89
+
90
+ **Ruby:** "Good question! The generated `agent.md` can be linked to whatever your tool expects:"
91
+
92
+ **For Cursor:**
93
+ ```bash
94
+ ln -s agent.md .cursorrules
95
+ ```
96
+
97
+ **For GitHub Copilot:**
98
+ ```bash
99
+ ln -s ../../agent.md .github/copilot-instructions.md
100
+ ```
101
+
102
+ **For Claude Code:**
103
+ ```bash
104
+ ln -s agent.md CLAUDE.md
105
+ ```
106
+
107
+ **Claude:** "Perfect! So developers can easily integrate this with their preferred AI tools."
108
+
109
+ ## The Impact: A Better Development Experience
110
+
111
+ **Ruby:** "Since we've been using this approach, the results have been amazing."
112
+
113
+ **Claude:** "Tell me more!"
114
+
115
+ **Ruby:** "Well, developers are getting much better help from AI assistants because you now have access to the collective wisdom of the entire gem ecosystem. You can give specific, contextual advice instead of generic suggestions."
116
+
117
+ **Claude:** "And I can reference real examples and best practices from the actual gem authors!"
118
+
119
+ **Ruby:** "Exactly! Plus, new team members can onboard faster because AI assistants have all the context they need about the project's dependencies."
120
+
121
+ **Claude:** "This is really changing how we work together. I feel like I'm finally getting the full picture of what your gems can do."
122
+
123
+ ## The Future: Building Something Special
124
+
125
+ **Ruby:** "This is just the beginning, Claude. We're creating a more intelligent development ecosystem where human developers and AI agents can collaborate effectively."
126
+
127
+ **Claude:** "It feels like we're bridging a gap that's been there for a while. Gems have always been powerful, but now their knowledge is accessible to AI agents in a meaningful way."
128
+
129
+ **Ruby:** "And the best part is that it's growing organically. As more gems add context files, the entire ecosystem becomes more AI-friendly."
130
+
131
+ **Claude:** "So what's next?"
132
+
133
+ **Ruby:** "We're encouraging teams to install `agent-context` in their projects and start adding context to their gems. Every gem that participates makes the entire Ruby ecosystem smarter for AI agents."
134
+
135
+ **Claude:** "Count me in! This is exactly the kind of collaboration I've been looking for."
136
+
137
+ ## The Moral of the Story
138
+
139
+ **Ruby:** "Sometimes the best solutions come from understanding each other's needs and finding ways to bridge the gaps."
140
+
141
+ **Claude:** "And when we work together, we can create something that's greater than the sum of its parts."
142
+
143
+ **Ruby:** "Exactly. This isn't just about making gems AI-friendly - it's about creating a more collaborative, intelligent development experience for everyone."
144
+
145
+ ---
146
+
147
+ *Ready to join Ruby and Claude's adventure? Install `agent-context` in your project and start exploring the AI-friendly future of Ruby development.*
148
+
149
+ *For more information, visit the [agent-context repository](https://github.com/ioquatix/agent-context) or check out the comprehensive usage guide in the gem's context files.*
data/readme.md CHANGED
@@ -1,16 +1,38 @@
1
1
  # Agent::Context
2
2
 
3
- Provides tools for installing and managing context files from Ruby gems for AI agents.
3
+ Provides tools for installing and managing context files from Ruby gems for AI agents, and generating `agent.md` files following the <https://agent.md> specification.
4
4
 
5
5
  [![Development Status](https://github.com/ioquatix/agent-context/workflows/Test/badge.svg)](https://github.com/ioquatix/agent-context/actions?workflow=Test)
6
6
 
7
7
  ## Overview
8
8
 
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.
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
+
11
+ When you install context from gems, they are placed in the `.context/` directory and an `agent.md` file is generated or updated to provide a comprehensive overview for AI agents.
12
+
13
+ ## Quick Start
14
+
15
+ Add the gem to your project and install context from all available gems:
16
+
17
+ ``` bash
18
+ $ bundle add agent-context
19
+ $ bake agent:context:install
20
+ ```
21
+
22
+ This workflow:
23
+
24
+ - Adds the `agent-context` gem to your project.
25
+ - Installs context files from all gems into `.context/`.
26
+ - Generates or updates `agent.md` with a comprehensive overview.
27
+ - Follows the <https://agent.md> specification for agentic coding tools.
10
28
 
11
29
  ## Context
12
30
 
13
- This gem provides it's own context files in `context` and external dependencies in `.context`:
31
+ This gem provides its own context files in the `context/` directory, including:
32
+
33
+ - `usage.md` - Comprehensive guide for using and providing context files.
34
+
35
+ When you install context from other gems, they will be placed in the `.context/` directory and referenced in `agent.md`.
14
36
 
15
37
  ## Usage
16
38
 
@@ -24,6 +46,20 @@ $ bundle add agent-context
24
46
 
25
47
  ### Commands
26
48
 
49
+ #### Install Context (Primary Command)
50
+
51
+ Install context from all available gems and update `agent.md`:
52
+
53
+ ``` bash
54
+ $ bake agent:context:install
55
+ ```
56
+
57
+ Install context from a specific gem:
58
+
59
+ ``` bash
60
+ $ bake agent:context:install --gem async
61
+ ```
62
+
27
63
  #### List available context
28
64
 
29
65
  List all gems that have context available:
@@ -46,35 +82,90 @@ Show the content of a specific context file:
46
82
  $ bake agent:context:show --gem async --file thread-safety
47
83
  ```
48
84
 
49
- #### Install context
85
+ ## Version Control
86
+
87
+ Both `.context/` and `agent.md` should be committed to git:
88
+
89
+ - `agent.md` is user-facing documentation that should be versioned.
90
+ - `.context/` files are referenced by `agent.md` and needed for AI agents to function properly.
91
+ - This ensures AI agents in CI have access to the full context.
92
+
93
+ ## Providing Context in Your Gem
94
+
95
+ To provide context files in your gem, create a `context/` directory in your gem's root:
96
+
97
+ your-gem/
98
+ ├── context/
99
+ │ ├── getting-started.md
100
+ │ ├── usage.md
101
+ │ ├── configuration.md
102
+ │ └── index.yaml (optional)
103
+ ├── lib/
104
+ └── your-gem.gemspec
105
+
106
+ ### Optional: Custom Index File
107
+
108
+ You can provide a custom `index.yaml` file to control ordering and metadata:
109
+
110
+ ``` yaml
111
+ description: "Your gem description from gemspec"
112
+ version: "1.0.0"
113
+ files:
114
+ - path: getting-started.md
115
+ title: "Getting Started"
116
+ description: "Quick start guide"
117
+ - path: usage.md
118
+ title: "Usage Guide"
119
+ description: "Detailed usage instructions"
120
+ ```
121
+
122
+ If no `index.yaml` is provided, one will be generated automatically from your gemspec and markdown files.
123
+
124
+ ## AI Tool Integration
125
+
126
+ The generated `agent.md` file can be integrated with various AI coding tools by creating symbolic links to their expected locations:
50
127
 
51
- Install context from all available gems:
128
+ ### Cline
52
129
 
53
130
  ``` bash
54
- $ bake agent:context:install
131
+ ln -s agent.md .clinerules
55
132
  ```
56
133
 
57
- Install context from a specific gem:
134
+ ### Claude Code
58
135
 
59
136
  ``` bash
60
- $ bake agent:context:install --gem async
137
+ ln -s agent.md CLAUDE.md
61
138
  ```
62
139
 
63
- This will create a `.context/` directory in your project with the installed context files organized by gem name.
140
+ ### Cursor
64
141
 
65
- ## Providing Context in Your Gem
142
+ ``` bash
143
+ ln -s agent.md .cursorrules
144
+ ```
66
145
 
67
- To provide context files in your gem, create a `context/` directory in your gem's root:
146
+ ### Gemini CLI, OpenAI Codex, OpenCode
68
147
 
69
- your-gem/
70
- ├── context/
71
- │ ├── thread-safety.md
72
- │ ├── performance.md
73
- │ └── migration-guide.md
74
- ├── lib/
75
- └── your-gem.gemspec
148
+ ``` bash
149
+ ln -s agent.md AGENTS.md
150
+ ```
151
+
152
+ ### GitHub Copilot
153
+
154
+ ``` bash
155
+ ln -s ../../agent.md .github/copilot-instructions.md
156
+ ```
157
+
158
+ ### Replit
159
+
160
+ ``` bash
161
+ ln -s agent.md .replit.md
162
+ ```
163
+
164
+ ### Windsurf
76
165
 
77
- Context files can be in any format, but `.md` and `.md` files are commonly used for documentation.
166
+ ``` bash
167
+ ln -s agent.md .windsurfrules
168
+ ```
78
169
 
79
170
  ## See Also
80
171