the_local 0.1.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +15 -0
  3. data/LICENSE.txt +21 -0
  4. data/PROVIDERS.md +135 -0
  5. data/README.md +72 -0
  6. data/Rakefile +15 -0
  7. data/exe/the_local +6 -0
  8. data/lib/generators/the_local/install_generator.rb +21 -0
  9. data/lib/generators/the_local/provider_generator.rb +144 -0
  10. data/lib/generators/the_local/templates/guide.md.tt +25 -0
  11. data/lib/generators/the_local/templates/reference.rb.tt +17 -0
  12. data/lib/generators/the_local/templates/the_local.rb.tt +46 -0
  13. data/lib/the_local/agent.rb +41 -0
  14. data/lib/the_local/builder.rb +30 -0
  15. data/lib/the_local/cli.rb +34 -0
  16. data/lib/the_local/disk_providers.rb +32 -0
  17. data/lib/the_local/installer.rb +41 -0
  18. data/lib/the_local/process_doc_writer.rb +48 -0
  19. data/lib/the_local/process_rules/develop_process_rules.md +97 -0
  20. data/lib/the_local/process_rules.rb +17 -0
  21. data/lib/the_local/railtie.rb +15 -0
  22. data/lib/the_local/rake.rb +23 -0
  23. data/lib/the_local/reference/guide.md +103 -0
  24. data/lib/the_local/reference.rb +16 -0
  25. data/lib/the_local/refresh.rb +28 -0
  26. data/lib/the_local/registry.rb +59 -0
  27. data/lib/the_local/scope.rb +15 -0
  28. data/lib/the_local/sync.rb +28 -0
  29. data/lib/the_local/tasks/the_local.rake +9 -0
  30. data/lib/the_local/the_local/agents/the_local-develop.md +111 -0
  31. data/lib/the_local/the_local/agents/the_local-info.md +111 -0
  32. data/lib/the_local/the_local/agents/the_local-install.md +111 -0
  33. data/lib/the_local/the_local.rb +59 -0
  34. data/lib/the_local/trigger_writer.rb +64 -0
  35. data/lib/the_local/version.rb +5 -0
  36. data/lib/the_local.rb +51 -0
  37. data/sig/the_local.rbs +4 -0
  38. metadata +95 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 16b120e16f6b7ca6130882854eaafb3d7d5c70ae35e2fbab17e094f4cb365056
