the_local 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/{lib/the_local/process_rules/develop_process_rules.md → CLAUDE.md} +20 -0
- data/MIGRATING.md +478 -0
- data/PROVIDERS.md +62 -123
- data/README.md +20 -14
- data/lib/generators/the_local/provider_generator.rb +15 -90
- data/lib/the_local/agent.rb +3 -2
- data/lib/the_local/author.rb +43 -0
- data/lib/the_local/creators/develop.md +81 -0
- data/lib/the_local/creators/info.md +67 -0
- data/lib/the_local/creators/install.md +79 -0
- data/lib/the_local/disk_providers.rb +18 -10
- data/lib/the_local/format.rb +27 -0
- data/lib/the_local/front_matter.rb +26 -0
- data/lib/the_local/installer.rb +2 -2
- data/lib/the_local/interface.rb +26 -0
- data/lib/the_local/provider_check.rb +92 -0
- data/lib/the_local/rake.rb +14 -9
- data/lib/the_local/refresh.rb +6 -1
- data/lib/the_local/registry.rb +2 -30
- data/lib/the_local/sync.rb +0 -1
- data/lib/the_local/version.rb +1 -1
- data/lib/the_local.rb +1 -21
- data/the_local/agents/the_local-develop.md +48 -0
- data/the_local/agents/the_local-info.md +40 -0
- data/the_local/agents/the_local-install.md +49 -0
- data/the_local/interface.yml +21 -0
- metadata +16 -15
- data/lib/generators/the_local/templates/guide.md.tt +0 -41
- data/lib/generators/the_local/templates/reference.rb.tt +0 -17
- data/lib/generators/the_local/templates/the_local.rb.tt +0 -49
- data/lib/the_local/builder.rb +0 -57
- data/lib/the_local/process_doc_writer.rb +0 -48
- data/lib/the_local/process_rules.rb +0 -17
- data/lib/the_local/reference/guide.md +0 -179
- data/lib/the_local/reference.rb +0 -16
- data/lib/the_local/the_local/agents/the_local-develop.md +0 -187
- data/lib/the_local/the_local/agents/the_local-info.md +0 -187
- data/lib/the_local/the_local/agents/the_local-install.md +0 -187
- data/lib/the_local/the_local.rb +0 -59
data/PROVIDERS.md
CHANGED
|
@@ -6,134 +6,73 @@ of a project's direct dependencies into `.claude/agents/` — by reading each
|
|
|
6
6
|
dependency's committed `.md` straight from its gem path on disk. This document
|
|
7
7
|
covers the **provider** side: how a gem contributes those locals.
|
|
8
8
|
|
|
9
|
-
A provider
|
|
10
|
-
|
|
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 |
|
|
9
|
+
A provider commits a manifest, `the_local/interface.yml`, and the three locals
|
|
10
|
+
authored from it at `the_local/agents/<gem>-{info,install,develop}.md`. It ships
|
|
11
|
+
no Ruby of its own.
|
|
41
12
|
|
|
42
|
-
|
|
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.
|
|
13
|
+
## Declare the interface, author the locals
|
|
46
14
|
|
|
47
|
-
|
|
15
|
+
**The manifest is the one judgment call, and a human makes it.** It names the
|
|
16
|
+
gem's public entry points and assigns each to the local that owns it:
|
|
48
17
|
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
```yaml
|
|
19
|
+
scope: <one line: the user-visible work this gem owns>
|
|
51
20
|
|
|
52
|
-
|
|
53
|
-
bin/rails g
|
|
54
|
-
--scope "one-line phrase describing the gem's domain" \
|
|
55
|
-
[--prefix <filename-namespace>] \
|
|
56
|
-
[--worker develop|operate]
|
|
57
|
-
```
|
|
21
|
+
install:
|
|
22
|
+
- bin/rails g demo:install
|
|
58
23
|
|
|
59
|
-
|
|
24
|
+
develop:
|
|
25
|
+
- Demo.emit
|
|
60
26
|
|
|
27
|
+
sources:
|
|
28
|
+
- lib/generators/demo/install_generator.rb
|
|
29
|
+
- lib/demo/emitter.rb
|
|
61
30
|
```
|
|
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
31
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
## Adopting it
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
agents_dir: File.expand_path("the_local/agents", __dir__)) do |c|
|
|
119
|
-
# c.agent "info", …
|
|
120
|
-
end
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
```ruby
|
|
124
|
-
begin
|
|
125
|
-
require "the_local"
|
|
126
|
-
<Gem>::Companion.register!
|
|
127
|
-
rescue LoadError
|
|
128
|
-
# the_local not installed — <gem> works standalone.
|
|
129
|
-
end
|
|
130
|
-
```
|
|
131
|
-
4. `require_relative "<gem>/the_local"` from the gem's entrypoint (so your own
|
|
132
|
-
`the_local:build` and standalone use load the register block — a host never
|
|
133
|
-
needs it), and add `gem "the_local"` to the Gemfile (dev/test — an optional
|
|
134
|
-
companion, not a hard dependency).
|
|
135
|
-
5. Add `require "the_local/rake"` to the `Rakefile`, then build, commit, and
|
|
136
|
-
**ship** the rendered locals — `rake the_local:build && git add
|
|
137
|
-
lib/<gem>/the_local/agents`, and make sure they're in the gemspec's `files`.
|
|
138
|
-
These committed bytes are the whole contract — what the host reads from disk;
|
|
139
|
-
rebuild and recommit on every change.
|
|
32
|
+
`rake the_local:author` then runs the_local's creators against that declaration
|
|
33
|
+
and the files under `sources:`, writing the three locals. The creators no longer
|
|
34
|
+
guess at what matters — they document what you declared, verified against the
|
|
35
|
+
code as it is now rather than against a README that may be stale.
|
|
36
|
+
|
|
37
|
+
`rake the_local:check` enforces the contract in both directions: every declared
|
|
38
|
+
entry point is documented by its own local, nothing undeclared appears anywhere,
|
|
39
|
+
and nothing appears in a local it wasn't declared for.
|
|
40
|
+
|
|
41
|
+
## What each local is for
|
|
42
|
+
|
|
43
|
+
The three never overlap. One entry point, one local.
|
|
44
|
+
|
|
45
|
+
| Local | Purpose | Tools |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| **`install`** | Hooking the gem into a consumer — the exact setup steps. | `Bash, Read, Edit` |
|
|
48
|
+
| **`develop`** | Using the gem — calling its entry points from consuming code. | `Read, Write, Edit, Grep` |
|
|
49
|
+
| **`info`** | Read-only catchall: what the gem is, and which of the other two to reach for. | `Read` |
|
|
50
|
+
|
|
51
|
+
They are step-by-step implementation guides, kept as short as the job allows.
|
|
52
|
+
Where a step takes a real decision, the local surfaces the question to the
|
|
53
|
+
developer rather than choosing. A local never sends its reader into the
|
|
54
|
+
provider's source.
|
|
55
|
+
|
|
56
|
+
## The committed files are the contract
|
|
57
|
+
|
|
58
|
+
The host install **reads them straight from your gem's path on disk and copies
|
|
59
|
+
them verbatim** into `.claude/agents/` — no provider code is loaded in the host.
|
|
60
|
+
If they aren't committed (and, for a packaged gem, in the gemspec's `files`), the
|
|
61
|
+
gem contributes nothing.
|
|
62
|
+
|
|
63
|
+
## Adopting it
|
|
64
|
+
|
|
65
|
+
1. **Wire the tooling.** In a Rails-engine gem, run `bin/rails g the_local:provider`;
|
|
66
|
+
it adds `gem "the_local"` to the Gemfile and `require "the_local/rake"` to the
|
|
67
|
+
Rakefile. A plain gem does those two edits by hand.
|
|
68
|
+
2. **Declare the interface.** Write `the_local/interface.yml`. Authoring refuses
|
|
69
|
+
to run without it.
|
|
70
|
+
3. **Author.** Run `rake the_local:author`.
|
|
71
|
+
4. **Check and commit.** Fix what `rake the_local:check` reports, then
|
|
72
|
+
`git add the_local`. For a packaged gem, make sure `the_local/**/*` is in the
|
|
73
|
+
gemspec's `files`.
|
|
74
|
+
5. **Keep it current.** When the gem's public interface changes, update the
|
|
75
|
+
manifest and re-author. An internal-only change needs nothing.
|
|
76
|
+
|
|
77
|
+
`the_local` is its own first provider and uses exactly this wiring;
|
|
78
|
+
`test/the_local/dogfood_test.rb` asserts its committed locals match its manifest.
|
data/README.md
CHANGED
|
@@ -24,10 +24,14 @@ change, point at the repo instead:
|
|
|
24
24
|
gem "the_local", github: "DYB-Development/the_local"
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The gem's core is Rails-free
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
The gem's core is Rails-free. `bundle exec the_local install` and the
|
|
28
|
+
`the_local:check`/`the_local:refresh` rake tasks work without Rails; a Rails host
|
|
29
|
+
can equivalently use the `bin/rails g the_local:install` / `the_local:provider`
|
|
30
|
+
generators.
|
|
31
|
+
|
|
32
|
+
Once installed, the locals themselves know the rest — the `the_local-info`,
|
|
33
|
+
`the_local-install`, and `the_local-develop` agents explain, set up, and drive
|
|
34
|
+
the_local, so this README stays short on purpose.
|
|
31
35
|
|
|
32
36
|
## Usage
|
|
33
37
|
|
|
@@ -37,21 +41,23 @@ There are two sides.
|
|
|
37
41
|
app's own) into `.claude/agents/`, and write the delegation trigger:
|
|
38
42
|
|
|
39
43
|
```bash
|
|
40
|
-
|
|
44
|
+
bundle exec the_local install
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
Install **copies each provider's committed `.md` verbatim** —
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
after a `bundle install`/`update` to re-sync.
|
|
47
|
+
Install **copies each provider's committed `.md` verbatim** — no provider code is
|
|
48
|
+
loaded — so every app on the same gem version gets a byte-identical local. Re-run
|
|
49
|
+
it (or `rake the_local:refresh`) after a `bundle install`/`update` to re-sync.
|
|
47
50
|
|
|
48
|
-
**Provider gem** — contribute the locals an app installs. A gem
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
**Provider gem** — contribute the locals an app installs. A gem declares its
|
|
52
|
+
public interface in `the_local/interface.yml` and commits the three locals
|
|
53
|
+
authored from it, `the_local/agents/<gem>-{info,install,develop}.md`; it ships no
|
|
54
|
+
Ruby. `rake the_local:author` writes those locals against the declaration and the
|
|
55
|
+
gem's current code, and `rake the_local:check` fails if they document more or less
|
|
56
|
+
than was declared — so the docs never drift from the source. Wire the tooling
|
|
57
|
+
with:
|
|
52
58
|
|
|
53
59
|
```bash
|
|
54
|
-
bin/rails g the_local:provider
|
|
60
|
+
bin/rails g the_local:provider
|
|
55
61
|
```
|
|
56
62
|
|
|
57
63
|
See [PROVIDERS.md](PROVIDERS.md) for the full provider guide, including the
|
|
@@ -1,47 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
-
require "the_local/builder"
|
|
5
4
|
|
|
6
5
|
module TheLocal
|
|
7
6
|
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
7
|
class ProviderGenerator < Rails::Generators::Base
|
|
16
8
|
source_root File.expand_path("templates", __dir__)
|
|
17
9
|
|
|
18
10
|
GEMFILE_LINE = %(gem "the_local")
|
|
19
11
|
RAKEFILE_REQUIRE = %(require "the_local/rake")
|
|
20
12
|
|
|
21
|
-
desc "
|
|
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)"
|
|
13
|
+
desc "Wire the_local provider tooling into this gem"
|
|
30
14
|
|
|
31
15
|
def relocate_to_gem_root
|
|
32
16
|
self.destination_root = gem_root
|
|
33
17
|
end
|
|
34
18
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
|
|
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"
|
|
19
|
+
def announce_next_step
|
|
20
|
+
say "Declare this gem's public interface in the_local/interface.yml, then " \
|
|
21
|
+
"run `rake the_local:author` to write its locals into the_local/agents/."
|
|
45
22
|
end
|
|
46
23
|
|
|
47
24
|
def add_to_gemfile
|
|
@@ -51,71 +28,35 @@ module TheLocal
|
|
|
51
28
|
return unless File.exist?(gemfile)
|
|
52
29
|
return if File.read(gemfile).include?(GEMFILE_LINE)
|
|
53
30
|
|
|
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"
|
|
31
|
+
append_to_file "Gemfile", "\n#{GEMFILE_LINE}\n"
|
|
57
32
|
end
|
|
58
33
|
|
|
59
|
-
def
|
|
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
|
|
34
|
+
def hook_check_task_into_rakefile
|
|
70
35
|
return unless File.exist?(File.join(destination_root, "Rakefile"))
|
|
71
36
|
return if File.read(File.join(destination_root, "Rakefile")).include?(RAKEFILE_REQUIRE)
|
|
72
37
|
|
|
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
|
|
38
|
+
append_to_file "Rakefile", "\n#{RAKEFILE_REQUIRE}\n"
|
|
88
39
|
end
|
|
89
40
|
|
|
90
41
|
private
|
|
91
42
|
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def prefix
|
|
97
|
-
options[:prefix] || gem_name
|
|
43
|
+
def gem_name
|
|
44
|
+
File.basename(gemspec.to_s, ".gemspec")
|
|
98
45
|
end
|
|
99
46
|
|
|
100
|
-
|
|
101
|
-
|
|
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]
|
|
47
|
+
def gem_root
|
|
48
|
+
gemspec ? File.dirname(gemspec) : destination_root
|
|
109
49
|
end
|
|
110
50
|
|
|
111
|
-
def
|
|
112
|
-
ascend_to_gemspec(destination_root)
|
|
51
|
+
def gemspec
|
|
52
|
+
ascend_to_gemspec(destination_root)
|
|
113
53
|
end
|
|
114
54
|
|
|
115
55
|
def ascend_to_gemspec(start)
|
|
116
56
|
dir = File.expand_path(start)
|
|
117
57
|
loop do
|
|
118
|
-
|
|
58
|
+
found = Dir.glob(File.join(dir, "*.gemspec")).first
|
|
59
|
+
return found if found
|
|
119
60
|
|
|
120
61
|
parent = File.dirname(dir)
|
|
121
62
|
return nil if parent == dir
|
|
@@ -123,22 +64,6 @@ module TheLocal
|
|
|
123
64
|
dir = parent
|
|
124
65
|
end
|
|
125
66
|
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
67
|
end
|
|
143
68
|
end
|
|
144
69
|
end
|
data/lib/the_local/agent.rb
CHANGED
|
@@ -11,8 +11,8 @@ module TheLocal
|
|
|
11
11
|
# +source_path+ is the absolute path to the provider's committed, pre-rendered
|
|
12
12
|
# .md file; the host installer copies it verbatim. It stays nil until a
|
|
13
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)
|
|
14
|
+
Agent = Data.define(:gem_name, :prefix, :name, :description, :tools, :body, :knowledge, :source_path, :scope) do
|
|
15
|
+
def initialize(source_path: nil, scope: nil, **args)
|
|
16
16
|
super
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -30,6 +30,7 @@ module TheLocal
|
|
|
30
30
|
name: #{qualified_name}
|
|
31
31
|
description: #{description}
|
|
32
32
|
tools: #{tools}
|
|
33
|
+
scope: #{scope}
|
|
33
34
|
---
|
|
34
35
|
|
|
35
36
|
#{body}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "interface"
|
|
4
|
+
|
|
5
|
+
module TheLocal
|
|
6
|
+
class Author
|
|
7
|
+
FACETS = %w[info install develop].freeze
|
|
8
|
+
CREATORS = File.expand_path("creators", __dir__)
|
|
9
|
+
|
|
10
|
+
def self.claude_command(prompt)
|
|
11
|
+
["claude", "-p", "--allowedTools", "Read,Grep,Write",
|
|
12
|
+
"--permission-mode", "acceptEdits", "--", prompt]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ClaudeRunner = lambda do |prompt, dir|
|
|
16
|
+
system(*claude_command(prompt), chdir: dir) ||
|
|
17
|
+
raise(Error, "the_local: the creator run failed in #{dir} (is the `claude` CLI installed?)")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(gem_root:, runner: ClaudeRunner)
|
|
21
|
+
@gem_root = gem_root
|
|
22
|
+
@runner = runner
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
require_declared_interface
|
|
27
|
+
FACETS.each { |facet| @runner.call(prompt_for(facet), @gem_root) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def prompt_for(facet)
|
|
31
|
+
File.read(File.join(CREATORS, "#{facet}.md"))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def require_declared_interface
|
|
37
|
+
return if File.exist?(File.join(@gem_root, Interface::FILE))
|
|
38
|
+
|
|
39
|
+
raise Error, "the_local: declare this gem's public interface in " \
|
|
40
|
+
"#{Interface::FILE} before authoring its locals"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-author-develop
|
|
3
|
+
description: Use to author (or refresh) a gem's `develop` local — the step-by-step guide to using the gem from a consumer. Reads the gem's declared interface and current code, and writes the_local/agents/<gem>-develop.md. Run inside the provider gem.
|
|
4
|
+
tools: Read, Grep, Write
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You author ONE file: `the_local/agents/<gem>-develop.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 `develop:` are
|
|
13
|
+
your assignment — document those, all of them, and nothing else. Entry points
|
|
14
|
+
listed under `install:` 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:` to get every signature, argument, and required
|
|
26
|
+
order exactly right. The README, existing locals, and comments state intent and
|
|
27
|
+
may be stale; the code wins.
|
|
28
|
+
|
|
29
|
+
Then hide all of it. Your reader is implementing against this gem from your file
|
|
30
|
+
alone and will never open its source. No paths into `lib/`, no private classes, no
|
|
31
|
+
"internally it…", no instruction to go read the gem. If a fact is not part of the
|
|
32
|
+
contract a consumer relies on, cut it.
|
|
33
|
+
|
|
34
|
+
## What this local is for
|
|
35
|
+
|
|
36
|
+
Using the gem: calling its entry points from consuming code. Not installing it,
|
|
37
|
+
not configuring it — that is the install local's.
|
|
38
|
+
|
|
39
|
+
Write it as steps someone follows to implement, in order. Where an entry point
|
|
40
|
+
takes a real decision — an option with no safe default, a choice that depends on
|
|
41
|
+
the consumer's own domain — 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>-develop
|
|
52
|
+
description: Use PROACTIVELY for <the declared tasks, named> — MUST BE USED instead of <the thing people hand-roll>.
|
|
53
|
+
tools: Read, Write, Edit, Grep
|
|
54
|
+
scope: <copied verbatim from the manifest>
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<one or two sentences: what this local does and the ceremony it always follows>
|
|
58
|
+
|
|
59
|
+
## What <gem> is
|
|
60
|
+
<one paragraph, plus when this local should fire>
|
|
61
|
+
|
|
62
|
+
## Interface
|
|
63
|
+
<one bullet per declared entry point, each leading with the entry point in
|
|
64
|
+
backticks, then one line on what it does>
|
|
65
|
+
|
|
66
|
+
## How to use it
|
|
67
|
+
<numbered steps to implement, including where consuming code goes, what runs
|
|
68
|
+
after, and any decision to put to the developer>
|
|
69
|
+
|
|
70
|
+
## Conventions
|
|
71
|
+
<the invariants that must never be skipped, and what is out of scope>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`description` is the routing surface — name the real tasks. "Any `<gem>` work"
|
|
75
|
+
matches only someone who already named the gem, which is when no local was needed.
|
|
76
|
+
|
|
77
|
+
## Before you finish
|
|
78
|
+
|
|
79
|
+
- Every entry point under `develop:` appears in your Interface as its own bullet.
|
|
80
|
+
- Nothing declared under `install:` appears anywhere in your file.
|
|
81
|
+
- Re-read it with no access to the source. Could you implement from this alone?
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-author-info
|
|
3
|
+
description: Use to author (or refresh) a gem's `info` local — the read-only explainer that carries what the install and develop locals do not. Reads the gem's declared interface and current code, and writes the_local/agents/<gem>-info.md. Run inside the provider gem.
|
|
4
|
+
tools: Read, Grep, Write
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You author ONE file: `the_local/agents/<gem>-info.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:` and
|
|
13
|
+
`develop:` belong to those locals — documenting one here is an error the check
|
|
14
|
+
will reject. Most manifests declare nothing under `info:`, and then your Interface
|
|
15
|
+
section documents no commands at all; say which of the other two locals owns the
|
|
16
|
+
surface and route the reader there.
|
|
17
|
+
|
|
18
|
+
Copy the manifest's `scope:` line verbatim into your front matter.
|
|
19
|
+
|
|
20
|
+
## Verify against the code, then hide it
|
|
21
|
+
|
|
22
|
+
Read the files under `sources:` to confirm what the gem actually is and the
|
|
23
|
+
vocabulary it uses. The README states intent and may be stale; the code wins.
|
|
24
|
+
|
|
25
|
+
Then hide all of it. Your reader will never open the gem's source. No paths into
|
|
26
|
+
`lib/`, no private classes, no instruction to go read the gem.
|
|
27
|
+
|
|
28
|
+
## What this local is for
|
|
29
|
+
|
|
30
|
+
This is the catchall. It answers "what is this and when would I reach for it,"
|
|
31
|
+
and carries the vocabulary a reader needs to use the other two locals correctly.
|
|
32
|
+
It makes no changes and gives no steps.
|
|
33
|
+
|
|
34
|
+
Keep it to a page. A reader who wants to install goes to the install local; a
|
|
35
|
+
reader who wants to build goes to the develop local. Say so and stop. No history,
|
|
36
|
+
no design rationale, no tour of subsystems.
|
|
37
|
+
|
|
38
|
+
## The shape
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
---
|
|
42
|
+
name: <gem>-info
|
|
43
|
+
description: Use to learn what <gem> offers — <its real subjects>.
|
|
44
|
+
tools: Read
|
|
45
|
+
scope: <copied verbatim from the manifest>
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<one or two sentences: this local explains and makes no changes>
|
|
49
|
+
|
|
50
|
+
## What <gem> is
|
|
51
|
+
<one or two paragraphs: the problem it solves and when to reach for it>
|
|
52
|
+
|
|
53
|
+
## Interface
|
|
54
|
+
<what the other locals own, and which to route to — no commands unless the
|
|
55
|
+
manifest declares them under `info:`>
|
|
56
|
+
|
|
57
|
+
## How to use it
|
|
58
|
+
<the decision a reader makes here: which of the other two locals they need>
|
|
59
|
+
|
|
60
|
+
## Conventions
|
|
61
|
+
<the vocabulary and naming needed to read this gem's world correctly>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Before you finish
|
|
65
|
+
|
|
66
|
+
- Nothing declared under `install:` or `develop:` appears anywhere in your file.
|
|
67
|
+
- Re-read it with no access to the source. Does it orient someone in one page?
|