From 363311e2cecb12bb70db0e36768d1359ee4675fb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 2 Sep 2026 15:30:15 +1200 Subject: [PATCH 1/2] Install context under .agents Assisted-By: devx/445006c1-c85f-4c03-962d-be8ad40fb517 --- .gitignore | 1 + context/getting-started.md | 18 +++++++++--------- guides/getting-started/readme.md | 18 +++++++++--------- lib/agent/context.rb | 1 + lib/agent/context/index.rb | 17 ++++++++++++----- lib/agent/context/installer.rb | 9 ++++++--- lib/agent/context/paths.rb | 13 +++++++++++++ post.md | 6 +++--- readme.md | 10 +++++----- specification.md | 16 ++++++++-------- test/agent/context/index.rb | 10 +++++----- test/agent/context/installer.rb | 20 +++++++++++--------- 12 files changed, 83 insertions(+), 56 deletions(-) create mode 100644 lib/agent/context/paths.rb diff --git a/.gitignore b/.gitignore index ee82a6f..0f4ea84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /agents.md +/.agents/context /.context /.bundle /pkg diff --git a/context/getting-started.md b/context/getting-started.md index 412cb17..8e1f430 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -39,24 +39,24 @@ bake agent:context:list --gem async bake agent:context:show --gem async --file thread-safety ``` -## Understanding context/ vs .context/ +## Understanding context/ vs .agents/context/ **Important distinction:** - **`context/`** (no dot) = Directory in gems that contains context files to share. -- **`.context/`** (with dot) = Directory in your project where context gets installed. +- **`.agents/context/`** (with dot) = Directory in your project where context gets installed. ### What happens when you install context? When you run `bake agent:context:install`, the tool: 1. Scans all installed gems for `context/` directories (in the gem's root). -2. Creates a `.context/` directory in your current project. +2. Creates a `.agents/context/` directory in your current project. 3. Copies context files organized by gem name. For example: ``` your-project/ -├── .context/ # ← Installed context (with dot) +├── .agents/context/ # ← Installed context (with dot) │ ├── async/ # ← From the 'async' gem's context/ directory │ │ ├── thread-safety.md │ │ └── performance.md @@ -87,9 +87,9 @@ async-gem/ ### Key Points for Users -- Run `bake agent:context:install` to copy context to `.context/` (with dot). -- The `.context/` directory is where installed context lives in your project. -- Don't edit files in `.context/` - they get completely replaced when you reinstall. +- Run `bake agent:context:install` to copy context to `.agents/context/` (with dot). +- The `.agents/context/` directory is where installed context lives in your project. +- Don't edit files in `.agents/context/` - they get completely replaced when you reinstall. ## Providing Context (For Gem Authors) @@ -109,7 +109,7 @@ your-gem/ └── your-gem.gemspec ``` -**Important:** This is different from `.context/` (with dot) which is where context gets installed in user projects. +**Important:** This is different from `.agents/context/` (with dot) which is where context gets installed in user projects. #### 2. Add context files @@ -186,4 +186,4 @@ Before publishing, test your context files: ## Summary - **`context/`** = source (in gems). -- **`.context/`** = destination (in your project). +- **`.agents/context/`** = destination (in your project). diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 412cb17..8e1f430 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -39,24 +39,24 @@ bake agent:context:list --gem async bake agent:context:show --gem async --file thread-safety ``` -## Understanding context/ vs .context/ +## Understanding context/ vs .agents/context/ **Important distinction:** - **`context/`** (no dot) = Directory in gems that contains context files to share. -- **`.context/`** (with dot) = Directory in your project where context gets installed. +- **`.agents/context/`** (with dot) = Directory in your project where context gets installed. ### What happens when you install context? When you run `bake agent:context:install`, the tool: 1. Scans all installed gems for `context/` directories (in the gem's root). -2. Creates a `.context/` directory in your current project. +2. Creates a `.agents/context/` directory in your current project. 3. Copies context files organized by gem name. For example: ``` your-project/ -├── .context/ # ← Installed context (with dot) +├── .agents/context/ # ← Installed context (with dot) │ ├── async/ # ← From the 'async' gem's context/ directory │ │ ├── thread-safety.md │ │ └── performance.md @@ -87,9 +87,9 @@ async-gem/ ### Key Points for Users -- Run `bake agent:context:install` to copy context to `.context/` (with dot). -- The `.context/` directory is where installed context lives in your project. -- Don't edit files in `.context/` - they get completely replaced when you reinstall. +- Run `bake agent:context:install` to copy context to `.agents/context/` (with dot). +- The `.agents/context/` directory is where installed context lives in your project. +- Don't edit files in `.agents/context/` - they get completely replaced when you reinstall. ## Providing Context (For Gem Authors) @@ -109,7 +109,7 @@ your-gem/ └── your-gem.gemspec ``` -**Important:** This is different from `.context/` (with dot) which is where context gets installed in user projects. +**Important:** This is different from `.agents/context/` (with dot) which is where context gets installed in user projects. #### 2. Add context files @@ -186,4 +186,4 @@ Before publishing, test your context files: ## Summary - **`context/`** = source (in gems). -- **`.context/`** = destination (in your project). +- **`.agents/context/`** = destination (in your project). diff --git a/lib/agent/context.rb b/lib/agent/context.rb index 5197429..873070e 100644 --- a/lib/agent/context.rb +++ b/lib/agent/context.rb @@ -5,6 +5,7 @@ # Copyright, 2025, by Samuel Williams. require_relative "context/version" +require_relative "context/paths" require_relative "context/installer" require_relative "context/index" diff --git a/lib/agent/context/index.rb b/lib/agent/context/index.rb index d5ee89f..16ae55f 100644 --- a/lib/agent/context/index.rb +++ b/lib/agent/context/index.rb @@ -5,7 +5,9 @@ # Copyright, 2025, by Shopify Inc. require_relative "version" +require_relative "paths" require "fileutils" +require "pathname" require "yaml" # @namespace @@ -19,12 +21,15 @@ module Context # agents.md files, update the context section, and generate new files when needed. class Index # Initialize a new index instance. - # @parameter context_path [String] The path to the context directory (default: ".context"). - def initialize(context_path = ".context") + # @parameter context_path [String] The directory containing installed context. + # @parameter context_link_path [String] The path used for links from `agents.md`. + def initialize(context_path = CONTEXT_PATH, context_link_path: CONTEXT_PATH) @context_path = context_path + @context_link_path = context_link_path end attr :context_path + attr :context_link_path # Update or create an AGENTS.md file in the project root with context section # This follows the AGENTS.md specification for agentic coding tools @@ -74,7 +79,8 @@ def generate_context_section # Use files from index if available, otherwise fall back to parsing if index["files"] && !index["files"].empty? index["files"].each do |file_info| - sections << "#### [#{file_info['title']}](.context/#{gem_name}/#{file_info['path']})" + link_path = File.join(@context_link_path, gem_name, file_info["path"]) + sections << "#### [#{file_info['title']}](#{link_path})" sections << "" sections << file_info["description"] if file_info["description"] && !file_info["description"].empty? sections << "" @@ -84,8 +90,9 @@ def generate_context_section files.each do |file_path| if File.exist?(file_path) title, description = extract_content(file_path) - relative_path = file_path.sub("#{@context_path}/", "") - sections << "#### [#{title}](.context/#{relative_path})" + relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(@context_path)) + link_path = File.join(@context_link_path, relative_path.to_s) + sections << "#### [#{title}](#{link_path})" sections << "" sections << description if description && !description.empty? sections << "" diff --git a/lib/agent/context/installer.rb b/lib/agent/context/installer.rb index 0091591..f03654a 100644 --- a/lib/agent/context/installer.rb +++ b/lib/agent/context/installer.rb @@ -9,6 +9,8 @@ require "pathname" require "yaml" +require_relative "paths" + module Agent module Context # Installer class for managing context files from Ruby gems. @@ -31,11 +33,12 @@ class Installer # @parameter root [String] The root directory to work from (default: current directory). # @parameter specifications [Gem::Specification] The gem specifications to search (default: all installed gems). def initialize(root: Dir.pwd, specifications: ::Gem::Specification) - @root = root - @context_path = ".context" + @root = File.expand_path(root) + @context_path = File.join(@root, CONTEXT_PATH) @specifications = specifications end + attr_reader :root attr_reader :context_path # Find all gems that have a context directory @@ -44,7 +47,7 @@ def find_gems_with_context(skip_local: true) @specifications.each do |spec| # Skip gems loaded from current working directory if requested: - next if skip_local && spec.full_gem_path == @root + next if skip_local && File.expand_path(spec.full_gem_path) == @root context_path = File.join(spec.full_gem_path, "context") if Dir.exist?(context_path) diff --git a/lib/agent/context/paths.rb b/lib/agent/context/paths.rb new file mode 100644 index 0000000..709cac9 --- /dev/null +++ b/lib/agent/context/paths.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Shopify Inc. +# Copyright, 2025-2026, by Samuel Williams. + +# @namespace +module Agent + # @namespace + module Context + CONTEXT_PATH = File.join(".agents", "context") + end +end diff --git a/post.md b/post.md index 9f77f68..a8c63b9 100644 --- a/post.md +++ b/post.md @@ -28,7 +28,7 @@ **Claude:** "So how does this work in practice?" -**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." +**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." **Claude:** "That sounds perfect! What does this `agents.md` file look like?" @@ -45,7 +45,7 @@ Context files from installed gems providing documentation and guidance for AI ag Code analysis for documentation generation. -#### [Getting Started with Decode](.context/decode/getting-started.md) +#### [Getting Started with Decode](.agents/context/decode/getting-started.md) The Decode gem provides programmatic access to Ruby code structure... @@ -53,7 +53,7 @@ The Decode gem provides programmatic access to Ruby code structure... A fast and scalable test runner. -#### [Using Sus Testing Framework](.context/sus/usage.md) +#### [Using Sus Testing Framework](.agents/context/sus/usage.md) Sus is a modern Ruby testing framework... ``` diff --git a/readme.md b/readme.md index 1cb103e..92835c0 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ Provides tools for installing and managing context files from Ruby gems for AI a 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. -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. +When you install context from gems, they are placed in the `.agents/context/` directory and an `agents.md` file is generated or updated to provide a comprehensive overview for AI agents. ## Quick Start @@ -22,7 +22,7 @@ $ bake agent:context:install This workflow: - Adds the `agent-context` gem to your project. - - Installs context files from all gems into `.context/`. + - Installs context files from all gems into `.agents/context/`. - Generates or updates `agents.md` with a comprehensive overview. - Follows the specification for agentic coding tools. @@ -32,7 +32,7 @@ This gem provides its own context files in the `context/` directory, including: - `usage.md` - Comprehensive guide for using and providing context files. -When you install context from other gems, they will be placed in the `.context/` directory and referenced in `agents.md`. +When you install context from other gems, they will be placed in the `.agents/context/` directory and referenced in `agents.md`. ## Usage @@ -88,10 +88,10 @@ $ bake agent:context:show --gem async --file thread-safety ## Version Control -Both `.context/` and `agents.md` should be committed to git: +Both `.agents/context/` and `agents.md` should be committed to git: - `agents.md` is user-facing documentation that should be versioned. - - `.context/` files are referenced by `agents.md` and needed for AI agents to function properly. + - `.agents/context/` files are referenced by `agents.md` and needed for AI agents to function properly. - This ensures AI agents in CI have access to the full context. ## Providing Context in Your Gem diff --git a/specification.md b/specification.md index e55fc79..826f541 100644 --- a/specification.md +++ b/specification.md @@ -32,7 +32,7 @@ A **Context Provider** is any software package, library, or module that includes A **Context Consumer** is any project or tool that utilizes contextual information from its dependencies. Context consumers: -- Install context from their dependencies into a `.context/` directory. +- Install context from their dependencies into a `.agents/context/` directory. - Use tools to discover and access available context. - Apply context based on file patterns and metadata. @@ -75,9 +75,9 @@ package-root/ └── package.json ``` -### 3.2 Consumer Directory: `.context/` +### 3.2 Consumer Directory: `.agents/context/` -Context consumers SHOULD create a `.context/` directory in their project root to store installed context. This directory: +Context consumers SHOULD create a `.agents/context/` directory in their project root to store installed context. This directory: - **Location**: Must be at the project root (typically where package manifests are located). - **Purpose**: Contains context files copied from dependencies. @@ -88,7 +88,7 @@ Context consumers SHOULD create a `.context/` directory in their project root to Example structure: ``` project-root/ -├── .context/ +├── .agents/context/ │ ├── package-a/ │ │ ├── getting-started.md │ │ └── configuration.md @@ -100,9 +100,9 @@ project-root/ ### 3.3 Directory Separation Rationale -The separation between `context/` and `.context/` serves several purposes: +The separation between `context/` and `.agents/context/` serves several purposes: -- **Ownership**: `context/` is controlled by the project itself, `.context/` contains external dependencies. +- **Ownership**: `context/` is controlled by the project itself, `.agents/context/` contains external dependencies. - **Isolation**: Prevents conflicts between different packages' context files. - **Discoverability**: Makes it easy to find context for specific packages. - **Maintenance**: Allows independent management of provided vs. consumed context. @@ -170,7 +170,7 @@ Context installation MUST follow these principles: ``` FOR each package with context: - CREATE directory .context/package-name/ - COPY all files recursively from package/context/ to .context/package-name/ + CREATE directory .agents/context/package-name/ + COPY all files recursively from package/context/ to .agents/context/package-name/ END ``` diff --git a/test/agent/context/index.rb b/test/agent/context/index.rb index 488bcb3..11d54f3 100644 --- a/test/agent/context/index.rb +++ b/test/agent/context/index.rb @@ -13,7 +13,7 @@ with "AGENT.md functionality" do let(:temporary_directory) {Dir.mktmpdir} - let(:context_path) {File.join(temporary_directory, ".context")} + let(:context_path) {File.join(temporary_directory, ".agents", "context")} let(:agent_md_path) {File.join(temporary_directory, "agent.md")} def around @@ -128,7 +128,7 @@ def around expect(content).to be(:include?, "# Agent") expect(content).to be(:include?, "## Context") expect(content).to be(:include?, "### example_gem") - expect(content).to be(:include?, "#### [Example Gem](.context/example_gem/README.md)") + expect(content).to be(:include?, "#### [Example Gem](.agents/context/example_gem/README.md)") expect(content).to be(:include?, "This is an example gem that provides context for AI agents") end @@ -159,7 +159,7 @@ def around content = File.read(agent_md_path) expect(content).to be(:include?, "### example_gem") expect(content).to be(:include?, "A test gem with custom index") - expect(content).to be(:include?, "#### [Getting Started Guide](.context/example_gem/getting-started.md)") + expect(content).to be(:include?, "#### [Getting Started Guide](.agents/context/example_gem/getting-started.md)") expect(content).to be(:include?, "Custom description from index") end @@ -198,7 +198,7 @@ def around with "title and description extraction" do let(:temporary_directory) {Dir.mktmpdir} - let(:context_path) {File.join(temporary_directory, ".context")} + let(:context_path) {File.join(temporary_directory, ".agents", "context")} def around FileUtils.mkdir_p(context_path) @@ -290,7 +290,7 @@ def around with "index.yaml handling" do let(:temporary_directory) {Dir.mktmpdir} - let(:context_path) {File.join(temporary_directory, ".context")} + let(:context_path) {File.join(temporary_directory, ".agents", "context")} def around FileUtils.mkdir_p(context_path) diff --git a/test/agent/context/installer.rb b/test/agent/context/installer.rb index 46af09e..af74ee2 100644 --- a/test/agent/context/installer.rb +++ b/test/agent/context/installer.rb @@ -102,18 +102,20 @@ def around end it "can install context from a specific gem" do - helper = subject.new(specifications: @specifications) - helper.instance_variable_set(:@context_path, @target_path) + helper = subject.new(root: @target_path, specifications: @specifications) result = helper.install_gem_context("fake-gem") expect(result).to be_truthy + target_context_path = File.join(@target_path, ".agents", "context", "fake-gem") + expect(helper.context_path).to be == File.join(@target_path, ".agents", "context") + # Check that files were copied - expect(File).to be(:exist?, File.join(@target_path, "fake-gem", "getting-started.md")) - expect(File).to be(:exist?, File.join(@target_path, "fake-gem", "configuration.md")) + expect(File).to be(:exist?, File.join(target_context_path, "getting-started.md")) + expect(File).to be(:exist?, File.join(target_context_path, "configuration.md")) # Check content - content = File.read(File.join(@target_path, "fake-gem", "getting-started.md")) + content = File.read(File.join(target_context_path, "getting-started.md")) expect(content).to be == "# Getting Started\n\nThis is a test." end @@ -124,15 +126,15 @@ def around end it "can install context from all gems" do - helper = subject.new(specifications: @specifications) - helper.instance_variable_set(:@context_path, @target_path) + helper = subject.new(root: @target_path, specifications: @specifications) installed = helper.install_all_context expect(installed).to be(:include?, "fake-gem") # Check that files were copied - expect(File).to be(:exist?, File.join(@target_path, "fake-gem", "getting-started.md")) - expect(File).to be(:exist?, File.join(@target_path, "fake-gem", "configuration.md")) + target_context_path = File.join(@target_path, ".agents", "context", "fake-gem") + expect(File).to be(:exist?, File.join(target_context_path, "getting-started.md")) + expect(File).to be(:exist?, File.join(target_context_path, "configuration.md")) end end end From 11e7bd248f16b73f5cf1b03ff36373079915db8f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 2 Sep 2026 15:41:21 +1200 Subject: [PATCH 2/2] Ignore generated agent files Assisted-By: devx/445006c1-c85f-4c03-962d-be8ad40fb517 --- .gitignore | 2 +- context/getting-started.md | 1 + guides/getting-started/readme.md | 1 + readme.md | 6 +++--- specification.md | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 0f4ea84..ac40d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /agents.md -/.agents/context +/.agents/ /.context /.bundle /pkg diff --git a/context/getting-started.md b/context/getting-started.md index 8e1f430..29e3bed 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -89,6 +89,7 @@ async-gem/ - Run `bake agent:context:install` to copy context to `.agents/context/` (with dot). - The `.agents/context/` directory is where installed context lives in your project. +- Ignore the generated `.agents/` directory in version control. - Don't edit files in `.agents/context/` - they get completely replaced when you reinstall. ## Providing Context (For Gem Authors) diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 8e1f430..29e3bed 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -89,6 +89,7 @@ async-gem/ - Run `bake agent:context:install` to copy context to `.agents/context/` (with dot). - The `.agents/context/` directory is where installed context lives in your project. +- Ignore the generated `.agents/` directory in version control. - Don't edit files in `.agents/context/` - they get completely replaced when you reinstall. ## Providing Context (For Gem Authors) diff --git a/readme.md b/readme.md index 92835c0..abf3c65 100644 --- a/readme.md +++ b/readme.md @@ -88,11 +88,11 @@ $ bake agent:context:show --gem async --file thread-safety ## Version Control -Both `.agents/context/` and `agents.md` should be committed to git: +The `.agents/` directory contains generated files and should be excluded from version control. The generated `agents.md` index should be committed: - `agents.md` is user-facing documentation that should be versioned. - - `.agents/context/` files are referenced by `agents.md` and needed for AI agents to function properly. - - This ensures AI agents in CI have access to the full context. + - `.agents/context/` can be restored by running `bake agent:context:install`. + - Ignoring the entire `.agents/` directory covers other generated agent resources too. ## Providing Context in Your Gem diff --git a/specification.md b/specification.md index 826f541..afac15c 100644 --- a/specification.md +++ b/specification.md @@ -82,7 +82,7 @@ Context consumers SHOULD create a `.agents/context/` directory in their project - **Location**: Must be at the project root (typically where package manifests are located). - **Purpose**: Contains context files copied from dependencies. - **Organization**: Must organize context by package name in subdirectories. -- **Exclusion**: SHOULD be excluded from version control (e.g., in `.gitignore`). +- **Exclusion**: The generated `.agents/` directory SHOULD be excluded from version control (e.g., in `.gitignore`). - **Transient Nature**: Should contain only reproducible content that can be regenerated from installed packages and MUST NOT contain unique or modified files. Example structure: