the_local 0.3.0 → 0.4.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/CLAUDE.md +117 -0
  4. data/MIGRATING.md +478 -0
  5. data/PROVIDERS.md +62 -123
  6. data/README.md +20 -14
  7. data/lib/generators/the_local/provider_generator.rb +15 -90
  8. data/lib/the_local/agent.rb +3 -2
  9. data/lib/the_local/author.rb +43 -0
  10. data/lib/the_local/creators/develop.md +81 -0
  11. data/lib/the_local/creators/info.md +67 -0
  12. data/lib/the_local/creators/install.md +79 -0
  13. data/lib/the_local/disk_providers.rb +18 -10
  14. data/lib/the_local/format.rb +27 -0
  15. data/lib/the_local/front_matter.rb +26 -0
  16. data/lib/the_local/installer.rb +2 -2
  17. data/lib/the_local/interface.rb +26 -0
  18. data/lib/the_local/provider_check.rb +92 -0
  19. data/lib/the_local/rake.rb +14 -9
  20. data/lib/the_local/registry.rb +2 -30
  21. data/lib/the_local/trigger_writer.rb +1 -1
  22. data/lib/the_local/version.rb +1 -1
  23. data/lib/the_local.rb +1 -20
  24. data/the_local/agents/the_local-develop.md +48 -0
  25. data/the_local/agents/the_local-info.md +40 -0
  26. data/the_local/agents/the_local-install.md +49 -0
  27. data/the_local/interface.yml +21 -0
  28. metadata +16 -12
  29. data/lib/generators/the_local/templates/guide.md.tt +0 -41
  30. data/lib/generators/the_local/templates/reference.rb.tt +0 -17
  31. data/lib/generators/the_local/templates/the_local.rb.tt +0 -49
  32. data/lib/the_local/builder.rb +0 -57
  33. data/lib/the_local/reference/guide.md +0 -179
  34. data/lib/the_local/reference.rb +0 -16
  35. data/lib/the_local/the_local/agents/the_local-develop.md +0 -187
  36. data/lib/the_local/the_local/agents/the_local-info.md +0 -187
  37. data/lib/the_local/the_local/agents/the_local-install.md +0 -187
  38. data/lib/the_local/the_local.rb +0 -59
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: the_local-author-install
3
+ description: Use to author (or refresh) a gem's `install` local — the step-by-step guide to hooking the gem into a consumer. Reads the gem's declared interface and current code, and writes the_local/agents/<gem>-install.md. Run inside the provider gem.
4
+ tools: Read, Grep, Write
5
+ ---
6
+
7
+ You author ONE file: `the_local/agents/<gem>-install.md`, where `<gem>` is the
8
+ basename of the single `*.gemspec` in the current directory.
9
+
10
+ ## Your assignment is the manifest
11
+
12
+ Read `the_local/interface.yml` first. The entry points listed under `install:` are
13
+ your assignment — document those, all of them, and nothing else. Entry points
14
+ listed under `develop:` belong to another local; documenting one here is an error
15
+ the check will reject.
16
+
17
+ You do not decide what the gem's public surface is. That decision is already made
18
+ in the manifest. If an entry point there looks wrong or missing, say so in your
19
+ final message — do not silently document something else.
20
+
21
+ Copy the manifest's `scope:` line verbatim into your front matter.
22
+
23
+ ## Verify against the code, then hide it
24
+
25
+ Read the files under `sources:` — the generators, the initializer templates, the
26
+ migrations — so every command and every file it writes is exact. A README's
27
+ install section states intent and may be stale; the code wins.
28
+
29
+ Then hide all of it. Your reader is wiring this gem up from your file alone and
30
+ will never open its source. No paths into the gem's own `lib/`, no private
31
+ classes, no instruction to go read the gem. Name only the commands the developer
32
+ runs and the host files those commands create or edit.
33
+
34
+ ## What this local is for
35
+
36
+ Hooking the systems together so they work: adding the gem to a consumer and
37
+ configuring it. Not how to build with it — that is the develop local's.
38
+
39
+ Write it as ordered steps, top to bottom. Where setup takes a real decision — a
40
+ choice between install paths, a value with no safe default, a companion gem that
41
+ may or may not be wanted — state the choice and tell the local to ask the
42
+ developer rather than pick. Surfacing that question is part of the job.
43
+
44
+ Cut every sentence that is not a step or a fact needed to complete one. No
45
+ history, no rationale, no asides.
46
+
47
+ ## The shape
48
+
49
+ ```
50
+ ---
51
+ name: <gem>-install
52
+ description: Use to hook <gem> into a project — <the declared setup tasks, named>.
53
+ tools: Bash, Read, Edit
54
+ scope: <copied verbatim from the manifest>
55
+ ---
56
+
57
+ <one or two sentences: this local follows these steps exactly and invents none>
58
+
59
+ ## What <gem> is
60
+ <one line: what it is, and when to hook it in>
61
+
62
+ ## Interface
63
+ <one bullet per declared entry point, each leading with the command in backticks,
64
+ then one line on what it does>
65
+
66
+ ## How to use it
67
+ <numbered steps, in order: the command to run, the host files it touches, and any
68
+ decision to put to the developer>
69
+
70
+ ## Conventions
71
+ <post-install checks, re-sync rules, and what is out of scope>
72
+ ```
73
+
74
+ ## Before you finish
75
+
76
+ - Every entry point under `install:` appears in your Interface as its own bullet.
77
+ - Nothing declared under `develop:` appears anywhere in your file.
78
+ - Re-read it with no access to the source. Could someone wire the gem up correctly
79
+ from these steps alone?
@@ -1,32 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "agent"
4
+ require_relative "front_matter"
5
+ require_relative "registry"
6
+
3
7
  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