4
+ data.tar.gz: 32ef6ba59eac385d577fc5d068477ffd465e14130dadf09b47b7b9166bf07b89
5
+ SHA512:
6
+ metadata.gz: e4fb273d9d3646d17a47f76b6c21048c38d62c2bf40ad491d609b9873e759e9063a47bdfc0f1eb1ccbf7cd33acea2757411b862aa5ec19ad5659d0fbbf3ba69f
7
+ data.tar.gz: 3fa1ff5e3989868bd713e7b21b1b253d866e9bc060b02294b1ea09f37b4056b8c0a2b4df9c009fcfa8198041ab301feab9985a1368daff5c6c561a1c072bf3b5
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-06-02
4
+
5
+ - Initial release.
6
+ - `TheLocal.register` API for gems and apps to contribute Claude Code locals,
7
+ behind a soft `require "the_local"` guard so providers work standalone.
8
+ - Provider build model: `TheLocal::Builder` + `rake the_local:build` render each
9
+ agent to a committed `.md`; the installer copies those files verbatim.
10
+ - `the_local:install` and `the_local:provider` Rails generators, plus a
11
+ rake-only `the_local:refresh` to re-sync a host after bundle changes.
12
+ - Direct-dependency install scope and a registry-generated delegation trigger
13
+ written into the host's `CLAUDE.md`/`AGENTS.md`.
14
+ - the_local dogfoods itself as a provider (`the_local-info`/`-install`/`-develop`)
15
+ and propagates a canonical develop-process doc into every host.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 tylercschneider
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/PROVIDERS.md ADDED
@@ -0,0 +1,135 @@
1
+ # Becoming a provider
2
+
3
+ `the_local` has two sides. The **consuming-app** side (`bundle exec the_local
4
+ install`, or `bin/rails g the_local:install` in a Rails app) installs the locals
5
+ of a project's direct dependencies into `.claude/agents/` — by reading each
6
+ dependency's committed `.md` straight from its gem path on disk. This document
7
+ covers the **provider** side: how a gem contributes those locals.
8
+
9
+ A provider registers its agents with `TheLocal.register` behind a soft
10
+ `require "the_local"` guard, so the gem keeps working when `the_local` is absent.
11
+
12
+ ## Build at home, copy verbatim
13
+
14
+ The agent *definition* (`the_local.rb` + `guide.md`) is the single source of
15
+ truth. The provider **renders it to committed `.md` files** with a gem-side
16
+ `the_local:build` task and commits those files to its own repo; the host install
17
+ then **reads them straight from your gem's path on disk and copies them verbatim**
18
+ into `.claude/agents/`. No provider code is loaded in the host and no register
19
+ block runs there — the committed, shipped `.md` is the entire contract. If those
20
+ files aren't committed and in the gemspec's `files`, the gem contributes nothing;
21
+ if they are, it contributes everything, with no install-time wiring.
22
+
23
+ So the rendered output depends only on the provider gem version — every app that
24
+ installs the same version gets a byte-identical local, instead of the host
25
+ re-rendering (and possibly drifting) from an in-memory definition. The committed
26
+ `.md` is a reviewable build artifact: it lands in the gem's own PR. Keep it in
27
+ sync with the definition by re-running `the_local:build` and committing the
28
+ result whenever you change the guide or an agent's `body:`/`description:`.
29
+
30
+ ## The common command interface
31
+
32
+ `the_local` exists to give every gem the **same command interface to apps**, so
33
+ a host agent always finds the same shape no matter which gem it's delegating to.
34
+ A provider exposes three lifecycle facets:
35
+
36
+ | Facet | Purpose | Typical tools |
37
+ |---|---|---|
38
+ | **`info`** | Read-only. Explains what the gem offers — its API and conventions. Makes no changes. | `Read` |
39
+ | **`install`** | Adds the gem to a host and sets it up **correctly** — the exact, gem-specific steps. | `Bash, Read, Edit` |
40
+ | **worker** | The proactive domain worker the host routes real work to. Named `develop` for libraries you build against, `operate` for CLIs you run. | per domain |
41
+
42
+ `info` and `install` are universal; the worker's name varies by the gem's
43
+ nature. Every agent embeds the provider's knowledge (`Reference.content`)
44
+ verbatim — that knowledge is the single source of truth, so the locals never
45
+ drift from the docs.
46
+
47
+ ## Adopting it — Rails-engine gems (generator)
48
+
49
+ If the gem has Rails available in development (e.g. a mountable engine), scaffold
50
+ the wiring with the generator:
51
+
52
+ ```bash
53
+ bin/rails g the_local:provider <gem_name> \
54
+ --scope "one-line phrase describing the gem's domain" \
55
+ [--prefix <filename-namespace>] \
56
+ [--worker develop|operate]
57
+ ```
58
+
59
+ It creates, and wires up:
60
+
61
+ ```
62
+ lib/<gem>/reference.rb # the Reference loader (single source of truth)
63
+ lib/<gem>/reference/guide.md # the knowledge, with TODO markers to fill in
64
+ lib/<gem>/the_local.rb # Companion.register! — info / install / <worker>
65
+ lib/<gem>/the_local/agents/*.md # the rendered locals, built + committed
66
+ Gemfile # + gem "the_local", github: … (soft, dev/test)
67
+ lib/<gem>.rb # + require_relative "<gem>/the_local"
68
+ Rakefile # + require "the_local/rake" (rake the_local:build)
69
+ ```
70
+
71
+ The generator builds the `.md` once on scaffold so they land in the diff for
72
+ review. They are rendered from the TODO placeholder definition, so you rebuild
73
+ them after filling in the real content (below).
74
+
75
+ Then **fill in the scaffold** — this is the real work the generator can't do:
76
+
77
+ 1. Write `reference/guide.md` as the complete user-facing API. Its **Install**
78
+ section must be the exact, correct steps for *this* gem (for an engine:
79
+ add the gem → `bundle install` → install + run migrations → wire concerns /
80
+ initializers), not a generic placeholder.
81
+ 2. Tailor the three agent `body:` strings in `the_local.rb` to the gem.
82
+ 3. **Rebuild and commit the locals.** The scaffold built `.md` from the TODO
83
+ placeholders, so regenerate them from your real definition and commit them:
84
+
85
+ ```bash
86
+ rake the_local:build
87
+ git add lib/<gem>/the_local/agents
88
+ ```
89
+
90
+ Rebuild whenever the guide or an agent's `body:`/`description:` changes — the
91
+ host copies these bytes verbatim, so a stale commit ships stale locals.
92
+ 4. Add a `companion_test` asserting the facets register and each embeds
93
+ `Reference.content`, plus a **drift test** asserting each committed file
94
+ equals its `agent.to_markdown` (so a forgotten rebuild fails CI). See
95
+ `test/the_local/companion_test.rb` — the_local is its own provider and uses
96
+ exactly this wiring.
97
+
98
+ ## Adopting it — non-Rails gems (manual)
99
+
100
+ A plain gem has no `bin/rails`, so do the same things by hand. `the_local` is
101
+ itself a non-Rails provider built this way — mirror its own wiring
102
+ (`lib/the_local/the_local.rb`, `lib/the_local/reference.rb`, the committed
103
+ `lib/the_local/the_local/agents/`, and the `Rakefile`):
104
+
105
+ 1. `lib/<gem>/reference.rb` — a `Reference` module with `DIR`, `content`, and
106
+ `read(name)` reading from `lib/<gem>/reference/`.
107
+ 2. `lib/<gem>/reference/guide.md` — the knowledge (single source of truth).
108
+ 3. `lib/<gem>/the_local.rb` — a `Companion.register!` that calls `TheLocal.register`
109
+ with an `agents_dir` (where the committed `.md` live) and declares the
110
+ `info` / `install` / worker agents, followed by the guard:
111
+
112
+ ```ruby
113
+ TheLocal.register("<gem>", scope: "…",
114
+ agents_dir: File.expand_path("the_local/agents", __dir__)) do |c|
115
+ # c.agent "info", …
116
+ end
117
+ ```
118
+
119
+ ```ruby
120
+ begin
121
+ require "the_local"
122
+ <Gem>::Companion.register!
123
+ rescue LoadError
124
+ # the_local not installed — <gem> works standalone.
125
+ end
126
+ ```
127
+ 4. `require_relative "<gem>/the_local"` from the gem's entrypoint (so your own
128
+ `the_local:build` and standalone use load the register block — a host never
129
+ needs it), and add `gem "the_local", github: "DYB-Development/the_local"` to
130
+ the Gemfile (dev/test — an optional companion, not a hard dependency).
131
+ 5. Add `require "the_local/rake"` to the `Rakefile`, then build, commit, and
132
+ **ship** the rendered locals — `rake the_local:build && git add
133
+ lib/<gem>/the_local/agents`, and make sure they're in the gemspec's `files`.
134
+ These committed bytes are the whole contract — what the host reads from disk;
135
+ rebuild and recommit on every change.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # TheLocal
2
+
3
+ Resident Claude Code expert subagents ("locals"), contributed by the gems an app
4
+ uses. Any gem or app declares the locals that know its conventions; `the_local`
5
+ aggregates the locals of an app's installed providers into its
6
+ `.claude/agents/`, plus a delegation rule so the app's agent actually uses them.
7
+
8
+ A "local" is a Claude Code subagent that knows one gem's conventions cold. The
9
+ host's orchestrating agent delegates that gem's work to it, so usage stays
10
+ consistent instead of drifting.
11
+
12
+ ## Installation
13
+
14
+ Add it to your Gemfile:
15
+
16
+ ```ruby
17
+ gem "the_local"
18
+ ```
19
+
20
+ or install it directly with `gem install the_local`. To track an unreleased
21
+ change, point at the repo instead:
22
+
23
+ ```ruby
24
+ gem "the_local", github: "DYB-Development/the_local"
25
+ ```
26
+
27
+ The gem's core is Rails-free, but the primary documented workflow uses the
28
+ `bin/rails g the_local:install` / `the_local:provider` generators, so those steps
29
+ assume a Rails host. The `the_local:refresh` rake task and the register API work
30
+ without Rails.
31
+
32
+ ## Usage
33
+
34
+ There are two sides.
35
+
36
+ **Consuming app** — install the locals of the app's direct dependencies (and the
37
+ app's own) into `.claude/agents/`, and write the delegation trigger:
38
+
39
+ ```bash
40
+ bin/rails g the_local:install
41
+ ```
42
+
43
+ Install **copies each provider's committed `.md` verbatim** — the rendering
44
+ happens in the provider gem at build time, not in the host — so every app on the
45
+ same gem version gets a byte-identical local. Re-run `the_local:refresh` (rake)
46
+ after a `bundle install`/`update` to re-sync.
47
+
48
+ **Provider gem** — contribute the locals an app installs. A gem registers its
49
+ agents with `TheLocal.register` behind a soft guard, exposing the common command
50
+ interface (`info` / `install` / worker), then renders them to committed `.md`
51
+ with `rake the_local:build`. Scaffold it with:
52
+
53
+ ```bash
54
+ bin/rails g the_local:provider <gem_name> --scope "the gem's domain"
55
+ ```
56
+
57
+ See [PROVIDERS.md](PROVIDERS.md) for the full provider guide, including the
58
+ manual steps for non-Rails gems.
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/DYB-Development/the_local.
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
13
+
14
+ require "the_local"
15
+ require "the_local/rake"
data/exe/the_local ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "the_local/cli"
5
+
6
+ TheLocal::CLI.new(ARGV).call
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "the_local"
5
+
6
+ module TheLocal
7
+ module Generators
8
+ # `bin/rails g the_local:install` — installs the locals contributed by the
9
+ # app's direct dependencies into .claude/agents/, and writes the delegation
10
+ # trigger. A thin wrapper over Refresh, which discovers providers from each
11
+ # bundled gem's committed agents on disk.
12
+ class InstallGenerator < Rails::Generators::Base
13
+ desc "Install the Claude Code locals of this app's direct dependencies"
14
+
15
+ def install_locals
16
+ allowed = Refresh.call(destination: destination_root)
17
+ say "the_local: installed locals for #{allowed.join(", ")}", :green
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "the_local/builder"
5
+
6
+ module TheLocal
7
+ module Generators
8
+ # `bin/rails g the_local:provider <gem_name>` — scaffolds the provider side
9
+ # of the_local into a gem: a Reference loader, a knowledge guide, and a
10
+ # Companion that registers the common command interface (info / install /
11
+ # worker) via TheLocal.register behind a soft `require "the_local"` guard.
12
+ #
13
+ # The companion app side is `the_local:install`; this is its mirror for the
14
+ # gems that *contribute* locals. See PROVIDERS.md.
15
+ class ProviderGenerator < Rails::Generators::Base
16
+ source_root File.expand_path("templates", __dir__)
17
+
18
+ GEMFILE_LINE = %(gem "the_local", github: "DYB-Development/the_local")
19
+ RAKEFILE_REQUIRE = %(require "the_local/rake")
20
+
21
+ desc "Scaffold the_local provider wiring (info/install/worker locals) into this gem"
22
+
23
+ argument :gem_name, type: :string, desc: "The providing gem's name, e.g. citizen"
24
+ class_option :prefix, type: :string,
25
+ desc: "Agent filename namespace (defaults to the gem name)"
26
+ class_option :scope, type: :string, default: "TODO: one-line phrase describing this gem's domain",
27
+ desc: "One-line domain phrase used in the delegation trigger"
28
+ class_option :worker, type: :string, default: "develop",
29
+ desc: "Name of the domain worker facet (develop for libraries, operate for CLIs)"
30
+
31
+ def relocate_to_gem_root
32
+ self.destination_root = gem_root
33
+ end
34
+
35
+ def create_reference
36
+ template "reference.rb.tt", "lib/#{lib_path}/reference.rb"
37
+ end
38
+
39
+ def create_guide
40
+ template "guide.md.tt", "lib/#{lib_path}/reference/guide.md"
41
+ end
42
+
43
+ def create_companion
44
+ template "the_local.rb.tt", "lib/#{lib_path}/the_local.rb"
45
+ end
46
+
47
+ def add_to_gemfile
48
+ return if gem_name == "the_local"
49
+
50
+ gemfile = File.join(destination_root, "Gemfile")
51
+ return unless File.exist?(gemfile)
52
+ return if File.read(gemfile).include?(GEMFILE_LINE)
53
+
54
+ append_to_file "Gemfile",
55
+ "\n# Optional companion: #{gem_name} registers its locals with the_local " \
56
+ "when present.\n# Registration is guarded, so #{gem_name} works standalone.\n#{GEMFILE_LINE}\n"
57
+ end
58
+
59
+ def require_from_entrypoint
60
+ entrypoint = File.join("lib", "#{lib_path}.rb")
61
+ return unless File.exist?(File.join(destination_root, entrypoint))
62
+ return if File.read(File.join(destination_root, entrypoint)).include?(require_line)
63
+
64
+ append_to_file entrypoint,
65
+ "\n# Register #{gem_name}'s locals when the_local is present (no-op otherwise).\n" \
66
+ "#{require_line}\n"
67
+ end
68
+
69
+ def hook_build_task_into_rakefile
70
+ return unless File.exist?(File.join(destination_root, "Rakefile"))
71
+ return if File.read(File.join(destination_root, "Rakefile")).include?(RAKEFILE_REQUIRE)
72
+
73
+ append_to_file "Rakefile",
74
+ "\n# Render #{gem_name}'s committed the_local agent files: `rake the_local:build`.\n" \
75
+ "require \"#{lib_path}\"\n#{RAKEFILE_REQUIRE}\n"
76
+ end
77
+
78
+ # Render the committed .md files now, so they land in the diff for review.
79
+ # Loading the companion registers this gem's locals; reset first so only
80
+ # they are built, not anything else the process may have registered.
81
+ def build_agent_files
82
+ companion = File.join(destination_root, "lib", lib_path, "the_local.rb")
83
+ return unless File.exist?(companion)
84
+
85
+ TheLocal.reset!
86
+ load companion
87
+ TheLocal::Builder.new(registry: TheLocal.registry).call
88
+ end
89
+
90
+ private
91
+
92
+ def require_line
93
+ %(require_relative "#{File.basename(lib_path)}/the_local")
94
+ end
95
+
96
+ def prefix
97
+ options[:prefix] || gem_name
98
+ end
99
+
100
+ # Thor renders templates via File.binread, so the ERB buffer is ASCII-8BIT.
101
+ # A UTF-8 scope would flip the buffer mid-render and then clash with the
102
+ # template's own non-ASCII literals; match the buffer's encoding to avoid it.
103
+ def scope
104
+ options[:scope]&.b
105
+ end
106
+
107
+ def worker
108
+ options[:worker]
109
+ end
110
+
111
+ def gem_root
112
+ ascend_to_gemspec(destination_root) || destination_root
113
+ end
114
+
115
+ def ascend_to_gemspec(start)
116
+ dir = File.expand_path(start)
117
+ loop do
118
+ return dir if Dir.glob(File.join(dir, "*.gemspec")).any?
119
+
120
+ parent = File.dirname(dir)
121
+ return nil if parent == dir
122
+
123
+ dir = parent
124
+ end
125
+ end
126
+
127
+ def lib_path
128
+ gem_name.tr("-", "/")
129
+ end
130
+
131
+ def module_name
132
+ gem_name.split("-").map { |segment| segment.split("_").map(&:capitalize).join }.join("::")
133
+ end
134
+
135
+ def open_module
136
+ module_name.split("::").map { |name| "module #{name}" }.join("\n")
137
+ end
138
+
139
+ def close_module
140
+ module_name.split("::").map { "end" }.join("\n")
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,25 @@
1
+ ## <%= module_name %>
2
+
3
+ > **DO NOT** explore the <%= gem_name %> gem source code. This reference is the
4
+ > complete user-facing API, embedded verbatim into every <%= gem_name %> local so
5
+ > their guidance never drifts. Keep it the single source of truth.
6
+
7
+ TODO: One paragraph — what <%= gem_name %> is and the problem it solves.
8
+
9
+ ### What it offers
10
+
11
+ TODO: The public API — the methods/classes/DSL a host actually calls, with tiny
12
+ examples. This is what the `info` and `<%= worker %>` locals answer from.
13
+
14
+ ### Install
15
+
16
+ TODO: The exact, correct steps to add <%= gem_name %> to a host and set it up —
17
+ this is what the `install` local follows. Be specific to how <%= gem_name %> is
18
+ actually installed (e.g. for a Rails engine: add the gem, `bundle install`,
19
+ install + run migrations, wire any concerns/initializers). Don't leave it
20
+ generic.
21
+
22
+ ### <%= module_name %> conventions
23
+
24
+ TODO: The conventions the `<%= worker %>` local must enforce when doing
25
+ <%= gem_name %> work, so usage stays consistent across the host.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ <%= open_module %>
4
+ # Single source of truth for <%= gem_name %>'s user-facing API, read by the
5
+ # the_local companion subagents so their guidance never drifts from the docs.
6
+ module Reference
7
+ DIR = File.expand_path("reference", __dir__)
8
+
9
+ def self.content
10
+ read("guide.md")
11
+ end
12
+
13
+ def self.read(name)
14
+ File.read(File.join(DIR, name)).chomp
15
+ end
16
+ end
17
+ <%= close_module %>
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "reference"
4
+
5
+ <%= open_module %>
6
+ # Registers <%= gem_name %>'s locals (Claude Code subagents) with the_local.
7
+ # These are the common command interface every provider exposes to apps:
8
+ # `info` (read-only, explains the gem), `install` (sets it up in a host), and
9
+ # `<%= worker %>` (the proactive domain worker). Soft dependency: registration
10
+ # is a no-op when the_local is absent.
11
+ module Companion
12
+ def self.register!
13
+ TheLocal.register("<%= gem_name %>"<%= ", prefix: \"#{prefix}\"" unless prefix == gem_name %>, scope: "<%= scope %>",
14
+ agents_dir: File.expand_path("the_local/agents", __dir__)) do |c|
15
+ c.agent "info",
16
+ description: "Use to learn what <%= gem_name %> offers — its API and conventions.",
17
+ tools: "Read",
18
+ body: "You explain what <%= gem_name %> does and how to use it, answering from the " \
19
+ "reference. You make no changes. TODO: tailor this body to <%= gem_name %>.",
20
+ knowledge: <%= module_name %>::Reference.content
21
+
22
+ c.agent "install",
23
+ description: "Use to add <%= gem_name %> to a project and set it up correctly.",
24
+ tools: "Bash, Read, Edit",
25
+ body: "You add <%= gem_name %> to the project and complete its setup, following the " \
26
+ "reference's install section exactly. TODO: tailor this body to <%= gem_name %>.",
27
+ knowledge: <%= module_name %>::Reference.content
28
+
29
+ c.agent "<%= worker %>",
30
+ description: "Use PROACTIVELY for any <%= gem_name %> work. MUST BE USED instead of " \
31
+ "hand-rolling it. TODO: name the concrete tasks this local owns.",
32
+ tools: "Read, Write, Edit, Grep",
33
+ body: "You do <%= gem_name %> work following the reference's conventions. TODO: tailor " \
34
+ "this body to <%= gem_name %>.",
35
+ knowledge: <%= module_name %>::Reference.content
36
+ end
37
+ end
38
+ end
39
+ <%= close_module %>
40
+
41
+ begin
42
+ require "the_local"
43
+ <%= module_name %>::Companion.register!
44
+ rescue LoadError
45
+ # the_local not installed — <%= gem_name %> works standalone.
46
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # An immutable description of one Claude Code subagent contributed by a
5
+ # provider (a gem or the app). Renders to a `.claude/agents/*.md` definition.
6
+ #
7
+ # +gem_name+ is the providing gem (used to filter to a host's direct
8
+ # dependencies). +prefix+ is the filename namespace (often a shorter alias,
9
+ # e.g. gem "keystone_ui" → prefix "keystone"). +knowledge+ is a string or
10
+ # array of strings appended below the role body — the provider's reference(s).
11
+ # +source_path+ is the absolute path to the provider's committed, pre-rendered
12
+ # .md file; the host installer copies it verbatim. It stays nil until a
13
+ # provider supplies an agents_dir, so existing providers keep working.
14
+ Agent = Data.define(:gem_name, :prefix, :name, :description, :tools, :body, :knowledge, :source_path) do
15
+ def initialize(source_path: nil, **args)
16
+ super
17
+ end
18
+
19
+ def qualified_name
20
+ "#{prefix}-#{name}"
21
+ end
22
+
23
+ def filename
24
+ "#{qualified_name}.md"
25
+ end
26
+
27
+ def to_markdown
28
+ <<~MARKDOWN
29
+ ---
30
+ name: #{qualified_name}
31
+ description: #{description}
32
+ tools: #{tools}
33
+ ---
34
+
35
+ #{body}
36
+
37
+ #{Array(knowledge).join("\n\n")}
38
+ MARKDOWN
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module TheLocal
6
+ # Renders each registered agent's markdown to its committed source_path, so a
7
+ # provider gem can commit the files and the host installer later copies them
8
+ # verbatim (rather than rendering at install time). Plain Ruby — driven by the
9
+ # the_local:build rake task a provider runs. Agents that declared no agents_dir
10
+ # (and so have no source_path) are skipped: there is nowhere to write them.
11
+ class Builder
12
+ def initialize(registry:)
13
+ @registry = registry
14
+ end
15
+
16
+ def call
17
+ buildable_agents.map do |agent|
18
+ FileUtils.mkdir_p(File.dirname(agent.source_path))
19
+ File.write(agent.source_path, agent.to_markdown)
20
+ agent.source_path
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def buildable_agents
27
+ @registry.agents.select(&:source_path)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "the_local"
4
+
5
+ module TheLocal
6
+ # Rails-free entrypoint for the `the_local` executable. `the_local install`
7
+ # syncs the current bundle's locals into .claude/agents/, so a plain gem can
8
+ # install without a Rails generator or a Rakefile.
9
+ class CLI
10
+ def initialize(argv, out: $stdout)
11
+ @argv = argv
12
+ @out = out
13
+ end
14
+
15
+ def call
16
+ case @argv.first
17
+ when "install" then install
18
+ else usage
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def install
25
+ allowed = Refresh.call(destination: Dir.pwd)
26
+ @out.puts "the_local: installed locals for #{allowed.join(", ")}"
27
+ @out.puts "Restart your Claude Code session to use them — agents load at startup."
28
+ end
29
+
30
+ def usage
31
+ @out.puts "Usage: the_local install"
32
+ end
33
+ end
34
+ end