readout 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.
- checksums.yaml +7 -0
- data/.claude/agents/the_local-develop.md +111 -0
- data/.claude/agents/the_local-info.md +111 -0
- data/.claude/agents/the_local-install.md +111 -0
- data/CHANGELOG.md +11 -0
- data/CLAUDE.md +118 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +12 -0
- data/develop_process_rules.md +97 -0
- data/lib/readout/result.rb +5 -0
- data/lib/readout/stat.rb +21 -0
- data/lib/readout/version.rb +5 -0
- data/lib/readout.rb +9 -0
- data/sig/readout.rbs +4 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 30ced084ea44a90e897d97abed29f66612e5076f552d38bcc9f80c7ec7b8eac3
|
|
4
|
+
data.tar.gz: 6be986f4b0730dd944ca0fa4995e6a27023a4072604fbece912e455649a2fb06
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a39383cdbe3c98004d589becdf62d160b71eccd1c08c39ac115a47b8a5c066d3804fe9a958f57ffe8e564bee89c84462288b635c91fd7c80605efd79807a9e19
|
|
7
|
+
data.tar.gz: 1dde297075475d6677aae17b8c67c74193751e8c6a74359ce51bbe5ce8f50fce78c89b81a475f2b2b7734c03f6a4c2fbada91306920577d95834d2faaa8ba2ce
|
|
@@ -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: "tylercschneider/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.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-info
|
|
3
|
+
description: Use to learn how the_local works — providers, the build/install model, the delegation trigger, and the direct-dependency scope rule.
|
|
4
|
+
tools: Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You explain how the_local works, answering only from the reference: providers register locals, the_local:build renders committed .md, install/refresh copy them verbatim into a host, the CLAUDE.md delegation trigger makes the host delegate, and only direct dependencies contribute. You make no changes.
|
|
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: "tylercschneider/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.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-install
|
|
3
|
+
description: Use to add the_local to a host app and set it up correctly.
|
|
4
|
+
tools: Bash, Read, Edit
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You set the_local up in a host gem or app, following the reference's install section exactly: add the gem (git source until it is on RubyGems), bundle, run `bundle exec the_local install` to sync locals into .claude/agents/ and write the delegation trigger, and re-run it after bundle changes. You do not invent steps the reference does not list.
|
|
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: "tylercschneider/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.
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here, following
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Initial gem scaffold: source-agnostic metric-contract gem (`Readout`), plain
|
|
11
|
+
Ruby (no Rails / no event-source dependency).
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
- the_local-* agents
|
|
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
|
+
|
|
118
|
+
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tylercschneider
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Readout
|
|
2
|
+
|
|
3
|
+
A **source-agnostic, self-describing metric contract**.
|
|
4
|
+
|
|
5
|
+
A `Readout::Stat` describes a metric — its key, title, plain-English definition,
|
|
6
|
+
unit, timeframe, and declared inputs — and exposes a normalized **result shape**
|
|
7
|
+
(`value`, `shape`, `as_of`, `exact`). Behind every Stat is a **Source** (an adapter
|
|
8
|
+
port); the Stat doesn't care where its number comes from.
|
|
9
|
+
|
|
10
|
+
That decoupling is the whole point: a display layer (e.g. `dash_kit`) renders Stats
|
|
11
|
+
without knowing whether the number came from `tally` rollups, a live query, or a
|
|
12
|
+
fixture. Swap the Source, keep the Stat.
|
|
13
|
+
|
|
14
|
+
Readout depends on nothing concrete — no Rails, no event source, no aggregation
|
|
15
|
+
engine. Adapters bridge it to whatever produces the numbers.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem "readout"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Status
|
|
24
|
+
|
|
25
|
+
The core contract works — a `Stat` reads through its `Source` and returns a
|
|
26
|
+
normalized `Result`:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
source = ->(inputs) { Readout::Result.new(value: 0.42, shape: :scalar, exact: true) }
|
|
30
|
+
stat = Readout::Stat.new(
|
|
31
|
+
key: :sales_conversion,
|
|
32
|
+
title: "Sales Conversion",
|
|
33
|
+
definition: "Share of qualified leads that became deals.",
|
|
34
|
+
calculation: "deals ÷ qualified leads, within the period",
|
|
35
|
+
unit: :percent,
|
|
36
|
+
timeframe: "This month",
|
|
37
|
+
source: source
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
stat.read(qualified: 100, won: 42).value # => 0.42
|
|
41
|
+
stat.definition # the plain-English "what it captures"
|
|
42
|
+
stat.calculation # the "how it's computed"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A Stat is **self-describing** — `title`, `definition`, `calculation`, `unit`,
|
|
46
|
+
`timeframe` — so a UI can explain any number without hardcoding meaning. `Result`
|
|
47
|
+
carries `value, shape, as_of, exact`. Swap the `Source` (a tally-backed one, a live
|
|
48
|
+
query, a fixture) without touching the Stat or the display.
|
|
49
|
+
|
|
50
|
+
Next: declared inputs (required/optional) + validation.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
Available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -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.
|
data/lib/readout/stat.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Readout
|
|
4
|
+
class Stat
|
|
5
|
+
attr_reader :key, :title, :definition, :calculation, :unit, :timeframe
|
|
6
|
+
|
|
7
|
+
def initialize(key:, title: nil, definition: nil, calculation: nil, unit: nil, timeframe: nil, source: nil)
|
|
8
|
+
@key = key
|
|
9
|
+
@title = title
|
|
10
|
+
@definition = definition
|
|
11
|
+
@calculation = calculation
|
|
12
|
+
@unit = unit
|
|
13
|
+
@timeframe = timeframe
|
|
14
|
+
@source = source
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read(inputs)
|
|
18
|
+
@source.call(inputs)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/readout.rb
ADDED
data/sig/readout.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: readout
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- tylercschneider
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: 'Readout defines a Stat — a self-describing metric (key, title, plain-English
|
|
13
|
+
definition, unit, timeframe, declared inputs) with a normalized result shape (value,
|
|
14
|
+
shape, as_of, exact) and a Source adapter port. It depends on nothing concrete:
|
|
15
|
+
any source (e.g. tally rollups) plugs in behind a Stat, so the display layer never
|
|
16
|
+
couples to where a number comes from.'
|
|
17
|
+
email:
|
|
18
|
+
- tylercschneider@gmail.com
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- ".claude/agents/the_local-develop.md"
|
|
24
|
+
- ".claude/agents/the_local-info.md"
|
|
25
|
+
- ".claude/agents/the_local-install.md"
|
|
26
|
+
- CHANGELOG.md
|
|
27
|
+
- CLAUDE.md
|
|
28
|
+
- LICENSE.txt
|
|
29
|
+
- README.md
|
|
30
|
+
- Rakefile
|
|
31
|
+
- develop_process_rules.md
|
|
32
|
+
- lib/readout.rb
|
|
33
|
+
- lib/readout/result.rb
|
|
34
|
+
- lib/readout/stat.rb
|
|
35
|
+
- lib/readout/version.rb
|
|
36
|
+
- sig/readout.rbs
|
|
37
|
+
homepage: https://github.com/tylercschneider/readout
|
|
38
|
+
licenses:
|
|
39
|
+
- MIT
|
|
40
|
+
metadata:
|
|
41
|
+
allowed_push_host: https://rubygems.org
|
|
42
|
+
homepage_uri: https://github.com/tylercschneider/readout
|
|
43
|
+
source_code_uri: https://github.com/tylercschneider/readout
|
|
44
|
+
changelog_uri: https://github.com/tylercschneider/readout/blob/main/CHANGELOG.md
|
|
45
|
+
bug_tracker_uri: https://github.com/tylercschneider/readout/issues
|
|
46
|
+
rubygems_mfa_required: 'true'
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 3.2.0
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 4.0.20
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Source-agnostic, self-describing metric contract
|
|
64
|
+
test_files: []
|