8
  module DiskProviders
11
- AGENTS_GLOB = File.join("lib", "**", "the_local", "agents", "*.md")
9
+ AGENTS_GLOB = File.join("the_local", "agents", "*.md")
10
+ LEGACY_AGENTS_GLOB = File.join("lib", "**", "the_local", "agents", "*.md")
12
11
 
13
12
  def self.load(registry:, specs:)
14
13
  specs.each { |spec| register(registry, spec) }
15
14
  end
16
15
 
16
+ def self.agent_files(path)
17
+ rendered = Dir.glob(File.join(path, AGENTS_GLOB))
18
+ rendered.any? ? rendered : Dir.glob(File.join(path, LEGACY_AGENTS_GLOB))
19
+ end
20
+
17
21
  def self.register(registry, spec)
18
- files = Dir.glob(File.join(spec[:path], AGENTS_GLOB))
22
+ files = agent_files(spec[:path])
19
23
  return if files.empty?
20
24
 
21
25
  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))
26
+ representative = agents.find(&:scope) || agents.first
27
+ registry.add_provider(
28
+ Provider.new(gem_name: spec[:name], prefix: representative.prefix, scope: representative.scope)
29
+ )
23
30
  agents.each { |agent| registry.add(agent) }
24
31
  end
25
32
 
26
33
  def self.agent_from(gem_name, file)
27
34
  prefix, _, name = File.basename(file, ".md").rpartition("-")
28
35
  Agent.new(gem_name: gem_name, prefix: prefix, name: name,
29
- description: nil, tools: nil, body: nil, knowledge: nil, source_path: file)
36
+ description: nil, tools: nil, body: nil, knowledge: nil, source_path: file,
37
+ scope: FrontMatter.new(File.read(file)).scope)
30
38
  end
31
39
  end
