the_local 0.3.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 +29 -1
- data/CLAUDE.md +117 -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/registry.rb +2 -30
- data/lib/the_local/version.rb +1 -1
- data/lib/the_local.rb +1 -20
- 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 -12
- 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/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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7e8e6af806f08c3db82c0cd4d2087ca1d09a883e7175c7077e7bd233a2d853a
|
|
4
|
+
data.tar.gz: a44109b99e55bd41534d195a26ae7b01ce5765851573358190e80f009b6b4954
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07ef43635b84f2581ab545563e80145c1ca6aad3d621c36c8236b19492ee566e92885bdfc60afe584499acc7321dbfe798b29b984d4efabe1c1c103951ef8a95
|
|
7
|
+
data.tar.gz: 130fcf910919e1df3e142f8510a5dc02323a200e806bb66407007b39bbffe5d6d88ff705a70b2bf2ddc492b68814eaed327a696ae65ba02bf17f95eb798e9aa2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.4.0] - 2026-07-31
|
|
2
|
+
|
|
3
|
+
- A provider gem ships **no Ruby and no guide**. Its committed
|
|
4
|
+
`the_local/agents/<gem>-{info,install,develop}.md` are authored by
|
|
5
|
+
`rake the_local:author`, which runs the_local's creators against the gem's
|
|
6
|
+
current code — one local at a time — and writes each from it. The creators live
|
|
7
|
+
inside the_local and are never installed into a host.
|
|
8
|
+
- A provider now declares its public interface in **`the_local/interface.yml`**:
|
|
9
|
+
the `scope` line, the entry points assigned to `install` and `develop`, and the
|
|
10
|
+
`sources` that define them. `rake the_local:author` refuses to run without it.
|
|
11
|
+
Deciding what is public is a human's call, made once and committed, instead of a
|
|
12
|
+
judgment the creators re-make on every run.
|
|
13
|
+
- The three locals **never overlap**: `install` hooks the gem into a consumer,
|
|
14
|
+
`develop` covers using it from consuming code, `info` is the read-only catchall.
|
|
15
|
+
One entry point belongs to exactly one local.
|
|
16
|
+
- `rake the_local:check` enforces that contract in both directions — every declared
|
|
17
|
+
entry point documented by its own local, nothing undeclared documented anywhere,
|
|
18
|
+
nothing documented by a local it wasn't declared for — on top of the existing
|
|
19
|
+
front-matter and section checks.
|
|
20
|
+
- Locals are **black-box docs**: they carry the gem's public interface (how, when,
|
|
21
|
+
where, what commands) and never reference its internals or send a reader into the
|
|
22
|
+
provider's source. Their facts come from the code, not from a README that could
|
|
23
|
+
be stale.
|
|
24
|
+
- The deterministic renderer is removed — no `the_local/guide.md`, no
|
|
25
|
+
`rake the_local:build`, no `ProviderBuild`/`Builder`. Install is unchanged: it
|
|
26
|
+
copies committed files off disk verbatim.
|
|
27
|
+
- `the_local:provider` wires the dependency and the Rakefile hook only; it no
|
|
28
|
+
longer scaffolds a guide. `the_local` is its own first provider, with a committed
|
|
29
|
+
manifest its locals are checked against.
|
|
2
30
|
|
|
3
31
|
## [0.3.0] - 2026-07-01
|
|
4
32
|
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<!-- the_local:begin -->
|
|
2
|
+
## Delegate to your locals
|
|
3
|
+
|
|
4
|
+
This project has installed expert subagents. Before doing work yourself,
|
|
5
|
+
check whether a local owns it and delegate — never work from memory on
|
|
6
|
+
something a local covers:
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
See each agent's description for specifics.
|
|
11
|
+
<!-- the_local:end -->
|
|
12
|
+
|
|
13
|
+
<!-- the_local:process:begin -->
|
|
14
|
+
Read and follow this develop process for all work in this project. It is
|
|
15
|
+
also written verbatim to `develop_process_rules.md` — reference that file directly.
|
|
16
|
+
|
|
17
|
+
# Develop Process
|
|
18
|
+
|
|
19
|
+
The standard process for writing code across all projects. Default to these rules
|
|
20
|
+
unless a project explicitly overrides them.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Diverging from this process
|
|
25
|
+
|
|
26
|
+
Read this process before starting work and follow it — it is the default for
|
|
27
|
+
every session. If a task genuinely calls for breaking one of these rules, do not
|
|
28
|
+
silently deviate: **PAUSE and ask for a one-time exception**, naming the rule and
|
|
29
|
+
why it should be set aside here. An exception is granted for that instance only —
|
|
30
|
+
it needs no doc or notes update — and then you continue. Do not treat a granted
|
|
31
|
+
exception as a standing change to the process.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Test-Driven Development
|
|
36
|
+
|
|
37
|
+
TDD is the default for everything. Work one tiny cycle at a time:
|
|
38
|
+
|
|
39
|
+
1. **Write one test that asserts one thing.**
|
|
40
|
+
2. **Run it and watch it fail** — for the right reason. A test you never saw fail
|
|
41
|
+
proves nothing.
|
|
42
|
+
3. **Write the minimum code to make it pass.**
|
|
43
|
+
4. **Run the test and watch it pass.**
|
|
44
|
+
5. **Commit.**
|
|
45
|
+
6. Repeat with the next test.
|
|
46
|
+
|
|
47
|
+
One assertion per test. One test per commit cycle. No batching multiple behaviors
|
|
48
|
+
into a single test or a single commit.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Commits
|
|
53
|
+
|
|
54
|
+
- A commit is normally **two files: the test file and the code file.**
|
|
55
|
+
- When implementing or updating an interface (e.g. a new controller endpoint) a
|
|
56
|
+
commit may touch more files (route + controller + view) — that is the minimal
|
|
57
|
+
coherent unit for that interface, and it is allowed.
|
|
58
|
+
- Keep each commit focused on the one behavior the test describes.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## What to Test
|
|
63
|
+
|
|
64
|
+
- **Test our own code only.**
|
|
65
|
+
- **Never test third-party code** — not a gem, not an API, not a framework. The
|
|
66
|
+
only test that may reference a dependency is one that asserts *our system is
|
|
67
|
+
correctly wired to it* (the integration seam), never the dependency's own
|
|
68
|
+
behavior.
|
|
69
|
+
- **Never test another interface inside a unit test.** A test covers one interface.
|
|
70
|
+
The single exception is the smoke integration test described below.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Smoke Integration Test
|
|
75
|
+
|
|
76
|
+
When implementing an interface, write **one smoke integration test** that exercises
|
|
77
|
+
the interface end to end and proves the pieces are connected. This is the one place
|
|
78
|
+
where touching more than the unit under test is expected and correct.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Pull Requests
|
|
83
|
+
|
|
84
|
+
- **Always work on a feature branch and open a PR.** Confirm the target branch
|
|
85
|
+
before any git operation (`git branch --show-current`).
|
|
86
|
+
- **Keep PRs small and manageable** — typically **no more than 8–10 files.**
|
|
87
|
+
- Keep the focus of a PR narrow. One concern per PR.
|
|
88
|
+
- **All tests pass before opening the PR.**
|
|
89
|
+
- **The linter and every other CI check pass before opening the PR.**
|
|
90
|
+
- Never start a new PR until the previous one is merged.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Code Quality
|
|
95
|
+
|
|
96
|
+
- Follow Clean Code principles: small functions, clear names, no surprises.
|
|
97
|
+
- Follow SOLID principles. Readable by a human first.
|
|
98
|
+
- Keep it simple — no abstraction until a real need calls for it.
|
|
99
|
+
- Explicitly require libraries rather than assuming autoload.
|
|
100
|
+
|
|
101
|
+
## Comments
|
|
102
|
+
|
|
103
|
+
- **Write self-documenting code, not comments.** Code should be clean and readable
|
|
104
|
+
on its own. Names — of classes, methods, variables, and partials — carry the intent.
|
|
105
|
+
- **A comment is a smell.** If you feel a comment is needed, the code is either built
|
|
106
|
+
wrong or needs refactoring (a clearer name, a smaller method, an extracted object or
|
|
107
|
+
partial) so the intent is obvious without prose. Follow SOLID and this resolves itself.
|
|
108
|
+
- Do not leave explanatory headers on classes/methods, inline "what this does" notes,
|
|
109
|
+
or section banners. Delete them and let the structure speak.
|
|
110
|
+
- Narrow exceptions, kept rare: a genuinely non-obvious *why* (a workaround for an
|
|
111
|
+
external bug, a legal/security constraint) and machine-readable annotations the
|
|
112
|
+
tooling requires (e.g. `rubocop:disable`). Prefer refactoring over a "why" comment
|
|
113
|
+
whenever you can.
|
|
114
|
+
<!-- the_local:process:end -->
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
data/MIGRATING.md
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# Migrating a provider gem to the guide-based model
|
|
2
|
+
|
|
3
|
+
> **Superseded.** the_local no longer uses a guide. A provider now ships **no
|
|
4
|
+
> guide and no Ruby** — its committed `the_local/agents/*.md` are authored by the
|
|
5
|
+
> `the_local-author-*` creator agents, which read the gem's current code. To make
|
|
6
|
+
> a gem a provider, or to refresh a stale one, follow **[PROVIDERS.md](PROVIDERS.md)**,
|
|
7
|
+
> not this runbook. This file is kept only for gems still on the old
|
|
8
|
+
> `TheLocal.register` layout that need to reach a committed trio first; once they
|
|
9
|
+
> have one, re-author it with the creators.
|
|
10
|
+
|
|
11
|
+
This is a **deterministic runbook** for converting one provider gem from the old
|
|
12
|
+
`TheLocal.register` layout to the new guide-based one (the_local ≥ the version on
|
|
13
|
+
`main` after PR #72). Follow it top to bottom, **one gem at a time**. Every step
|
|
14
|
+
ends with a **VERIFY** gate; if a gate's actual output does not match the stated
|
|
15
|
+
expectation, **STOP** and resolve it before continuing — do not proceed on a
|
|
16
|
+
failed gate.
|
|
17
|
+
|
|
18
|
+
Do not batch gems. Do not skip verification. Do not invent steps.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Two entry points
|
|
23
|
+
|
|
24
|
+
**Already on the guide model** (the gem has `the_local/guide.md` and no
|
|
25
|
+
`lib/**/the_local.rb`)? Its locals are rendering from the generic template and
|
|
26
|
+
the host agent is not routing to them. Branch first, update the engine, then skip
|
|
27
|
+
to **Step 6 — Author the locals' metadata** and run Steps 7-9:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git checkout main && git pull --ff-only
|
|
31
|
+
git checkout -b chore/author-the-locals
|
|
32
|
+
bundle update the_local
|
|
33
|
+
bundle exec ruby -e 'require "the_local/guide"; puts "ok"' # => ok
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If that prints `LoadError` instead of `ok`, the bundled the_local predates
|
|
37
|
+
authored metadata — **STOP** and point the Gemfile at the_local `main`. Step 6's
|
|
38
|
+
VERIFY needs it.
|
|
39
|
+
|
|
40
|
+
**Still on the register model** (the gem has `lib/<path>/the_local.rb`)? Start at
|
|
41
|
+
the Preconditions and run the whole runbook top to bottom.
|
|
42
|
+
|
|
43
|
+
Either way you finish at the same place: an authored guide, a rebuilt trio, a
|
|
44
|
+
green drift test.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What the migration changes
|
|
49
|
+
|
|
50
|
+
| Old (register-based) | New (guide-based) |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `lib/<path>/the_local.rb` — `Companion.register!` block | *(deleted)* |
|
|
53
|
+
| `lib/<path>/reference.rb` — `Reference` loader | *(deleted)* |
|
|
54
|
+
| `lib/<path>/reference/guide.md` — the knowledge | **moved to** `the_local/guide.md` (gem root) |
|
|
55
|
+
| `c.agent description:`/`body:`/`scope:` — the authored metadata | **re-authored as** front matter in `the_local/guide.md` (Step 6) |
|
|
56
|
+
| `lib/<path>/the_local/agents/*.md` — committed locals | **rebuilt at** `the_local/agents/*.md` (gem root) |
|
|
57
|
+
| entrypoint `require`/`require_relative "<path>/the_local"` | *(deleted)* |
|
|
58
|
+
| Rakefile `require "<path>/the_local"` before `require "the_local/rake"` | *(deleted; keep the `the_local/rake` line)* |
|
|
59
|
+
| `test/**/companion_test.rb`, `test/**/reference_test.rb` | *(deleted; replaced by a drift test)* |
|
|
60
|
+
|
|
61
|
+
The gem carries **no Ruby** for the_local afterward — one `the_local/guide.md`
|
|
62
|
+
in, the rendered `the_local/agents/*.md` out.
|
|
63
|
+
|
|
64
|
+
`<path>` is the gem's lib subpath: `keystone_ui` for a plain gem,
|
|
65
|
+
`event_engine/subscribers` for a hyphenated gem. The commands below discover it;
|
|
66
|
+
never hardcode it.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Preconditions (run once, at the gem root)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd <the gem's root> # the directory containing *.gemspec
|
|
74
|
+
git checkout main && git pull --ff-only # start clean
|
|
75
|
+
git checkout -b chore/the_local-guide-migration
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**VERIFY** you are in a provider gem on a fresh branch:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
ls *.gemspec # exactly one gemspec
|
|
82
|
+
git branch --show-current # chore/the_local-guide-migration
|
|
83
|
+
find lib -path '*/reference/guide.md' # exactly one path — the old guide
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- If there is **no** `*/reference/guide.md`, this gem is not an old-layout
|
|
87
|
+
provider (or already migrated). **STOP.**
|
|
88
|
+
- If there are **two or more**, **STOP** and report — the runbook assumes one.
|
|
89
|
+
|
|
90
|
+
Update to the new the_local so `rake the_local:build` uses the guide-based engine:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bundle update the_local
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**VERIFY** the new engine is present:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bundle exec ruby -e 'require "the_local/provider_build"; puts "ok"' # => ok
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If this prints an error instead of `ok`, the bundled the_local is too old.
|
|
103
|
+
**STOP** and fix the dependency (point the Gemfile at the_local `main`).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Step 1 — Capture the current facts
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
GUIDE=$(find lib -path '*/reference/guide.md') # lib/<path>/reference/guide.md
|
|
111
|
+
LIBPATH=$(dirname "$(dirname "$GUIDE")") # lib/<path>
|
|
112
|
+
GEM=$(basename ./*.gemspec .gemspec) # the gem name
|
|
113
|
+
echo "GUIDE=$GUIDE LIBPATH=$LIBPATH GEM=$GEM"
|
|
114
|
+
echo "-- committed locals today --"; ls "$LIBPATH/the_local/agents/"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**VERIFY** the three variables are non-empty and `$LIBPATH/the_local/agents/`
|
|
118
|
+
lists the current `.md` files.
|
|
119
|
+
|
|
120
|
+
**DECISION — worker facet.** Look at the listed filenames:
|
|
121
|
+
|
|
122
|
+
- If they are `<gem>-info.md`, `<gem>-install.md`, `<gem>-develop.md` → normal
|
|
123
|
+
case, continue.
|
|
124
|
+
- If you see `<gem>-operate.md` (a CLI gem's worker) → the new model renders
|
|
125
|
+
**`develop`**, not `operate`. The migration **renames the worker**: the old
|
|
126
|
+
`operate` local disappears and a `develop` local replaces it. This is a
|
|
127
|
+
deliberate, visible change. Note it for the PR description and continue — the
|
|
128
|
+
old `-operate.md` is deleted in Step 3 and not rebuilt.
|
|
129
|
+
|
|
130
|
+
**GATE — is the guide actually finished?**
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
grep -nE '^[[:space:]]*TODO:' "$GUIDE" || echo "no TODO markers"
|
|
134
|
+
grep -cE '^### (Interface|Recipe|Install|Conventions)' "$GUIDE" # expect 4
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- If there are **any** line-leading `TODO:` markers, or fewer than **4**
|
|
138
|
+
canonical sections, this gem's guide was never finished. The build gate will
|
|
139
|
+
reject it. **STOP.** This is *authoring* work (write the gem's real interface),
|
|
140
|
+
not mechanical migration — do it as a separate, deliberate task, then return.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Step 2 — Move the guide to the gem root
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
mkdir -p the_local
|
|
148
|
+
git mv "$GUIDE" the_local/guide.md
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**VERIFY**:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
test -f the_local/guide.md && echo "moved" # => moved
|
|
155
|
+
test -e "$GUIDE" && echo "STILL THERE — STOP" || echo "old path gone"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Expect `moved` and `old path gone`.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Step 3 — Delete the old Ruby and rendered locals
|
|
163
|
+
|
|
164
|
+
Delete the companion Ruby **first** — that way the require-cleanup in Step 4 sees
|
|
165
|
+
only the two lines that actually need removing, not the soft guard *inside* the
|
|
166
|
+
companion.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
git rm "$LIBPATH/the_local.rb" "$LIBPATH/reference.rb"
|
|
170
|
+
git rm -r "$LIBPATH/the_local/agents"
|
|
171
|
+
# reference/ dir held only the moved guide; remove it if now empty:
|
|
172
|
+
rmdir "$LIBPATH/reference" 2>/dev/null || true
|
|
173
|
+
# Delete the old drift/registration tests if the gem has them:
|
|
174
|
+
find test -name companion_test.rb -o -name reference_test.rb 2>/dev/null | xargs -r git rm
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**VERIFY** the old artifacts are gone:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
ls "$LIBPATH/the_local.rb" "$LIBPATH/reference.rb" 2>/dev/null \
|
|
181
|
+
&& echo "OLD RUBY REMAINS — STOP" || echo "old ruby gone"
|
|
182
|
+
find "$LIBPATH" -path '*the_local/agents*' -name '*.md' | grep . \
|
|
183
|
+
&& echo "OLD AGENTS REMAIN — STOP" || echo "old agents gone"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Expect `old ruby gone` and `old agents gone`.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Step 4 — Remove the companion require from the entrypoint and Rakefile
|
|
191
|
+
|
|
192
|
+
With the companion file already deleted, the gem still *requires* it from two
|
|
193
|
+
places (its entrypoint and its Rakefile). Both requires must go. **Keep**
|
|
194
|
+
`require "the_local/rake"` in the Rakefile.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Every line here is a companion require to delete (rake/builder are excluded):
|
|
198
|
+
grep -rn 'the_local"' lib Rakefile | grep -vE 'the_local/(rake|builder)"'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Edit each file the command prints, deleting the line that requires the companion
|
|
202
|
+
(`require "<path>/the_local"` or `require_relative "<path>/the_local"`), plus any
|
|
203
|
+
now-orphaned comment directly above it. Do **not** delete `require "the_local/rake"`.
|
|
204
|
+
|
|
205
|
+
**VERIFY** no companion require remains, and the rake require survives:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
grep -rn 'the_local"' lib Rakefile | grep -vE 'the_local/(rake|builder)"' \
|
|
209
|
+
&& echo "COMPANION REQUIRE REMAINS — STOP" || echo "entrypoint clean"
|
|
210
|
+
grep -q 'require "the_local/rake"' Rakefile && echo "rake require kept"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Expect `entrypoint clean` and `rake require kept`.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Step 5 — Make the gemspec ship `the_local/`
|
|
218
|
+
|
|
219
|
+
The committed locals now live at the gem root, so the gemspec must include them.
|
|
220
|
+
Check how this gem selects files:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
grep -n 'spec.files' ./*.gemspec
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- **`git ls-files`** based (ships every tracked file) → **no edit needed.** The
|
|
227
|
+
root `the_local/` ships automatically. Skip to VERIFY.
|
|
228
|
+
- **`Dir[...]` / `Dir.chdir { Dir[...] }`** based (an explicit allowlist, e.g.
|
|
229
|
+
`Dir["lib/**/*", "app/**/*"]` or `Dir["{app,config,db,lib}/**/*", ...]`) → the
|
|
230
|
+
root `the_local/` is **not** matched. Edit the glob to add `"the_local/**/*"`
|
|
231
|
+
to the file list.
|
|
232
|
+
|
|
233
|
+
Example edit (keystone_ui-style):
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
# before
|
|
237
|
+
spec.files = Dir["lib/**/*", "app/**/*"]
|
|
238
|
+
# after
|
|
239
|
+
spec.files = Dir["lib/**/*", "app/**/*", "the_local/**/*"]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**VERIFY** the gemspec now includes the guide (once it exists on disk it must be
|
|
243
|
+
selectable). After Step 7 builds the agents, this is re-checked; for now just
|
|
244
|
+
confirm the edit is syntactically valid:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
ruby -e 'Gem::Specification.load(Dir["*.gemspec"].first)' && echo "gemspec loads"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Expect `gemspec loads`. If it errors, **STOP** and fix the syntax.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Step 6 — Author the locals' metadata
|
|
255
|
+
|
|
256
|
+
The register block carried each local's `description` and `body` plus the
|
|
257
|
+
provider's `scope`. **Moving the guide does not carry them over.** Without this
|
|
258
|
+
step every local renders from a generic template — `"Use PROACTIVELY for any
|
|
259
|
+
<gem> work"` — which the host agent only matches if the user already named the
|
|
260
|
+
gem, i.e. exactly when no local was needed. The local never fires and the host
|
|
261
|
+
answers generically. `rake the_local:build` refuses a guide whose scope is
|
|
262
|
+
unauthored, so this step is not optional.
|
|
263
|
+
|
|
264
|
+
**Recover the old wording first.** If this gem had a register block, git history
|
|
265
|
+
holds text that was already authored against the real gem — recover and improve
|
|
266
|
+
it rather than inventing new wording:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
git log --all --oneline -- "$LIBPATH/the_local.rb" | tail -1 # the commit that added it
|
|
270
|
+
git show <that-commit>:"$LIBPATH/the_local.rb" # scope, descriptions, bodies
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For a gem that never had one, investigate before writing: its gemspec, README,
|
|
274
|
+
public API, tests, and real call sites in consuming projects.
|
|
275
|
+
|
|
276
|
+
Add front matter at the very top of `the_local/guide.md`, above the `## Title`
|
|
277
|
+
line:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
---
|
|
281
|
+
scope: <domain> — <the tasks this gem owns>
|
|
282
|
+
locals:
|
|
283
|
+
info:
|
|
284
|
+
description: >-
|
|
285
|
+
Use to learn what <gem> offers — <its actual subjects>.
|
|
286
|
+
body: >-
|
|
287
|
+
What this local answers from, and that it changes nothing.
|
|
288
|
+
install:
|
|
289
|
+
description: >-
|
|
290
|
+
Use to add <gem> to a project and set it up correctly.
|
|
291
|
+
body: >-
|
|
292
|
+
The steps it follows, and what it must NOT do (e.g. companion gems).
|
|
293
|
+
develop:
|
|
294
|
+
description: >-
|
|
295
|
+
Use PROACTIVELY for <the real tasks, named> — MUST BE USED instead of
|
|
296
|
+
<the thing people hand-roll>.
|
|
297
|
+
body: >-
|
|
298
|
+
The ceremony that must never be skipped, and what is out of scope.
|
|
299
|
+
---
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Each field answers one question:
|
|
303
|
+
|
|
304
|
+
| Field | Question | Why it matters |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `scope` | What user-visible tasks does this gem own? | The one line the host's `CLAUDE.md` delegation rule names. |
|
|
307
|
+
| `description` | What would someone actually ask for? | **The routing surface.** The host agent matches a task against it to decide whether to delegate. |
|
|
308
|
+
| `body` | What ceremony must never be skipped? What is out of scope? | A standing instruction. Facts buried at line 90 of the reference are not instructions. |
|
|
309
|
+
|
|
310
|
+
You author only these. The renderer owns which locals exist, their `tools`, the
|
|
311
|
+
front-matter keys and their order, and the file's structure — do not add a fourth
|
|
312
|
+
local, do not set `tools`, do not invent keys.
|
|
313
|
+
|
|
314
|
+
**GATE — is the `develop` description actually routable?** Read it and answer:
|
|
315
|
+
*would this match a request that never says the gem's name?*
|
|
316
|
+
|
|
317
|
+
- `"Use PROACTIVELY for any event_engine work"` → **broken.** Names only the gem.
|
|
318
|
+
- `"Use PROACTIVELY for any EventEngine work — defining events, choosing
|
|
319
|
+
process_type, emitting, and keeping the committed schema in sync"` → routable.
|
|
320
|
+
Names the tasks.
|
|
321
|
+
|
|
322
|
+
If yours reads like the first, rewrite it before continuing. **This is the whole
|
|
323
|
+
point of the step**; a guide that passes the build gate but fails this question
|
|
324
|
+
ships a local nobody reaches.
|
|
325
|
+
|
|
326
|
+
**VERIFY** the front matter parses and is complete:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
bundle exec ruby -r the_local/guide -e '
|
|
330
|
+
g = TheLocal::Guide.new(File.read("the_local/guide.md"))
|
|
331
|
+
abort "UNAUTHORED SCOPE — STOP" if g.scope.to_s.strip.empty?
|
|
332
|
+
%w[info install develop].each do |n|
|
|
333
|
+
abort "#{n}: no description — STOP" if g.local(n)["description"].to_s.strip.empty?
|
|
334
|
+
end
|
|
335
|
+
abort "TODO markers remain — STOP" if g.scope.include?("TODO")
|
|
336
|
+
puts "authored"
|
|
337
|
+
'
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Expect `authored`. Anything else — fix it before Step 7.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Step 7 — Build the committed locals
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
bundle exec rake the_local:build
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**VERIFY** exactly the standard trio is rendered at the root, and the old worker
|
|
351
|
+
name is gone:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
ls the_local/agents/ # <gem>-info.md <gem>-install.md <gem>-develop.md
|
|
355
|
+
ls the_local/agents/*-operate.md 2>/dev/null && echo "OPERATE STILL PRESENT — STOP" || echo "no operate file"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
- Expect three files: `<gem>-info.md`, `<gem>-install.md`, `<gem>-develop.md`.
|
|
359
|
+
- If `rake the_local:build` **fails** with `incomplete guide(s)` → the guide has
|
|
360
|
+
a `TODO:` or is missing a section. Return to Step 1's GATE. **STOP.**
|
|
361
|
+
|
|
362
|
+
**VERIFY the build is idempotent** (a second build produces no diff):
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
bundle exec rake the_local:build
|
|
366
|
+
git status --short the_local/agents/ # expect NO output
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Any output here means the build is non-deterministic — **STOP** and report.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Step 8 — Add a drift test
|
|
374
|
+
|
|
375
|
+
Create a test that fails if a future edit to the guide isn't rebuilt. Adapt the
|
|
376
|
+
path and test framework to the gem (Minitest shown; the gem root is the dir with
|
|
377
|
+
the gemspec).
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
# test/the_local_drift_test.rb (or spec/the_local_drift_spec.rb)
|
|
381
|
+
# frozen_string_literal: true
|
|
382
|
+
|
|
383
|
+
require "test_helper"
|
|
384
|
+
require "the_local/provider_build"
|
|
385
|
+
|
|
386
|
+
class TheLocalDriftTest < Minitest::Test
|
|
387
|
+
def test_committed_agents_match_the_rendered_build
|
|
388
|
+
gem_root = File.expand_path("..", __dir__)
|
|
389
|
+
TheLocal::ProviderBuild.new(gem_root).agents.each do |agent|
|
|
390
|
+
assert_equal agent.to_markdown, File.read(agent.source_path)
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Adjust `File.expand_path("..", __dir__)` so it resolves to the gem root from the
|
|
397
|
+
test file's location (one `..` per directory level of the test file below root).
|
|
398
|
+
|
|
399
|
+
**VERIFY** the gem's whole suite passes:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
bundle exec rake test # or: bundle exec rspec
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Expect **0 failures, 0 errors**. If the drift test fails, the committed agents
|
|
406
|
+
don't match the build — re-run Step 7. If other tests fail because they
|
|
407
|
+
referenced the deleted companion/reference, delete or rewrite those assertions
|
|
408
|
+
(they tested removed code).
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Step 9 — Lint, review, commit, PR
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
bundle exec rubocop # if the gem uses it — expect clean
|
|
416
|
+
git status --short # review every add/delete/modify
|
|
417
|
+
git diff --stat main
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**VERIFY** the diff contains only: the guide move, deleted companion/reference/old
|
|
421
|
+
agents, the entrypoint + Rakefile edits, the gemspec edit (unless git-ls-files),
|
|
422
|
+
the new `the_local/agents/*.md`, and the drift test. Anything else — **STOP** and
|
|
423
|
+
explain it before committing.
|
|
424
|
+
|
|
425
|
+
Commit in small, honest steps, then open a PR against the gem's `main`:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
git add -A
|
|
429
|
+
git commit -m "Migrate to the guide-based the_local provider model"
|
|
430
|
+
git push -u origin chore/the_local-guide-migration
|
|
431
|
+
gh pr create --base main --title "Migrate to the guide-based the_local provider model" --body "…"
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
In the PR body, state plainly:
|
|
435
|
+
- The gem now ships no Ruby for the_local; one `the_local/guide.md` renders the
|
|
436
|
+
locals.
|
|
437
|
+
- **If this was an `operate` gem:** the worker local is renamed `operate` →
|
|
438
|
+
`develop`. Hosts that installed `<gem>-operate.md` keep a stale copy until they
|
|
439
|
+
re-run `the_local install`; note that a host re-sync is needed.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## The six gems and their specifics (ground truth)
|
|
444
|
+
|
|
445
|
+
Verified against the repos on disk. Use this to sanity-check what each gem's run
|
|
446
|
+
should look like — but still run every VERIFY gate; do not skip them.
|
|
447
|
+
|
|
448
|
+
**Check which entry point each gem needs before starting it** — a gem migrated
|
|
449
|
+
before the authoring step existed has an unauthored guide and needs Step 6
|
|
450
|
+
onward, not the whole runbook:
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
test -f the_local/guide.md && ! ls lib/**/the_local.rb >/dev/null 2>&1 \
|
|
454
|
+
&& echo "already migrated — start at Step 6" || echo "register model — start at Preconditions"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
| Gem | lib `<path>` | Worker | gemspec `files` | gemspec edit? | Old comp/ref tests | Notes |
|
|
458
|
+
|---|---|---|---|---|---|---|
|
|
459
|
+
| `citizen` | `citizen` | develop | `Dir.chdir { Dir["{app,config,db,lib}/**/*", …] }` | **yes** — add `"the_local/**/*"` | none | clean |
|
|
460
|
+
| `cs133` | `cs133` | develop | `git ls-files` | **no** | none | clean |
|
|
461
|
+
| `dash_kit` | `dash_kit` | develop | `Dir.chdir { Dir["{app,config,db,lib}/**/*", …] }` | **yes** | none | clean |
|
|
462
|
+
| `event_engine` | `event_engine` | develop | `Dir.chdir { Dir["{app,config,db,lib}/**/*", …] }` | **yes** | **companion_test + reference_test** — delete both | **already migrated (PR #238) with an unauthored guide — start at Step 6.** Its old register block holds the authored text to recover. |
|
|
463
|
+
| `event_engine-subscribers` | `event_engine/subscribers` | **operate** | `Dir.chdir { Dir["{app,config,db,lib}/**/*", …] }` | **yes** | none | **guide is all `TODO:` — unfinished scaffold. Step 1 GATE will STOP you: finish the guide first (authoring), or skip this gem.** |
|
|
464
|
+
| `keystone_ui` | `keystone_ui` | develop | `Dir["lib/**/*", "app/**/*"]` | **yes** | **companion_test + reference_test** — delete both | clean |
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Fail-safe summary
|
|
469
|
+
|
|
470
|
+
Stop and get help the moment any of these is true:
|
|
471
|
+
|
|
472
|
+
- A VERIFY gate's output doesn't match its stated expectation.
|
|
473
|
+
- The guide has `TODO:` markers or fewer than 4 canonical sections.
|
|
474
|
+
- `rake the_local:build` reports `incomplete guide(s)` or `unauthored scope`.
|
|
475
|
+
- A `develop` description names only the gem ("any `<gem>` work") — it will not route.
|
|
476
|
+
- A second build produces a diff (non-idempotent).
|
|
477
|
+
- The final `git diff --stat` shows files outside the expected set.
|
|
478
|
+
- The gem's test suite has failures you can't trace to deleted companion/reference code.
|