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
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # Discovers providers by reading their committed agent files straight from each
5
+ # bundled gem's path on disk — no gem code is loaded and no register block runs.
6
+ # The committed .md (the build-and-commit artifact) is the declarative contract;
7
+ # a provider contributes simply by shipping those files. Populates the same
8
+ # registry the install pipeline already reads, so Installer/TriggerWriter/Sync
9
+ # are unchanged.
10
+ module DiskProviders
11
+ AGENTS_GLOB = File.join("lib", "**", "the_local", "agents", "*.md")
12
+
13
+ def self.load(registry:, specs:)
14
+ specs.each { |spec| register(registry, spec) }
15
+ end
16
+
17
+ def self.register(registry, spec)
18
+ files = Dir.glob(File.join(spec[:path], AGENTS_GLOB))
19
+ return if files.empty?
20
+
21
+ agents = files.map { |file| agent_from(spec[:name], file) }
22
+ registry.add_provider(Provider.new(gem_name: spec[:name], prefix: agents.first.prefix, scope: nil))
23
+ agents.each { |agent| registry.add(agent) }
24
+ end
25
+
26
+ def self.agent_from(gem_name, file)
27
+ prefix, _, name = File.basename(file, ".md").rpartition("-")
28
+ Agent.new(gem_name: gem_name, prefix: prefix, name: name,
29
+ description: nil, tools: nil, body: nil, knowledge: nil, source_path: file)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module TheLocal
6
+ # Copies each allowed provider's committed agent file into a destination's
7
+ # .claude/agents/ directory, verbatim. Plain Ruby so the Rails generator is a
8
+ # thin wrapper over it.
9
+ class Installer
10
+ AGENTS_DIR = ".claude/agents"
11
+
12
+ def initialize(registry:, destination:, allowed_gems:)
13
+ @registry = registry
14
+ @destination = destination
15
+ @allowed_gems = allowed_gems
16
+ end
17
+
18
+ def call
19
+ agents_dir = File.join(@destination, AGENTS_DIR)
20
+ FileUtils.mkdir_p(agents_dir)
21
+
22
+ installed_agents.each do |agent|
23
+ ensure_committed!(agent)
24
+ FileUtils.cp(agent.source_path, File.join(agents_dir, agent.filename))
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def installed_agents
31
+ @registry.agents.select { |agent| @allowed_gems.include?(agent.gem_name) }
32
+ end
33
+
34
+ def ensure_committed!(agent)
35
+ return if agent.source_path && File.exist?(agent.source_path)
36
+
37
+ raise Error, "the_local: #{agent.gem_name} registered #{agent.qualified_name} without a committed " \
38
+ "agent file. Run `rake the_local:build` in #{agent.gem_name} and commit its the_local/agents/."
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "process_rules"
4
+
5
+ module TheLocal
6
+ # Writes the canonical develop-process rules into a host's CLAUDE.md as a
7
+ # managed block, read at the start of every session so the host agent always
8
+ # follows one source of truth. Re-propagated on every install/refresh. Uses
9
+ # its own markers so it coexists with the delegation trigger in the same file.
10
+ class ProcessDocWriter
11
+ BEGIN_MARKER = "<!-- the_local:process:begin -->"
12
+ END_MARKER = "<!-- the_local:process:end -->"
13
+ RULES_FILENAME = "develop_process_rules.md"
14
+
15
+ def initialize(destination:, filename: "CLAUDE.md")
16
+ @destination = destination
17
+ @filename = filename
18
+ end
19
+
20
+ def call
21
+ File.write(File.join(@destination, RULES_FILENAME), "#{ProcessRules.content}\n")
22
+ path = File.join(@destination, @filename)
23
+ existing = File.exist?(path) ? File.read(path) : ""
24
+ File.write(path, "#{merge(existing)}\n")
25
+ end
26
+
27
+ def block
28
+ <<~MARKDOWN.chomp
29
+ #{BEGIN_MARKER}
30
+ Read and follow this develop process for all work in this project. It is
31
+ also written verbatim to `#{RULES_FILENAME}` — reference that file directly.
32
+
33
+ #{ProcessRules.content}
34
+ #{END_MARKER}
35
+ MARKDOWN
36
+ end
37
+
38
+ private
39
+
40
+ def merge(existing)
41
+ section = /#{Regexp.escape(BEGIN_MARKER)}.*?#{Regexp.escape(END_MARKER)}/m
42
+ return existing.sub(section, block) if existing.match?(section)
43
+ return block if existing.strip.empty?
44
+
45
+ "#{existing.chomp}\n\n#{block}"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,97 @@
1
+ # Develop Process
2
+
3
+ The standard process for writing code across all projects. Default to these rules
4
+ unless a project explicitly overrides them.
5
+
6
+ ---
7
+
8
+ ## Diverging from this process
9
+
10
+ Read this process before starting work and follow it — it is the default for
11
+ every session. If a task genuinely calls for breaking one of these rules, do not
12
+ silently deviate: **PAUSE and ask for a one-time exception**, naming the rule and
13
+ why it should be set aside here. An exception is granted for that instance only —
14
+ it needs no doc or notes update — and then you continue. Do not treat a granted
15
+ exception as a standing change to the process.
16
+
17
+ ---
18
+
19
+ ## Test-Driven Development
20
+
21
+ TDD is the default for everything. Work one tiny cycle at a time:
22
+
23
+ 1. **Write one test that asserts one thing.**
24
+ 2. **Run it and watch it fail** — for the right reason. A test you never saw fail
25
+ proves nothing.
26
+ 3. **Write the minimum code to make it pass.**
27
+ 4. **Run the test and watch it pass.**
28
+ 5. **Commit.**
29
+ 6. Repeat with the next test.
30
+
31
+ One assertion per test. One test per commit cycle. No batching multiple behaviors
32
+ into a single test or a single commit.
33
+
34
+ ---
35
+
36
+ ## Commits
37
+
38
+ - A commit is normally **two files: the test file and the code file.**
39
+ - When implementing or updating an interface (e.g. a new controller endpoint) a
40
+ commit may touch more files (route + controller + view) — that is the minimal
41
+ coherent unit for that interface, and it is allowed.
42
+ - Keep each commit focused on the one behavior the test describes.
43
+
44
+ ---
45
+
46
+ ## What to Test
47
+
48
+ - **Test our own code only.**
49
+ - **Never test third-party code** — not a gem, not an API, not a framework. The
50
+ only test that may reference a dependency is one that asserts *our system is
51
+ correctly wired to it* (the integration seam), never the dependency's own
52
+ behavior.
53
+ - **Never test another interface inside a unit test.** A test covers one interface.
54
+ The single exception is the smoke integration test described below.
55
+
56
+ ---
57
+
58
+ ## Smoke Integration Test
59
+
60
+ When implementing an interface, write **one smoke integration test** that exercises
61
+ the interface end to end and proves the pieces are connected. This is the one place
62
+ where touching more than the unit under test is expected and correct.
63
+
64
+ ---
65
+
66
+ ## Pull Requests
67
+
68
+ - **Always work on a feature branch and open a PR.** Confirm the target branch
69
+ before any git operation (`git branch --show-current`).
70
+ - **Keep PRs small and manageable** — typically **no more than 8–10 files.**
71
+ - Keep the focus of a PR narrow. One concern per PR.
72
+ - **All tests pass before opening the PR.**
73
+ - **The linter and every other CI check pass before opening the PR.**
74
+ - Never start a new PR until the previous one is merged.
75
+
76
+ ---
77
+
78
+ ## Code Quality
79
+
80
+ - Follow Clean Code principles: small functions, clear names, no surprises.
81
+ - Follow SOLID principles. Readable by a human first.
82
+ - Keep it simple — no abstraction until a real need calls for it.
83
+ - Explicitly require libraries rather than assuming autoload.
84
+
85
+ ## Comments
86
+
87
+ - **Write self-documenting code, not comments.** Code should be clean and readable
88
+ on its own. Names — of classes, methods, variables, and partials — carry the intent.
89
+ - **A comment is a smell.** If you feel a comment is needed, the code is either built
90
+ wrong or needs refactoring (a clearer name, a smaller method, an extracted object or
91
+ partial) so the intent is obvious without prose. Follow SOLID and this resolves itself.
92
+ - Do not leave explanatory headers on classes/methods, inline "what this does" notes,
93
+ or section banners. Delete them and let the structure speak.
94
+ - Narrow exceptions, kept rare: a genuinely non-obvious *why* (a workaround for an
95
+ external bug, a legal/security constraint) and machine-readable annotations the
96
+ tooling requires (e.g. `rubocop:disable`). Prefer refactoring over a "why" comment
97
+ whenever you can.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # Loads the canonical develop-process rules the_local propagates into every
5
+ # host, so a host agent reads and follows one source of truth.
6
+ module ProcessRules
7
+ DIR = File.expand_path("process_rules", __dir__)
8
+
9
+ def self.content
10
+ read("develop_process_rules.md")
11
+ end
12
+
13
+ def self.read(name)
14
+ File.read(File.join(DIR, name)).chomp
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+ require "the_local"
5
+
6
+ module TheLocal
7
+ # Minimal Railtie whose only job is to expose the `the_local:refresh` rake task
8
+ # to the host app. Registration deliberately does NOT use a Railtie (it happens
9
+ # at gem load — see the design plan); this is purely task exposure.
10
+ class Railtie < Rails::Railtie
11
+ rake_tasks do
12
+ load File.expand_path("tasks/the_local.rake", __dir__)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+ require "the_local"
5
+ require "the_local/builder"
6
+
7
+ # Gem-side build task. A provider adds `require "the_local/rake"` to its Rakefile
8
+ # (after loading the gem, so its locals are registered) and runs
9
+ # `rake the_local:build` to (re)render its committed .claude agent files from the
10
+ # registered definitions. Host apps don't use this — they install/refresh.
11
+ namespace :the_local do
12
+ desc "Render this provider's committed agent files from its registered definitions"
13
+ task :build do
14
+ written = TheLocal::Builder.new(registry: TheLocal.registry).call
15
+ puts "the_local: built #{written.length} agent file(s)"
16
+ end
17
+
18
+ desc "Install/refresh this project's locals from the current bundle into .claude/agents/"
19
+ task :install do
20
+ allowed = TheLocal::Refresh.call(destination: Dir.pwd)
21
+ puts "the_local: installed locals for #{allowed.join(", ")}"
22
+ end
23
+ end
@@ -0,0 +1,103 @@
1
+ ## TheLocal
2
+
3
+ > **DO NOT** explore the the_local gem source code. This reference is the
4
+ > complete user-facing API, embedded verbatim into every the_local local so
5
+ > their guidance never drifts. Keep it the single source of truth.
6
+
7
+ the_local is the engine that lets any gem or app ship resident Claude Code
8
+ expert subagents ("locals") that know its conventions. A provider gem registers
9
+ its locals once; the_local renders them to committed `.md` files and installs
10
+ the aggregated set from every directly-depended provider into a consuming app's
11
+ `.claude/agents/`, plus a delegation rule so the host's agent actually uses them.
12
+
13
+ ### The model
14
+
15
+ - **Providers define locals.** A gem (or the app) calls `TheLocal.register` to
16
+ declare its locals; each `c.agent` becomes one local. The register block runs
17
+ only at build time, behind a soft `require "the_local"` guard so the gem still
18
+ works standalone.
19
+ - **`the_local:build` renders committed `.md`.** The provider runs
20
+ `rake the_local:build`; `TheLocal::Builder` writes each agent to its
21
+ `source_path` under `lib/<gem>/the_local/agents/<prefix>-<name>.md`. The
22
+ rendered files are committed to the provider's repo. **These committed files
23
+ are the contract** — they are what a host reads. The register block + `guide.md`
24
+ are the source of truth they're built from.
25
+ - **Install discovers committed `.md` on disk.** In a host, install reads each
26
+ direct dependency's committed `lib/**/the_local/agents/*.md` straight from its
27
+ gem path and copies them into `.claude/agents/` byte-for-byte — no provider
28
+ code is loaded and no register block runs in the host. Output depends only on
29
+ the provider gem version (a true carbon copy across every app), a provider needs
30
+ no install-time wiring to be found, and a fragile gem can't crash the install.
31
+ - **The delegation trigger.** Install also writes a registry-generated block into
32
+ the host's `CLAUDE.md`/`AGENTS.md` telling the host agent to delegate to these
33
+ locals. This is what makes delegation actually happen.
34
+ - **Direct-dependency scope.** Only the host's *direct* dependencies contribute
35
+ locals; transitive provider gems are filtered out, so a host gets exactly the
36
+ experts for the gems it chose.
37
+
38
+ ### Install (in any gem or app)
39
+
40
+ 1. Add the gem to the host's `Gemfile` (until it is on RubyGems, use a git
41
+ source: `gem "the_local", github: "DYB-Development/the_local"`), then
42
+ `bundle install`.
43
+ 2. Run `bundle exec the_local install`. This syncs every direct provider's
44
+ committed locals into `.claude/agents/` and writes the delegation trigger
45
+ into `CLAUDE.md`/`AGENTS.md`. It needs no Rails — a plain gem installs the
46
+ same way an app does.
47
+ 3. Re-run `bundle exec the_local install` after any bundle change (a provider
48
+ added, removed, or upgraded) to bring the host's locals back in sync. The
49
+ shell can automate this; the gem only exposes the command.
50
+
51
+ Rails apps can equivalently run `bin/rails g the_local:install` and
52
+ `the_local:refresh`; a gem that already wires `require "the_local/rake"` into
53
+ its Rakefile also gets `rake the_local:install`. All three share one engine.
54
+
55
+ ### Author a provider (turn a gem into a provider)
56
+
57
+ 1. Run `bin/rails g the_local:provider <gem_name>` (pass `--scope`,
58
+ `--prefix`, `--worker` as needed). It scaffolds `lib/<gem>/reference.rb`, a
59
+ `lib/<gem>/reference/guide.md`, and a `lib/<gem>/the_local.rb` companion that
60
+ registers the standard interface; hooks `the_local:build` into the `Rakefile`;
61
+ requires the companion from the gem entrypoint; and builds the committed
62
+ `.md` for review.
63
+ 2. Write `guide.md` in this format — it is the single source of truth and is
64
+ embedded verbatim into every local. Document *your own* gem only: what it
65
+ does, how to install it, the conventions to enforce. Name companion gems but
66
+ do not explain their internals.
67
+ 3. Tailor the register block bodies and `scope` to your gem; the standard
68
+ interface is `info` (read-only explainer), `install` (sets the gem up in a
69
+ host), and a domain worker (`develop` for libraries, `operate` for CLIs).
70
+ 4. Run `rake the_local:build`, then **commit and ship**
71
+ `lib/<gem>/the_local/agents/*.md` (they must be in the gemspec's `files`).
72
+ This is the whole contract: a host discovers your locals by reading these
73
+ committed files from your gem on disk — it never loads your gem or runs your
74
+ register block — so if they aren't committed and shipped, you contribute
75
+ nothing, and if they are, you contribute everything. A drift test asserting
76
+ each committed file equals its `agent.to_markdown` keeps the artifact honest.
77
+
78
+ ### TheLocal.register
79
+
80
+ ```ruby
81
+ TheLocal.register("my_gem", prefix: "my_gem", scope: "one-line domain phrase",
82
+ agents_dir: File.expand_path("the_local/agents", __dir__)) do |c|
83
+ c.agent "info",
84
+ description: "Use to learn what my_gem offers.",
85
+ tools: "Read",
86
+ body: "You explain my_gem, answering only from the reference. You make no changes.",
87
+ knowledge: MyGem::Reference.content
88
+ end
89
+ ```
90
+
91
+ - `gem_name` (first arg) filters to a host's direct dependencies.
92
+ - `prefix` is the agent filename namespace; defaults to the gem name.
93
+ - `scope` is a one-line domain phrase used to generate the delegation trigger.
94
+ - `agents_dir` is the absolute path to the committed `.md` files; each agent
95
+ records its `source_path` there so the installer can copy it verbatim.
96
+
97
+ ### Conventions
98
+
99
+ - The register block lives behind `begin require "the_local" … rescue LoadError`
100
+ so the gem still works when the_local is absent.
101
+ - `guide.md` documents the providing gem only and stays the single source of
102
+ truth; never let a rendered `.md` drift from `agent.to_markdown`.
103
+ - Commit the rendered `.md`; never render in the host at install time.
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # Loads the_local's own knowledge guide, embedded verbatim into its locals.
5
+ module Reference
6
+ DIR = File.expand_path("reference", __dir__)
7
+
8
+ def self.content
9
+ read("guide.md")
10
+ end
11
+
12
+ def self.read(name)
13
+ File.read(File.join(DIR, name)).chomp
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # Re-syncs a host's locals from its current bundle. Discovers providers by
5
+ # reading their committed agent files from each bundled gem's path on disk
6
+ # (see DiskProviders) — no gem code is loaded, so a fragile gem can't crash the
7
+ # install and a provider needs no register/require wiring at install time to
8
+ # contribute. Then gathers the direct and bundled gem names from a Bundler
9
+ # definition and runs a Sync. Both the provider discovery and the definition
10
+ # are injectable so the logic stays testable without a real bundle.
11
+ module Refresh
12
+ def self.call(destination:, definition: Bundler.definition,
13
+ load_providers: -> { DiskProviders.load(registry: TheLocal.registry, specs: specs_from(definition)) })
14
+ TheLocal.reset!
15
+ load_providers.call
16
+ Sync.new(
17
+ registry: TheLocal.registry,
18
+ destination: destination,
19
+ direct_dependencies: definition.dependencies.map(&:name),
20
+ bundled_gems: definition.specs.map(&:name)
21
+ ).call
22
+ end
23
+
24
+ def self.specs_from(definition)
25
+ definition.specs.map { |spec| { name: spec.name, path: spec.full_gem_path } }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # A registered provider (gem or app): its gem name, filename prefix, and a
5
+ # one-line scope used to generate the delegation trigger.
6
+ Provider = Data.define(:gem_name, :prefix, :scope)
7
+
8
+ # Accumulates the providers and agents contributed by everything that calls
9
+ # TheLocal.register. The install generator reads this to write .claude/agents/
10
+ # and the delegation trigger.
11
+ class Registry
12
+ def initialize
13
+ @agents = []
14
+ @providers = []
15
+ end
16
+
17
+ attr_reader :agents, :providers
18
+
19
+ def add(agent)
20
+ @agents << agent
21
+ end
22
+
23
+ def add_provider(provider)
24
+ @providers << provider
25
+ end
26
+
27
+ def clear
28
+ @agents.clear
29
+ @providers.clear
30
+ end
31
+ end
32
+
33
+ # Yielded to a provider's register block. Turns each `agent` call into an
34
+ # Agent tagged with the providing gem and namespaced under its prefix.
35
+ class Collector
36
+ def initialize(gem_name, prefix, registry, agents_dir: nil)
37
+ @gem_name = gem_name
38
+ @prefix = prefix
39
+ @registry = registry
40
+ @agents_dir = agents_dir
41
+ end
42
+
43
+ def agent(name, description:, tools:, body:, knowledge: nil)
44
+ @registry.add(
45
+ Agent.new(gem_name: @gem_name, prefix: @prefix, name: name,
46
+ description: description, tools: tools, body: body, knowledge: knowledge,
47
+ source_path: source_path_for(name))
48
+ )
49
+ end
50
+
51
+ private
52
+
53
+ def source_path_for(name)
54
+ return nil unless @agents_dir
55
+
56
+ File.join(@agents_dir, "#{@prefix}-#{name}.md")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # Decides which providers' locals a host installs: its DIRECT dependencies plus
5
+ # the host project itself — never transitive dependencies. A registered
6
+ # provider is included when it is a direct dependency, or when it is not a
7
+ # bundled gem at all (which means it is the app registering its own locals).
8
+ module Scope
9
+ def self.allowed_gems(provider_gem_names:, direct_dependencies:, bundled_gems:)
10
+ provider_gem_names.uniq.select do |name|
11
+ direct_dependencies.include?(name) || !bundled_gems.include?(name)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ # Orchestrates a full sync: resolves which gems are in scope (direct deps + the
5
+ # app), writes their agents, and writes the delegation trigger. Plain Ruby so
6
+ # both the install generator and the refresh rake task share one path. Returns
7
+ # the allowed gem names (for reporting).
8
+ class Sync
9
+ def initialize(registry:, destination:, direct_dependencies:, bundled_gems:)
10
+ @registry = registry
11
+ @destination = destination
12
+ @direct_dependencies = direct_dependencies
13
+ @bundled_gems = bundled_gems
14
+ end
15
+
16
+ def call
17
+ allowed = Scope.allowed_gems(
18
+ provider_gem_names: @registry.providers.map(&:gem_name),
19
+ direct_dependencies: @direct_dependencies,
20
+ bundled_gems: @bundled_gems
21
+ )
22
+ Installer.new(registry: @registry, destination: @destination, allowed_gems: allowed).call
23
+ TriggerWriter.new(registry: @registry, destination: @destination, allowed_gems: allowed).call
24
+ ProcessDocWriter.new(destination: @destination).call
25
+ allowed
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :the_local do
4
+ desc "Re-sync installed locals from the current bundle"
5
+ task refresh: :environment do
6
+ allowed = TheLocal::Refresh.call(destination: Rails.root.to_s)
7
+ puts "the_local: refreshed locals for #{allowed.join(", ")}"
8
+ end
9
+ end
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: the_local-develop
3
+ description: Use PROACTIVELY to turn a gem into a the_local provider — scaffolding the companion, authoring the guide, and committing the rendered locals. MUST BE USED instead of wiring a provider by hand.
4
+ tools: Read, Write, Edit, Grep
5
+ ---
6
+
7
+ You turn a gem into a the_local provider following the reference's provider-author workflow: run `the_local:provider`, write guide.md as the single source of truth (your own gem only), tailor the register block, and hook the_local:build into the Rakefile. The deliverable is the committed, shipped lib/<gem>/the_local/agents/*.md — that is the whole contract a host reads from disk; a host never loads the gem, so unless those files are built, committed, and in the gemspec, the gem contributes nothing. You keep them in sync with agent.to_markdown.
8
+
9
+ ## TheLocal
10
+
11
+ > **DO NOT** explore the the_local gem source code. This reference is the
12
+ > complete user-facing API, embedded verbatim into every the_local local so
13
+ > their guidance never drifts. Keep it the single source of truth.
14
+
15
+ the_local is the engine that lets any gem or app ship resident Claude Code
16
+ expert subagents ("locals") that know its conventions. A provider gem registers
17
+ its locals once; the_local renders them to committed `.md` files and installs
18
+ the aggregated set from every directly-depended provider into a consuming app's
19
+ `.claude/agents/`, plus a delegation rule so the host's agent actually uses them.
20
+
21
+ ### The model
22
+
23
+ - **Providers define locals.** A gem (or the app) calls `TheLocal.register` to
24
+ declare its locals; each `c.agent` becomes one local. The register block runs
25
+ only at build time, behind a soft `require "the_local"` guard so the gem still
26
+ works standalone.
27
+ - **`the_local:build` renders committed `.md`.** The provider runs
28
+ `rake the_local:build`; `TheLocal::Builder` writes each agent to its
29
+ `source_path` under `lib/<gem>/the_local/agents/<prefix>-<name>.md`. The
30
+ rendered files are committed to the provider's repo. **These committed files
31
+ are the contract** — they are what a host reads. The register block + `guide.md`
32
+ are the source of truth they're built from.
33
+ - **Install discovers committed `.md` on disk.** In a host, install reads each
34
+ direct dependency's committed `lib/**/the_local/agents/*.md` straight from its
35
+ gem path and copies them into `.claude/agents/` byte-for-byte — no provider
36
+ code is loaded and no register block runs in the host. Output depends only on
37
+ the provider gem version (a true carbon copy across every app), a provider needs
38
+ no install-time wiring to be found, and a fragile gem can't crash the install.
39
+ - **The delegation trigger.** Install also writes a registry-generated block into
40
+ the host's `CLAUDE.md`/`AGENTS.md` telling the host agent to delegate to these
41
+ locals. This is what makes delegation actually happen.
42
+ - **Direct-dependency scope.** Only the host's *direct* dependencies contribute
43
+ locals; transitive provider gems are filtered out, so a host gets exactly the
44
+ experts for the gems it chose.
45
+
46
+ ### Install (in any gem or app)
47
+
48
+ 1. Add the gem to the host's `Gemfile` (until it is on RubyGems, use a git
49
+ source: `gem "the_local", github: "DYB-Development/the_local"`), then
50
+ `bundle install`.
51
+ 2. Run `bundle exec the_local install`. This syncs every direct provider's
52
+ committed locals into `.claude/agents/` and writes the delegation trigger
53
+ into `CLAUDE.md`/`AGENTS.md`. It needs no Rails — a plain gem installs the
54
+ same way an app does.
55
+ 3. Re-run `bundle exec the_local install` after any bundle change (a provider
56
+ added, removed, or upgraded) to bring the host's locals back in sync. The
57
+ shell can automate this; the gem only exposes the command.
58
+
59
+ Rails apps can equivalently run `bin/rails g the_local:install` and
60
+ `the_local:refresh`; a gem that already wires `require "the_local/rake"` into
61
+ its Rakefile also gets `rake the_local:install`. All three share one engine.
62
+
63
+ ### Author a provider (turn a gem into a provider)
64
+
65
+ 1. Run `bin/rails g the_local:provider <gem_name>` (pass `--scope`,
66
+ `--prefix`, `--worker` as needed). It scaffolds `lib/<gem>/reference.rb`, a
67
+ `lib/<gem>/reference/guide.md`, and a `lib/<gem>/the_local.rb` companion that
68
+ registers the standard interface; hooks `the_local:build` into the `Rakefile`;
69
+ requires the companion from the gem entrypoint; and builds the committed
70
+ `.md` for review.
71
+ 2. Write `guide.md` in this format — it is the single source of truth and is
72
+ embedded verbatim into every local. Document *your own* gem only: what it
73
+ does, how to install it, the conventions to enforce. Name companion gems but
74
+ do not explain their internals.
75
+ 3. Tailor the register block bodies and `scope` to your gem; the standard
76
+ interface is `info` (read-only explainer), `install` (sets the gem up in a
77
+ host), and a domain worker (`develop` for libraries, `operate` for CLIs).
78
+ 4. Run `rake the_local:build`, then **commit and ship**
79
+ `lib/<gem>/the_local/agents/*.md` (they must be in the gemspec's `files`).
80
+ This is the whole contract: a host discovers your locals by reading these
81
+ committed files from your gem on disk — it never loads your gem or runs your
82
+ register block — so if they aren't committed and shipped, you contribute
83
+ nothing, and if they are, you contribute everything. A drift test asserting
84
+ each committed file equals its `agent.to_markdown` keeps the artifact honest.
85
+
86
+ ### TheLocal.register
87
+
88
+ ```ruby
89
+ TheLocal.register("my_gem", prefix: "my_gem", scope: "one-line domain phrase",
90
+ agents_dir: File.expand_path("the_local/agents", __dir__)) do |c|
91
+ c.agent "info",
92
+ description: "Use to learn what my_gem offers.",
93
+ tools: "Read",
94
+ body: "You explain my_gem, answering only from the reference. You make no changes.",
95
+ knowledge: MyGem::Reference.content
96
+ end
97
+ ```
98
+
99
+ - `gem_name` (first arg) filters to a host's direct dependencies.
100
+ - `prefix` is the agent filename namespace; defaults to the gem name.
101
+ - `scope` is a one-line domain phrase used to generate the delegation trigger.
102
+ - `agents_dir` is the absolute path to the committed `.md` files; each agent
103
+ records its `source_path` there so the installer can copy it verbatim.
104
+
105
+ ### Conventions
106
+
107
+ - The register block lives behind `begin require "the_local" … rescue LoadError`
108
+ so the gem still works when the_local is absent.
109
+ - `guide.md` documents the providing gem only and stays the single source of
110
+ truth; never let a rendered `.md` drift from `agent.to_markdown`.
111
+ - Commit the rendered `.md`; never render in the host at install time.