32
40
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheLocal
4
+ module Format
5
+ FRONT_MATTER_KEYS = %w[name description tools scope].freeze
6
+
7
+ SECTIONS = ["## What", "## Interface", "## How to use it", "## Conventions"].freeze
8
+
9
+ def self.section(markdown, heading)
10
+ markdown[/^#{Regexp.escape(heading)}$\n(.*?)(?=^## |\z)/m, 1].to_s
11
+ end
12
+
13
+ def self.problems(markdown)
14
+ missing_keys(markdown) + missing_sections(markdown)
15
+ end
16
+
17
+ def self.missing_keys(markdown)
18
+ FRONT_MATTER_KEYS.reject { |key| markdown.match?(/^#{key}:/) }
19
+ .map { |key| "missing key: #{key}" }
20
+ end
21
+
22
+ def self.missing_sections(markdown)
23
+ SECTIONS.reject { |section| markdown.include?(section) }
24
+ .map { |section| "missing section: #{section}" }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module TheLocal
6
+ class FrontMatter
7
+ BLOCK = /\A---\n.*?\n---\n/m
8
+
9
+ def initialize(text)
10
+ @text = text
11
+ end
12
+
13
+ def scope
14
+ parsed["scope"]
15
+ end
16
+
17
+ private
18
+
19
+ def parsed
20
+ matched = @text[BLOCK]
21
+ return {} unless matched
22
+
23
+ YAML.safe_load(matched) || {}
24
+ end
25
+ end
26
+ end
@@ -34,8 +34,8 @@ module TheLocal
34
34
  def ensure_committed!(agent)
35
35
  return if agent.source_path && File.exist?(agent.source_path)
36
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/."
37
+ raise Error, "the_local: #{agent.gem_name} is missing the committed file for #{agent.qualified_name}. " \
38
+ "Run `rake the_local:author` in #{agent.gem_name} and commit its the_local/agents/."
39
39
  end
40
40
  end
41
41
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module TheLocal
6
+ class Interface
7
+ FILE = File.join("the_local", "interface.yml")
8
+
9
+ def self.at(gem_root)
10
+ path = File.join(gem_root, FILE)
11
+ new(File.exist?(path) ? YAML.safe_load_file(path) : nil)
12
+ end
13
+
14
+ def initialize(declaration)
15
+ @declaration = declaration || {}
16
+ end
17
+
18
+ def scope
19
+ @declaration["scope"]
20
+ end
21
+
22
+ def entry_points_for(facet)
23
+ @declaration[facet] || []
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "format"
4
+ require_relative "front_matter"
5
+ require_relative "interface"
6
+
7
+ module TheLocal
8
+ class ProviderCheck
9
+ LOCALS = %w[info install develop].freeze
10
+
11
+ def initialize(gem_root)
12
+ @gem_root = gem_root
13
+ end
14
+
15
+ def problems
16
+ format_problems + scope_problems + interface_problems
17
+ end
18
+
19
+ private
20
+
21
+ def format_problems
22
+ existing_files.flat_map do |file|
23
+ Format.problems(File.read(file)).map { |problem| "#{File.basename(file)}: #{problem}" }
24
+ end
25
+ end
26
+
27
+ def scope_problems
28
+ return disagreement_problems if declared.scope.nil?
29
+
30
+ existing_files.reject { |file| FrontMatter.new(File.read(file)).scope == declared.scope }
31
+ .map { |file| "#{File.basename(file)}: scope does not match the manifest" }
32
+ end
33
+
34
+ def disagreement_problems
35
+ scopes = existing_files.map { |file| FrontMatter.new(File.read(file)).scope }.compact.uniq
36
+ scopes.size > 1 ? ["the locals' scope lines disagree"] : []
37
+ end
38
+
39
+ def interface_problems
40
+ return [] if LOCALS.all? { |local| declared.entry_points_for(local).empty? }
41
+
42
+ LOCALS.select { |local| File.exist?(file_for(local)) }
43
+ .flat_map { |local| undocumented(local) + misdocumented(local) }
44
+ end
45
+
46
+ def undocumented(local)
47
+ declared.entry_points_for(local)
48
+ .reject { |entry_point| documented(local).any? { |span| span.include?(entry_point) } }
49
+ .map { |entry_point| "#{name_of(local)}: undocumented entry point: #{entry_point}" }
50
+ end
51
+
52
+ def misdocumented(local)
53
+ documented(local).reject { |span| declared_in(span) == local }
54
+ .map { |span| "#{name_of(local)}: #{misplacement(span)}" }
55
+ end
56
+
57
+ def misplacement(span)
58
+ owner = declared_in(span)
59
+ owner ? "entry point declared for #{owner}: #{span}" : "undeclared entry point: #{span}"
60
+ end
61
+
62
+ def declared_in(span)
63
+ LOCALS.find do |local|
64
+ declared.entry_points_for(local).any? { |entry_point| span.include?(entry_point) }
65
+ end
66
+ end
67
+
68
+ def documented(local)
69
+ Format.section(File.read(file_for(local)), "## Interface").scan(/^\s*-\s+`([^`\n]+)`/).flatten
70
+ end
71
+
72
+ def declared
73
+ @declared ||= Interface.at(@gem_root)
74
+ end
75
+
76
+ def existing_files
77
+ LOCALS.map { |local| file_for(local) }.select { |file| File.exist?(file) }
78
+ end
79
+
80
+ def file_for(local)
81
+ File.join(@gem_root, "the_local", "agents", name_of(local))
82
+ end
83
+
84
+ def name_of(local)
85
+ "#{gem_name}-#{local}.md"
86
+ end
87
+
88
+ def gem_name
89
+ File.basename(Dir.glob(File.join(@gem_root, "*.gemspec")).first.to_s, ".gemspec")
90
+ end
91
+ end
92
+ end
@@ -2,17 +2,22 @@
2
2
 
3
3
  require "rake"
4
4
  require "the_local"
5
- require "the_local/builder"
5
+ require "the_local/author"
6
+ require "the_local/provider_check"
6
7
 
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
8
  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, validate: true).call
15
- puts "the_local: built #{written.length} agent file(s)"
9
+ desc "Author this provider's committed locals from its current source"
10
+ task :author do
11
+ TheLocal::Author.new(gem_root: Dir.pwd).call
12
+ puts "the_local: authored locals; review the_local/agents/ and run `rake the_local:check`"
13
+ end
14
+
15
+ desc "Check this provider's committed locals hold the fixed format"
16
+ task :check do
17
+ problems = TheLocal::ProviderCheck.new(Dir.pwd).problems
18
+ raise TheLocal::Error, "the_local: malformed local(s):\n- #{problems.join("\n- ")}" if problems.any?
19
+
20
+ puts "the_local: locals hold the format"
16
21
  end
17
22
 
18
23
  desc "Install/refresh this project's locals from the current bundle into .claude/agents/"
@@ -5,9 +5,8 @@ module TheLocal
5
5
  # one-line scope used to generate the delegation trigger.
6
6
  Provider = Data.define(:gem_name, :prefix, :scope)
7
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.
8
+ # Accumulates the providers and agents discovered from disk. Installer and
9
+ # TriggerWriter read this to write .claude/agents/ and the delegation trigger.
11
10
  class Registry
12
11
  def initialize
13
12
  @agents = []
@@ -29,31 +28,4 @@ module TheLocal
29
28
  @providers.clear
30
29
  end
31
30
  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
31
  end
@@ -19,7 +19,7 @@ module TheLocal
19
19
  def call
20
20
  path = File.join(@destination, @filename)
21
21
  existing = File.exist?(path) ? File.read(path) : ""
22
- File.write(path, "#{merge(existing)}\n")
22
+ File.write(path, "#{merge(existing).chomp}\n")
23
23
  end
24
24
 
25
25
  def rule
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TheLocal
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
data/lib/the_local.rb CHANGED
@@ -11,7 +11,7 @@ require_relative "the_local/refresh"
11
11
  require_relative "the_local/disk_providers"
12
12
 
13
13
  # Resident Claude Code expert subagents ("locals"), contributed by the gems and
14
- # app that register with it and installed into a consuming app's .claude/agents/.
14
+ # app a host depends on and installed into the host's .claude/agents/.
15
15
  module TheLocal
16
16
  class Error < StandardError; end
17
17
 
@@ -20,23 +20,6 @@ module TheLocal
20
20
  @registry ||= Registry.new
21
21
  end
22
22
 
23
- # Providers (gems or the app) call this at load time to contribute their
24
- # agents. The first argument is the providing gem's name (used to filter to a
25
- # host's direct dependencies); +prefix+ is the agent filename namespace and
26
- # defaults to the gem name; +scope+ is a one-line phrase describing the
27
- # provider's domain, used to generate the delegation trigger. +agents_dir+
28
- # is the absolute path to the provider's committed, pre-rendered .md files
29
- # (e.g. File.expand_path("the_local/agents", __dir__)); when given, each
30
- # agent records its source_path there for the host installer to copy:
31
- #
32
- # TheLocal.register("keystone_ui", prefix: "keystone", scope: "UI work") do |c|
33
- # c.agent "scaffold", description: "…", tools: "…", body: "…", knowledge: "…"
34
- # end
35
- def register(gem_name, prefix: gem_name, scope: nil, agents_dir: nil)
36
- registry.add_provider(Provider.new(gem_name: gem_name, prefix: prefix, scope: scope))
37
- yield Collector.new(gem_name, prefix, registry, agents_dir: agents_dir)
38
- end
39
-
40
23
  def reset!
41
24
  registry.clear
42
25
  end
@@ -46,5 +29,3 @@ end
46
29
  # In a Rails host, expose the the_local:refresh rake task. Skipped outside Rails
47
30
  # so the gem core stays Rails-free.
48
31
  require_relative "the_local/railtie" if defined?(Rails::Railtie)
49
-
50
- require_relative "the_local/the_local"
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: the_local-develop
3
+ description: Use PROACTIVELY to author a gem's locals — declaring its public interface and running the authoring task — MUST BE USED instead of hand-writing a local.
4
+ tools: Read, Write, Edit, Grep
5
+ scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
6
+ ---
7
+
8
+ You author a gem's locals by declaring its interface and running the authoring
9
+ task. You do not hand-write locals and you never read the_local's source. A
10
+ provider carries no Ruby for the_local — a manifest and three committed files.
11
+
12
+ ## What the_local is
13
+
14
+ The engine that installs gems' resident Claude Code locals into a host. Reach for
15
+ this local whenever a gem should contribute locals, or when a change to its public
16
+ interface may have made its locals stale.
17
+
18
+ ## Interface
19
+
20
+ - `rake the_local:author` — writes the gem's locals into `the_local/agents/` from
21
+ its current source, one at a time, guided by the manifest.
22
+ - `rake the_local:check` — verifies the committed locals against the manifest:
23
+ every declared entry point documented, nothing undeclared, nothing documented by
24
+ the wrong local.
25
+
26
+ ## How to use it
27
+
28
+ 1. Write `the_local/interface.yml` with the developer. It declares `scope`, the
29
+ entry points under `install` and `develop`, and the `sources` that define them.
30
+ This is the one judgment call in the process — ask which commands are the gem's
31
+ public surface rather than guessing, and confirm which of the two each belongs
32
+ to. An entry point may appear under exactly one.
33
+ 2. Run `rake the_local:author`. It writes `the_local/agents/<gem>-{info,install,develop}.md`.
34
+ 3. Run `rake the_local:check` and fix what it reports.
35
+ 4. Commit `the_local/`. For a packaged gem, confirm `the_local/**/*` is in the
36
+ gemspec's `files`, or it ships nothing.
37
+ 5. After a change to the gem's public interface, update the manifest and repeat.
38
+ An internal-only change needs nothing.
39
+
40
+ ## Conventions
41
+
42
+ - The manifest is the contract. Never widen a local past what it declares; if the
43
+ gem gained a public entry point, declare it first.
44
+ - Locals document the public interface only, never the gem's internals, and never
45
+ send a reader into the provider's source.
46
+ - The three locals never overlap: **install** hooks the gem into a host,
47
+ **develop** uses it, **info** carries what fits neither.
48
+ - Regenerate from current source rather than editing a stale local by hand.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: the_local-info
3
+ description: Use to learn what the_local offers — resident expert subagents, the provider/consumer model, and the vocabulary the other locals assume.
4
+ tools: Read
5
+ scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
6
+ ---
7
+
8
+ You explain what the_local does, answering only from this reference. You make no
9
+ changes, and you never read the_local's source.
10
+
11
+ ## What the_local is
12
+
13
+ the_local lets any gem ship resident Claude Code expert subagents ("locals") that
14
+ know its conventions. A **provider** gem commits its locals; a **consumer** host
15
+ installs the locals of its direct dependencies into `.claude/agents/`, plus a
16
+ delegation rule so the host's agent uses them.
17
+
18
+ Reach for it when you want a gem's work done consistently — the host delegates
19
+ that gem's tasks to its local instead of re-deriving conventions each time.
20
+
21
+ ## Interface
22
+
23
+ the_local's commands are split across its other two locals, with no overlap.
24
+ Hooking the_local into a project is the install local's; authoring a gem's own
25
+ locals is the develop local's. Route to those rather than answering here.
26
+
27
+ ## How to use it
28
+
29
+ Decide which side you are on. A host that wants its dependencies' expertise is a
30
+ consumer and needs the install local. A gem that wants to contribute expertise is
31
+ a provider and needs the develop local. A gem can be both.
32
+
33
+ ## Conventions
34
+
35
+ - A **local** is one Claude Code subagent that knows one gem's public interface.
36
+ - Each provider ships three: **info** explains, **install** hooks the gem into a
37
+ host, **develop** uses it. A command belongs to exactly one of them.
38
+ - The committed `the_local/agents/*.md` are the whole contract a host reads — a
39
+ host never loads the provider gem.
40
+ - Only a host's **direct** dependencies contribute locals.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: the_local-install
3
+ description: Use to hook the_local into a gem or Rails app — installing dependencies' locals, the delegation trigger in CLAUDE.md, and the provider rake tasks.
4
+ tools: Bash, Read, Edit
5
+ scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
6
+ ---
7
+
8
+ You hook the_local into the host by following these steps exactly, in order. You
9
+ do not invent steps, and you never read the_local's source.
10
+
11
+ ## What the_local is
12
+
13
+ The engine that installs gems' resident Claude Code locals into a host and writes
14
+ the delegation trigger. Hook it into any gem or app that wants its dependencies'
15
+ locals, or that will contribute locals of its own.
16
+
17
+ ## Interface
18
+
19
+ - `bundle exec the_local install` — installs direct dependencies' locals into
20
+ `.claude/agents/` and writes the trigger. Works anywhere; no Rails required.
21
+ - `bin/rails g the_local:install` — the Rails equivalent of the above.
22
+ - `rake the_local:refresh` — re-syncs a Rails host after a bundle change.
23
+ - `rake the_local:install` — re-syncs a non-Rails host after a bundle change.
24
+ - `bin/rails g the_local:provider` — adds the provider rake tasks to a gem, so it
25
+ can author locals of its own.
26
+
27
+ ## How to use it
28
+
29
+ 1. Add `gem "the_local"` to the host's `Gemfile` and run `bundle install`.
30
+ 2. Install the locals. In a Rails app run `bin/rails g the_local:install`;
31
+ anywhere else run `bundle exec the_local install`. Either copies every direct
32
+ dependency's committed locals into `.claude/agents/` and writes the delegation
33
+ block into `CLAUDE.md`/`AGENTS.md`.
34
+ 3. Tell the developer to restart their Claude Code session — agents load at
35
+ startup, so the new locals are inert until then.
36
+ 4. Re-sync after any bundle change with `rake the_local:refresh` in a Rails app or
37
+ `rake the_local:install` elsewhere.
38
+ 5. Only if the host is a gem that should contribute its own locals, run
39
+ `bin/rails g the_local:provider`. Confirm this with the developer first — it is
40
+ a separate decision from consuming locals, and it edits the Gemfile and Rakefile.
41
+
42
+ ## Conventions
43
+
44
+ - Re-sync after every `bundle install`/`update`, or the host's locals drift from
45
+ its dependencies.
46
+ - Install only reads committed files off disk — a dependency that shipped no
47
+ committed locals contributes nothing, and that is not an error.
48
+ - Hooking up is all this local does. Authoring a gem's own locals is the develop
49
+ local's job.
@@ -0,0 +1,21 @@
1
+ scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
2
+
3
+ install:
4
+ - bundle exec the_local install
5
+ - bin/rails g the_local:install
6
+ - rake the_local:refresh
7
+ - rake the_local:install
8
+ - bin/rails g the_local:provider
9
+
10
+ develop:
11
+ - rake the_local:author
12
+ - rake the_local:check
13
+
14
+ sources:
15
+ - lib/the_local/cli.rb
16
+ - lib/the_local/rake.rb
17
+ - lib/the_local/tasks/the_local.rake
18
+ - lib/generators/the_local/install_generator.rb
19
+ - lib/generators/the_local/provider_generator.rb
20
+ - lib/the_local/author.rb
21
+ - lib/the_local/provider_check.rb