rails-hyperdrive 0.6.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1a89d616d54ce6f324e1105894dd01741a0d42e9887bb018fceea00aafbe6f1
4
- data.tar.gz: b279e9c105a9cbb75c7bffc8a46224f55ff94b6a5412a335db3ba4fe9c3f3812
3
+ metadata.gz: 65854e72d10acf60fd5140b816d8ee96bc6f573679dd74e7aeb369cbad9f572a
4
+ data.tar.gz: d86dd2e00fe05e063861b6a408c78dc38809a541cc39fa63a6ab5ca709e88bc3
5
5
  SHA512:
6
- metadata.gz: 891f3bcced1126be5b1eb48d456e369b05dcb87cda0f87c82b5d27d7eeea9e57c4ed39ca7549f5a573b13ef800efe7b56f75c39c6cfb544f15cfb1518a9b0d82
7
- data.tar.gz: 218fe629a71711e1915af9ad10a1cc58923ee9dc41ab16514224191526d19f249ed2df6658c5d281c25b1fa9b6430cfd75c163ee4b9d669853164f5889427780
6
+ metadata.gz: a4a279ebaaf70cf2cd43d050d8d031b0ddc45f0e7790e49954dc4f8c96e1a0733c61c3bb4a1ee365ed076992f29a47f00a40707c964328d11f63b5130cd1ce28
7
+ data.tar.gz: c84c36f0563a52ae2938a998167f2287d68ec586f355794b48c6b79887446fac5cc127f20e0a260613aaeb43f8d9d3187fd198b72a51f3ab7482b20ab5a84f41
data/CHANGELOG.md CHANGED
@@ -7,6 +7,308 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - **ERB templates for guidelines, agents, and commands.** A companion can ship a
15
+ flat artifact as `<name>.md.erb` directly in its own root
16
+ (`lib/<gem_name>/hyperdrive/guidelines/`, `agents/`, `commands/`, or an
17
+ `agents_dir:`/`commands_dir:` override). It renders at discovery against the
18
+ app's resolved bundle with the same helpers skills use — `gem?`, `any_gem?`,
19
+ `gem_version`, `canonical_render?` — and installs as `<name>.md`: a
20
+ guideline's frontmatter is parsed from the render and then stripped, a
21
+ templated command takes its identity from the rendered stem
22
+ (`analyze.md.erb` → `/analyze`, prefixed by `command_prefix:` as usual), and
23
+ the rendered bytes are what the lock hashes, so a re-render is an ordinary
24
+ upstream delivery through the existing drift, sidecar, and merge machinery.
25
+ There is no template/content pairing for flat kinds and no separate template
26
+ root: Claude Code plugins glob `*.md`, so a template is invisible to them.
27
+ A static `<name>.md` beats a template rendering to the same filename anywhere
28
+ in the kind's roots, always with a warning; a manifest entry may key the
29
+ artifact by either spelling (`reviewer.md.erb` or `reviewer.md`), the shipped
30
+ spelling winning with a warning when both appear, and
31
+ `rake hyperdrive:manifest:check` fails on that ambiguity. A convention-path
32
+ guideline template opts a gem in as a companion, like its static twin.
33
+ Declare a gem-wide `hyperdrive_version: ">= 0.8"` when shipping templated flat
34
+ artifacts: earlier installers never look for `*.md.erb` outside skill
35
+ directories, so the fence is what turns their silence into "upgrade
36
+ rails-hyperdrive to install it".
37
+ - `canonical_render?`, a fourth skill-template ERB helper: `true` in the
38
+ author-side canonical render (`rake hyperdrive:skills:render`/`check`) and
39
+ `false` when rendering into an app, so one template can serve both channels
40
+ where the same content has to read differently — a companion that also ships
41
+ as a Claude Code plugin, say, whose command spellings differ from the
42
+ hyperdrive-installed ones. Unlike the bundle predicates it is deterministic in
43
+ both bindings, so `if`/`else`/`unless` on it is safe. A template using it
44
+ should declare `hyperdrive_version: ">= 0.8"` in its manifest entry (or
45
+ gem-wide), so an older installer reports "upgrade rails-hyperdrive" rather
46
+ than skipping the skill with a template-render failure.
47
+
48
+ ## [0.7.0] - 2026-08-26
49
+
50
+ ### Added
51
+
52
+ - **Two new companion artifact kinds: agents and commands.** A companion gem can
53
+ now ship Claude Code subagents from `agents/*.md` (installed to
54
+ `.claude/agents/<name>.md`) and slash commands from `commands/*.md` (installed
55
+ to `.claude/commands/<name>.md`), alongside its skills and guidelines. Both
56
+ are flat single files that install byte-identical to what the gem ships, and
57
+ both ride the whole existing machine: gating, the `hyperdrive_version:` fence,
58
+ sha-based drift, `--overwrite`/`--sidecar`/`--merge`, `disabled:`, the stale
59
+ sweep, cross-source collision postfixing, and the additive top-up on
60
+ `bundle install`. Agents require `name` + `description` frontmatter like a
61
+ skill; a command's frontmatter is optional and never validated, and its
62
+ identity is its filename stem, so `commands/analyze.md` becomes `/analyze`.
63
+ Neither is wired into `CLAUDE.md` or `index.md` — Claude Code registers them
64
+ by file presence. Installing flat (no per-gem subdirectory) keeps the
65
+ `skills/`/`agents/`/`commands/` sibling geometry a gem ships, so relative
66
+ links between them resolve unchanged after install.
67
+ - Manifest additions in `hyperdrive.yml`: `agents:` and `commands:` gating
68
+ sections keyed by filename, taking the same values `guidelines:` entries do;
69
+ `agents_dir:` and `commands_dir:` to name additional roots (resolved like
70
+ `skills_dir:`); and `command_prefix:`, an optional gem-wide scalar inside
71
+ `commands:` that installs every command of that gem as `<prefix>-<filename>`,
72
+ for a companion that also ships as a Claude Code plugin and wants its
73
+ `/name` namespaced. `rake hyperdrive:manifest:check` lints all of them.
74
+
75
+ - `require "hyperdrive/skill_tasks"` is the require path for the companion-repo
76
+ rake tasks (`hyperdrive:skills:render`, `hyperdrive:skills:check`,
77
+ `hyperdrive:manifest:check`) — framework-neutral, since the tasks run in a
78
+ plain gem repo with no Rails involved.
79
+ - The `hyperdrive_version:` fence now carries a stated guarantee for companion
80
+ authors: it fences on whichever gem implements artifact discovery and install
81
+ — today rails-hyperdrive — and that version numbering is guaranteed
82
+ continuous across any future restructuring of the gem, so a fence like
83
+ `">= 0.8"` keeps its meaning permanently.
84
+ - `rake hyperdrive:manifest:check`, a strict author-side lint of a companion
85
+ gem's `hyperdrive.yml`, on the same companion-repo rake surface as
86
+ `hyperdrive:skills:*` (add `require "hyperdrive/skill_tasks"` to the
87
+ Rakefile; takes the same optional gemspec-path argument). Where the installer
88
+ is permissive so a manifest written for a newer schema never blocks an
89
+ install, the lint fails: unknown keys at every level — the top level,
90
+ `skills:`/`guidelines:` entries, and `conditional:` entries — plus any
91
+ `gem:`/`gems:` or `hyperdrive_version:` value the installer cannot parse, and
92
+ `skills:`/`guidelines:`/`conditional:` keys naming nothing the gem ships. The
93
+ retired `versions:` key and its `version:` near-miss are named pointedly.
94
+ A manifest that lints clean draws no gating warning at install time.
95
+ - A template/content-paired skill's supporting files can be templated too: a
96
+ `*.md.erb` in the template directory renders against the app's bundle and
97
+ installs as `x.md`, so generic skills.sh consumers never copy raw ERB. Within
98
+ a paired skill a template-side file owns its target path — the content
99
+ directory's same-named file never installs, whether the template renders, is
100
+ gated out by `conditional:`, or fails to render.
101
+ `rake hyperdrive:skills:render` now writes each supporting template's
102
+ fail-open canonical face into the paired content directory, and
103
+ `rake hyperdrive:skills:check` byte-gates those faces and fails on any
104
+ `*.md.erb` found under a public skills root. A supporting `*.md.erb` shipped
105
+ under a public skills root still renders, but draws a warning steering it to
106
+ the template directory.
107
+ - `hyperdrive:init --skip-mcp` skips MCP setup entirely: no `rails-hyperdrive`
108
+ entry in `.mcp.json` and no engine mount in `config/routes.rb`. Content
109
+ install, the discover-cache `.gitignore` rule, and the bundler-plugin Gemfile
110
+ directive all still run, and the summary reports `MCP: skipped (--skip-mcp)`
111
+ in place of the mount/server lines. Like `--skip-content`, it only suppresses
112
+ writes — existing MCP configuration is left untouched — and the two flags
113
+ combine.
114
+ - `gems:` is an exact alias of `gem:` at every position the key is read —
115
+ gem-wide defaults, `skills:`/`guidelines:` entries, and per-file
116
+ `conditional:` entries — for every value shape. A map carrying both keys is a
117
+ stylistic slip rather than an error: `gems:` wins, with a warning.
118
+ - Companion-manifest `gem:` gating accepts a map with exactly one of `any:` or
119
+ `all:`, so an artifact can require *every* listed target rather than any one
120
+ of them: `gems: {all: [devise, pundit]}`. `any:` is an explicit spelling of the
121
+ existing any-match, which every bare form (single name, comma-separated
122
+ string, YAML list, `"*"`) keeps by default. The map values take those same
123
+ flat forms, and the form is accepted everywhere `gem:` is — gem-wide defaults,
124
+ `skills:`/`guidelines:` entries, and per-file `conditional:` entries. A `"*"`
125
+ inside `all:` is always satisfied, so it is dropped with a warning; a
126
+ malformed map takes the usual fail-open path (warn, install ungated).
127
+ - Companion manifests can version-fence artifacts against the running
128
+ rails-hyperdrive with a `hyperdrive_version:` requirement, valid gem-wide at
129
+ the manifest top level and per `skills:`/`guidelines:` entry. It is matched
130
+ against the installer's own version rather than the bundle — a constraint
131
+ `gem:` cannot express, since it is an any-match gate across its targets. An
132
+ unsatisfied fence skips the artifact and names the upgrade, both in
133
+ `hyperdrive:init`/`hyperdrive:sync` output and during `bundle install` via the
134
+ bundler plugin.
135
+
136
+ ### Changed
137
+
138
+ - **Breaking (installer skew).** `.hyperdrive/lock.yml` is now written at schema
139
+ version 2, so every machine working on an app that has synced with this
140
+ release needs this release too. Older installers ship no schema guard and
141
+ cannot refuse: a 0.6.0 `init`, `sync`, or bundler-plugin top-up run against a
142
+ lock this release wrote silently degrades instead — it never discovers agents
143
+ or commands, so it orphan-warns their lock entries on every run, drops their
144
+ `disabled:` lists, and rewrites the lock back to version 1. Installed files
145
+ are never deleted. From this release on, the read guard (below) makes the
146
+ same skew halt with the upgrade remedy instead of degrading.
147
+ - **Breaking (bundler plugin).** The `bundler-rails-hyperdrive` plugin gem is
148
+ renamed `bundler-hyperdrive` — its directory, gem name, Gemfile directive
149
+ (`plugin "bundler-hyperdrive"`), and release tag namespace
150
+ (`bundler-hyperdrive/vX.Y.Z`). `hyperdrive:init` writes and matches the new
151
+ directive only; an app carrying the old one gets the new line appended and
152
+ should drop the old. Nothing else changes: the `Bundler::Hyperdrive`
153
+ namespace, the hook, and its behavior are untouched.
154
+ - **Breaking (companion gemspecs).** The two directory overrides move out of
155
+ gemspec metadata and into the gem-root manifest as top-level keys:
156
+ `hyperdrive_skills_dir` → `skills_dir:` in `hyperdrive.yml`, and
157
+ `hyperdrive_skill_templates_dir` → `skill_templates_dir:`. The metadata keys
158
+ are no longer read at all — under either spelling, the `rails_`-prefixed one
159
+ 0.6.0 shipped included, and with no deprecation or dual-read — so a gem still
160
+ declaring them ships from the default roots, and they no longer count as
161
+ companion opt-in signals (the manifest that now carries them is one).
162
+ Gemspec metadata is left as strictly the pre-install surface rubygems
163
+ serves: `hyperdrive_targets`, `hyperdrive_artifacts`, and the
164
+ `hyperdrive_manifest` bootstrap pointer. Discovery resolves both roots
165
+ fail-open like every other manifest value: a non-string or `..`-containing
166
+ value is warned about and the default roots are used (where the metadata keys
167
+ were ignored silently), and a blank one falls back silently. The
168
+ companion-repo rake tasks stay strict and now raise on a manifest that will
169
+ not parse, rather than rendering with default roots over a file the
170
+ installer cannot read. `rake hyperdrive:manifest:check` validates the two
171
+ new keys.
172
+ - **Breaking (companion gemspecs).** The remaining companion-gem gemspec
173
+ metadata keys drop their `rails_` prefix: `rails_hyperdrive_targets` →
174
+ `hyperdrive_targets`, `rails_hyperdrive_artifacts` →
175
+ `hyperdrive_artifacts`, and `rails_hyperdrive_manifest` →
176
+ `hyperdrive_manifest`. The contract is not Rails-specific, and the keys now
177
+ read that way. The old spellings are no longer read, with no deprecated
178
+ alias: a gem still declaring them is not opted in as a companion, its
179
+ manifest override is ignored, and `hyperdrive:discover` — which now queries
180
+ rubygems for `metadata.hyperdrive_targets:*` — no longer surfaces it.
181
+ Companion gems must update their gemspecs.
182
+ - **Breaking (companion manifests).** Version constraints move onto the gate
183
+ members and the sibling `versions:` key is gone. Wherever `gem:` takes a YAML
184
+ list — the bare list and the `any:`/`all:` values alike — a member is now
185
+ either a bare gem name or a single-pair map carrying that member's own
186
+ requirement: `gems: [railties: ">= 7.0"]`. The requirement is a
187
+ `Gem::Requirement` (comma-separated string or YAML list); a pair value of
188
+ `"*"` or nothing means unconstrained. Scalar and comma-separated string forms
189
+ stay name-only, and a pair value is never target-split, so a compound
190
+ requirement like `">= 4.9, < 6"` is passed whole. A `"*"` used as a pair key
191
+ is meaningless, so it is dropped with a warning in every list context; when it
192
+ was the sole member the gate resolves universal.
193
+
194
+ A `versions:` key remaining anywhere it used to be valid (top level,
195
+ `skills:`/`guidelines:` entries, `conditional:` entries) is warned about and
196
+ ignored: the named targets keep gating, unconstrained. Because requirements
197
+ now travel with the targets, an entry's gate replaces the gem-wide default
198
+ **wholesale** — the per-axis inheritance that let a top-level `versions:`
199
+ apply to an entry naming a different `gem:` is gone. `hyperdrive_version:`
200
+ inheritance is unchanged.
201
+
202
+ No compatibility shim: older rails-hyperdrive releases read a pair member as a
203
+ malformed `gem:` and install the artifact ungated.
204
+ - `.hyperdrive/lock.yml` is now read-guarded against its own schema version. The
205
+ lock is git-tracked and shared across branches that pin different
206
+ rails-hyperdrive versions, so an installer can meet a lock a newer one wrote.
207
+ It previously rewrote the file anyway: unknown top-level keys survive the
208
+ round-trip, but anything a newer schema stores inside the keys it recognizes
209
+ — `disabled:` lists for kinds it does not know, or a reshaped `enabled:` or
210
+ `claude_md` — was silently dropped.
211
+ `hyperdrive:init` and `hyperdrive:sync` now fail with the upgrade remedy
212
+ before any content write (`--dry-run` included, and init's bootstrap steps
213
+ still complete), and `bundle install`'s auto-install prints the same reason
214
+ and installs nothing.
215
+ - An artifact that changes destination — a companion renaming a skill, or a
216
+ cross-source name collision appearing or resolving, flipping between
217
+ `.claude/skills/<name>/` and `.claude/skills/<name>--<source_gem>/` — now has
218
+ its old copy removed, supporting files and emptied directories included,
219
+ instead of being left behind as a byte-duplicate that warns on every sync.
220
+ Removal requires the source gem to still be bundled and to have lost no
221
+ artifact to a discovery skip this run, so a broken companion release or a
222
+ version fence never deletes a good install; a locally-modified copy is always
223
+ warned about and left. The bundler plugin's auto-install removes nothing, as
224
+ before.
225
+ - Orphan warnings no longer claim an artifact is "no longer shipped by" a gem
226
+ that is still in the bundle, in both `hyperdrive:sync` output and the
227
+ "need attention" lines printed during `bundle install`.
228
+ - A `disabled:` entry naming a skill by its postfixed name (`foo--gem_a`) now
229
+ opts that source's artifact out permanently, rather than only while the
230
+ collision that produced the postfix exists.
231
+ - `hyperdrive:init`, `hyperdrive:sync`, and `hyperdrive:discover` are Rails
232
+ commands rather than rake tasks, so their flags now work bare —
233
+ `bin/rails hyperdrive:sync --merge` instead of
234
+ `bin/rails hyperdrive:sync -- --merge` — and
235
+ `bin/rails hyperdrive:sync --help` prints usage. The `-- --flag` form keeps
236
+ working, so existing scripts and docs need no change.
237
+
238
+ ### Removed
239
+
240
+ - **BREAKING:** `require "rails/hyperdrive/skill_tasks"`. The companion-repo
241
+ rake tasks are required as `hyperdrive/skill_tasks`; a Rakefile using the old
242
+ spelling raises `LoadError` and needs the one-line change. There is one path,
243
+ under no framework's namespace.
244
+
245
+ - **BREAKING:** the `hyperdrive:*` rake tasks. `bundle exec rake hyperdrive:init`
246
+ (and `:sync` / `:discover`) is no longer available, and the tasks no longer
247
+ appear in `bin/rails -T`. Use `bin/rails hyperdrive:<command>`, which is
248
+ unchanged.
249
+
250
+ ### Fixed
251
+
252
+ - A parseable `hyperdrive_version:` fence now survives an unusable `gem:`,
253
+ both per entry and in the gem-wide defaults. The fence is resolved before
254
+ the gate, so fail-open reads "install ungated unless fenced out": a manifest
255
+ written in a value shape an older installer cannot parse is fenced out of
256
+ that installer instead of installing everywhere unconstrained — the exact
257
+ case `hyperdrive_version:` exists to cover. The two gem-wide defaults are
258
+ independent too: an unusable `gem:` default no longer drops a parseable
259
+ fence, and a malformed fence no longer drops a usable `gem:` default; each
260
+ warns for its own axis. An entry whose *own* `hyperdrive_version:` is
261
+ unparsable still installs ungated and unfenced — the gem-wide fence is not
262
+ substituted for a constraint the entry never asked for.
263
+ - A `conditional:` key spelled as a template-backed supporting file's rendered
264
+ face (`references/x.md`) now gates that template, instead of silently
265
+ matching nothing while the file installed unconditionally. Either spelling —
266
+ the shipped `references/x.md.erb` or the face — resolves to the same gate;
267
+ a manifest carrying both for one file draws a warning and the shipped `.erb`
268
+ spelling wins. `rake hyperdrive:manifest:check` agrees with discovery on
269
+ both points, and no longer reports a face-spelled key as naming nothing
270
+ shipped.
271
+ - A `SKILL.md.erb` that fails to render because it reaches for a helper a
272
+ newer rails-hyperdrive added now reports its `hyperdrive_version:` fence
273
+ rather than a bare `ERB render failed (NameError)`, and that line reaches the
274
+ bundler-plugin surface like every other fence warning.
275
+ - An ordinary gate miss — a well-formed `gem:` whose target simply is not
276
+ bundled — no longer marks its source gem as having lost content, so the
277
+ stale-destination sweep keeps converging for companions that gate different
278
+ skills on different stacks. A renamed skill's old directory is now removed
279
+ instead of being orphan-warned indefinitely.
280
+ - `hyperdrive:sync` sweeps a stale `<dest>.new` sidecar when it removes the
281
+ destination that sidecar belonged to, under the same rule as everywhere else
282
+ (machine-pristine → removed; edited → warned about and left). A pristine
283
+ leftover no longer strands itself or keeps an emptied skill directory alive.
284
+ - `rake hyperdrive:manifest:check` fails when a `hyperdrive_manifest` gemspec
285
+ metadata key names a path that is not a file — previously read as "this gem
286
+ ships no manifest" and reported green, while the dangling key still counted
287
+ as companion opt-in and every artifact installed ungated.
288
+ - `rake hyperdrive:skills:render` / `:check` work for a companion that declares
289
+ neither `hyperdrive_skills_dir` nor `hyperdrive_skill_templates_dir`: the
290
+ content root now defaults to top-level `skills/` (matching discovery) rather
291
+ than to the same lib-convention path as the templates root, which made the
292
+ tasks hard-error with "content dir equals template dir".
293
+ - `init`/`sync` no longer count advisory discovery warnings as dropped
294
+ artifacts. Warnings that dropped shipped content — a whole artifact, or one
295
+ supporting file of one — print under `discovery skipped N item(s):` (was
296
+ `discovery skipped N artifact(s):`, which counted every warning and named
297
+ them all artifacts); everything that installed anyway prints under
298
+ `discovery reported M advisory warning(s):`.
299
+ - The bundler-plugin hook now prints discovery advisories during
300
+ `bundle install`, so a retired `versions:` key — content this release
301
+ deliberately installs unconstrained — is no longer silent there. Ordinary
302
+ artifact skips stay with `init`/`sync`.
303
+ - The `installing ungated` warning on a malformed manifest entry that keeps its
304
+ fence now reads `installing ungated unless fenced out`, so it can no longer
305
+ contradict a fence-skip line for the same artifact. Only an unparsable
306
+ `hyperdrive_version:` says `installing ungated and unfenced`.
307
+ - The warning for a manifest that will not parse now names the parser error
308
+ (`ignoring manifest hyperdrive.yml: malformed YAML (<reason>)`), matching what
309
+ `rake hyperdrive:manifest:check` already reported. Behaviour is unchanged:
310
+ gating that cannot be read still resolves to an absent manifest.
311
+
10
312
  ## [0.6.0] - 2026-08-17
11
313
 
12
314
  ### Changed
@@ -452,7 +754,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
452
754
  - `hyperdrive:init` generator that installs architecture skills and auto-discovers
453
755
  per-gem skills.
454
756
 
455
- [Unreleased]: https://github.com/rails-hyperdrive/rails-hyperdrive/compare/v0.6.0...HEAD
757
+ [Unreleased]: https://github.com/rails-hyperdrive/rails-hyperdrive/compare/v0.8.0...HEAD
758
+ [0.8.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.8.0
759
+ [0.7.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.7.0
456
760
  [0.6.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.6.0
457
761
  [0.5.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.5.0
458
762
  [0.4.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.4.0
data/README.md CHANGED
@@ -13,13 +13,15 @@
13
13
  Rails Hyperdrive is a development-only Rails engine for working on Rails apps with AI coding agents. It gives the agent two things it can't get from source alone: live answers from the booted app, and guidance specific to the gems and versions in the bundle.
14
14
 
15
15
  - **Live introspection.** The engine mounts an [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server at `http://localhost:3000/_hyperdrive/mcp` with **8 tools** that answer from the running app itself: eval Ruby, query the DB (read-only), tail logs, list models and routes, jump to source, look up docs, snapshot the stack. The agent asks the router instead of grepping `routes.rb`, and reads the live schema instead of replaying migrations.
16
- - **Stack-specific knowledge.** `bin/rails hyperdrive:init` discovers **skills** and **guidelines** shipped by companion gems and installs only the ones matching your Gemfile: guidance targeting Sidekiq, for example, lands only if your app bundles Sidekiq, at a version the guidance covers.
16
+ - **Stack-specific knowledge.** `bin/rails hyperdrive:init` discovers **skills**, **guidelines**, **agents**, and **commands** shipped by companion gems and installs only the ones matching your Gemfile: guidance targeting Sidekiq, for example, lands only if your app bundles Sidekiq, at a version the guidance covers.
17
17
 
18
18
  **rails-hyperdrive is the mechanism; companion gems are the content.** The gem itself ships no skills or guidelines, only the contract and the discovery/install engine. Content reaches your app three ways:
19
19
 
20
20
  - **Native support**: the library gem itself ships a top-level `skills/` directory in the [skills.sh](https://www.skills.sh) layout and opts in as a hyperdrive companion. The preferred route when the maintainer is on board: one gem, one source of truth.
21
21
  - **Adopted skill repos**: an existing skills.sh skill repo packaged as a gem, content untouched, gating declared on the side.
22
- - **Dedicated companion gems**: third-party guidance for a library that ships none itself. A name like `rails-hyperdrive-<library>` or `<library>-skills` keeps the gem legible in a Gemfile, but naming plays no part in discovery.
22
+ - **Dedicated companion gems**: third-party guidance for a library that ships none itself.
23
+
24
+ On the last two routes, name the gem `rails-hyperdrive-<name>`: the prefix tells a Gemfile reader exactly what the gem is, though naming plays no part in discovery.
23
25
 
24
26
  ---
25
27
 
@@ -82,22 +84,24 @@ The generated `.mcp.json` points at `http://localhost:3000<mount>/mcp`. If your
82
84
 
83
85
  Plus two MCP resources: `hyperdrive://stack-profile` (JSON snapshot of your resolved stack) and `hyperdrive://skills/{name}` (the markdown body of each installed skill). The skill list is enumerated at server boot and the stack snapshot is memoized per process, so a newly installed skill or a changed bundle reaches these two only after a dev-server restart.
84
86
 
85
- ### Two kinds of knowledge
87
+ ### Four kinds of knowledge
86
88
 
87
- Companion gems ship two artifact types, tuned for how agents consume context:
89
+ Companion gems ship four artifact kinds, tuned for how agents consume context:
88
90
 
89
91
  - **Skills**: *lazy*. Loaded on demand via Claude Code's native description matcher. Procedural knowledge: "how to write an idempotent Sidekiq job". Installed to `.claude/skills/<name>/SKILL.md`, optionally with supporting files (references, examples, workflows) alongside.
90
92
  - **Guidelines**: *eager*. Always in context via a single `@`-include from `CLAUDE.md`. Declarative facts: "this app uses ActionPolicy, not Pundit". Installed to `.claude/hyperdrive/guidelines/<name>.md`.
93
+ - **Agents**: Claude Code subagents, invoked by name or by task match. Installed to `.claude/agents/<name>.md`.
94
+ - **Commands**: Claude Code slash commands. Installed to `.claude/commands/<name>.md`, so a file named `analyze.md` becomes `/analyze`.
91
95
 
92
96
  A companion gem declares in its manifest which gem each artifact targets and at which versions, so what lands in your app is what matches your `Gemfile.lock`, and nothing aimed at a library or version you don't run.
93
97
 
94
- With no companion gems, `hyperdrive:init` sets up just the server plumbing (`.mcp.json`, the engine mount, the lockfile) and puts **nothing** into your agent's context window.
98
+ With no companion gems, `hyperdrive:init` sets up just the server plumbing (`.mcp.json`, the engine mount, the lockfile) and puts **nothing** into your agent's context window. The two halves are independently skippable: `--skip-mcp` installs content but no MCP plumbing, `--skip-content` the reverse. Neither removes anything already in place.
95
99
 
96
100
  ---
97
101
 
98
102
  ## Staying in sync
99
103
 
100
- **After `bundle install`: automatic.** `hyperdrive:init` registers the [`bundler-rails-hyperdrive`](bundler-rails-hyperdrive/) Bundler plugin in your Gemfile. From then on, adding a companion gem lands its artifacts on that very `bundle install`, with no extra command to run. The plugin is additive only (it never touches an existing file); version bumps and orphaned artifacts are only reported, with a pointer to `hyperdrive:sync`.
104
+ **After `bundle install`: automatic.** `hyperdrive:init` registers the [`bundler-hyperdrive`](bundler-hyperdrive/) Bundler plugin in your Gemfile. From then on, adding a companion gem lands its artifacts on that very `bundle install`, with no extra command to run. The plugin is additive only (it never touches an existing file); version bumps and orphaned artifacts are only reported, with a pointer to `hyperdrive:sync`.
101
105
 
102
106
  **`bin/rails hyperdrive:sync`: on demand.** Run it any time (e.g. after `bundle update`) to refresh installed content to the current bundle. It touches no bootstrap artifact and leaves locally modified files untouched (skip + warn). When you *have* edited an installed file and its gem ships a new version, three mutually exclusive flags reconcile the two:
103
107
 
@@ -127,6 +131,8 @@ CLAUDE.md # user-owned; ONE injected line: @.claude
127
131
  .claude/skills/<name>/
128
132
  SKILL.md # companion-shipped, installed verbatim (frontmatter included)
129
133
  <supporting files> # optional extras (references/, examples/, …), installed as shipped (*.md.erb rendered)
134
+ .claude/agents/<name>.md # companion-shipped subagent, installed verbatim
135
+ .claude/commands/<name>.md # companion-shipped slash command, installed verbatim
130
136
  .hyperdrive/lock.yml # git-tracked manifest (source gem, version, content hash)
131
137
  ```
132
138
 
@@ -148,6 +154,10 @@ disabled:
148
154
  - sidekiq-idempotency
149
155
  guidelines:
150
156
  - jobs-sidekiq
157
+ agents:
158
+ - sidekiq-reviewer
159
+ commands:
160
+ - analyze
151
161
  ```
152
162
 
153
163
  A disabled artifact is never installed, and one already on disk is removed on the next `hyperdrive:init` or `hyperdrive:sync`, but **only if you haven't edited it**. A locally modified file is reported and left alone, for you to delete when you're ready. Disabling a skill removes its shipped supporting files under the same per-file rule; files you created yourself in the skill directory survive and keep the directory alive. Disabling a guideline also drops its line from `index.md`, so it leaves eager context along with the file.
@@ -181,10 +191,14 @@ Ship markdown, declare what it targets, publish. That's the whole contract:
181
191
 
182
192
  ```
183
193
  skills/<name>/SKILL.md # skill (dir-per-skill, may ship supporting files)
194
+ agents/<name>.md # agent (flat file)
195
+ commands/<name>.md # command (flat file)
184
196
  lib/<gem_name>/hyperdrive/guidelines/<name>.md # guideline (flat file)
185
197
  ```
186
198
 
187
- Top-level `skills/` is the recommended home for skill content: it is the tool-agnostic face of your gem, readable by skills.sh and plain git-clone consumers as well as hyperdrive, and it is scanned by default. `lib/<gem_name>/hyperdrive/` is the hyperdrive-specific root: guidelines, and ERB skill templates (`SKILL.md.erb`, which must stay out of `skills/` so raw ERB never reaches generic consumers). Plain skills shipped under it remain scanned as well.
199
+ `<gem_name>` is your gem's name exactly as published, dashes and all `rails-hyperdrive-sidekiq` ships guidelines under `lib/rails-hyperdrive-sidekiq/hyperdrive/guidelines/`, not `lib/rails/hyperdrive/sidekiq/`.
200
+
201
+ Top-level `skills/`, `agents/`, and `commands/` are the tool-agnostic face of your gem — the same sibling layout `.claude/` has, so relative links between them keep resolving once installed. They are readable by skills.sh and plain git-clone consumers as well as hyperdrive, and are scanned once your gem has opted in (below). `lib/<gem_name>/hyperdrive/` is the hyperdrive-specific root: guidelines, and ERB skill templates (`SKILL.md.erb`, which must stay out of `skills/` so raw ERB never reaches generic consumers). Plain skills shipped under it remain scanned as well. Guidelines, agents, and commands may ship as `<name>.md.erb` in their own roots — rendered against the app's bundle at discovery and installed as `<name>.md`, with a static file of the same name taking precedence.
188
202
 
189
203
  Frontmatter is pure skills.sh: only `name` and `description` are read, so a skill repo's content integrates without modification:
190
204
 
@@ -195,25 +209,42 @@ description: Background job conventions for Sidekiq.
195
209
  ---
196
210
  ```
197
211
 
198
- Gating (which bundles an artifact installs into) lives in a `hyperdrive.yml` manifest at the gem root (or at the path named by a `rails_hyperdrive_manifest` gemspec metadata key), never in the content. Every key is optional; no manifest (or an empty one) means everything installs universally:
212
+ Agents follow the same contract. Commands are the exception: their frontmatter is optional and never validated, and their identity is the filename stem.
213
+
214
+ Gating (which bundles an artifact installs into) lives in a `hyperdrive.yml` manifest at the gem root (or at the path named by a `hyperdrive_manifest` gemspec metadata key), never in the content. Every key is optional; no manifest (or an empty one) means everything installs universally:
199
215
 
200
216
  ```yaml
201
- gem: sidekiq # gem-wide default: TARGET gem(s), resolved + version-matched in the bundle
202
- versions: ">= 7.0, < 9.0" # gem-wide default: Gem::Requirement matched against the target gem
217
+ gems: # gem-wide default: TARGET gem(s), resolved in the bundle ("gem:" is an alias)
218
+ - sidekiq: ">= 7.0, < 9.0" # a list member is a bare name, or a name: requirement pair
203
219
  skills: # per-skill overrides, keyed by skill dir relative to its skills root
204
220
  jobs-sidekiq:
205
- versions: ">= 8.0"
221
+ gems:
222
+ - sidekiq: ">= 8.0"
223
+ hyperdrive_version: ">= 0.7" # require a minimum rails-hyperdrive for this artifact
206
224
  guidelines: # per-guideline overrides, keyed by filename
207
225
  jobs.md:
208
226
  gem: sidekiq
227
+ agents: # per-agent overrides, keyed by filename
228
+ sidekiq-reviewer.md:
229
+ gem: sidekiq
230
+ commands: # per-command overrides, keyed by filename
231
+ command_prefix: sidekiq # optional; every command installs as <prefix>-<filename>
232
+ analyze.md:
233
+ gem: sidekiq
209
234
  ```
210
235
 
211
- Shipping a `hyperdrive.yml` (or declaring `rails_hyperdrive_manifest`) opts your gem in as a companion. Also declare your targets in gemspec metadata. That opts your gem in too, and it is the pre-install targeting signal (how `hyperdrive:discover` suggests you before anyone installs you):
236
+ A gate naming several targets can be written as a map with one of `any:`/`all:` `gems: {any: [sidekiq, solid_queue]}` installs when either is bundled, `gems: {all: [devise, pundit]}` only when both are. A bare list is shorthand for `any:`, and an entry's gate replaces the gem-wide default wholesale.
237
+
238
+ `hyperdrive_version:` is valid at the top level too, and is matched against the running rails-hyperdrive rather than the bundle — the sanctioned way to fence content that needs a newer installer. It fences on whichever gem implements artifact discovery and install — today rails-hyperdrive — and that version numbering is guaranteed continuous across any future restructuring of the gem, so a fence like `">= 0.8"` keeps its meaning permanently.
239
+
240
+ Shipping a `hyperdrive.yml` (or declaring `hyperdrive_manifest`) opts your gem in as a companion. Also declare your targets in gemspec metadata. That opts your gem in too, and it is the pre-install targeting signal (how `hyperdrive:discover` suggests you before anyone installs you):
212
241
 
213
242
  ```ruby
214
- spec.metadata["rails_hyperdrive_targets"] = "sidekiq"
243
+ spec.metadata["hyperdrive_targets"] = "sidekiq"
215
244
  ```
216
245
 
246
+ Companion repos get author-side CI checks by adding `require "hyperdrive/skill_tasks"` to the `Rakefile`: `rake hyperdrive:skills:check` keeps generated skill content in step with its templates, and `rake hyperdrive:manifest:check` lints `hyperdrive.yml` strictly.
247
+
217
248
  [docs/COMPANION_GEMS.md](docs/COMPANION_GEMS.md) has the full contract: multi-target artifacts, multi-file skills, per-file gem gating, ERB-templated content, the template/content paired layout that also serves `npx skills` and git-clone consumers, and the collision and dedup rules.
218
249
 
219
250
  ---
@@ -3,14 +3,21 @@ Description:
3
3
  - adds the rails-hyperdrive server to .mcp.json (Claude Code config),
4
4
  merging into any servers already configured there
5
5
  - mounts Rails::Hyperdrive::Engine at /_hyperdrive in config/routes.rb (idempotent)
6
- - discovers companion-gem skills + guidelines in the bundle and installs
7
- them (skills to .claude/skills/, guidelines to
8
- .claude/hyperdrive/guidelines/)
6
+ - registers the bundler-hyperdrive plugin in the Gemfile, so newly
7
+ bundled companion gems install their artifacts on bundle install
8
+ - adds a .gitignore rule for the hyperdrive:discover cache
9
+ - discovers companion-gem skills, guidelines, agents, and commands in the
10
+ bundle and installs them (skills to .claude/skills/, guidelines to
11
+ .claude/hyperdrive/guidelines/, agents to .claude/agents/, commands to
12
+ .claude/commands/)
9
13
  - maintains the .claude/hyperdrive/index.md aggregator and injects one
10
14
  @-include line into CLAUDE.md — both only while a companion gem ships
11
15
  a guideline, and both removed when the last one goes
12
16
  - tracks everything in .hyperdrive/lock.yml
13
17
 
18
+ The .mcp.json entry and the engine mount are skippable with --skip-mcp;
19
+ existing MCP configuration is left untouched either way.
20
+
14
21
  Re-running re-syncs content and leaves locally-modified files untouched
15
22
  (skip + warn). Routine content refresh is bin/rails hyperdrive:sync
16
23
  (see its --merge, --sidecar, and --overwrite flags for reconciling
@@ -20,4 +27,5 @@ Examples:
20
27
  bin/rails hyperdrive:init
21
28
  bin/rails hyperdrive:init --mount-at /admin/hyperdrive
22
29
  bin/rails hyperdrive:init --skip-content
30
+ bin/rails hyperdrive:init --skip-mcp
23
31
  bin/rails hyperdrive:init --dry-run
@@ -20,14 +20,15 @@ module Rails
20
20
  MCP_SERVER_KEY = "rails-hyperdrive".freeze
21
21
 
22
22
  GEMFILE = "Gemfile".freeze
23
- BUNDLER_PLUGIN = "bundler-rails-hyperdrive".freeze
23
+ BUNDLER_PLUGIN = "bundler-hyperdrive".freeze
24
24
 
25
25
  # No templates are rendered; source_root exists so Rails resolves the
26
26
  # sibling USAGE file for `--help`.
27
27
  source_root File.expand_path("templates", __dir__)
28
28
 
29
29
  class_option :mount_at, type: :string, default: DEFAULT_MOUNT_AT, desc: "Engine mount path."
30
- class_option :skip_content, type: :boolean, default: false, desc: "Skip all .claude content, CLAUDE.md, and the lockfile; write only .mcp.json and the mount."
30
+ class_option :skip_content, type: :boolean, default: false, desc: "Skip all .claude content, CLAUDE.md, and the lockfile; leave the .mcp.json, .gitignore, Gemfile, and mount steps."
31
+ class_option :skip_mcp, type: :boolean, default: false, desc: "Skip MCP setup entirely; write no .mcp.json entry and no engine mount."
31
32
  class_option :dry_run, type: :boolean, default: false, desc: "Show what would change; write nothing."
32
33
 
33
34
  def verify_environment
@@ -41,6 +42,8 @@ module Rails
41
42
  # The write is forced: Thor's conflict prompt would otherwise block the
42
43
  # run waiting on stdin.
43
44
  def write_mcp_json
45
+ return if options[:skip_mcp]
46
+
44
47
  existing = mcp_json_on_disk
45
48
  document = existing ? parse_mcp_json(existing) : {}
46
49
  return if document.nil?
@@ -80,6 +83,8 @@ module Rails
80
83
  end
81
84
 
82
85
  def mount_engine
86
+ return if options[:skip_mcp]
87
+
83
88
  routes_file = "config/routes.rb"
84
89
  unless File.exist?(::Rails.root.join(routes_file))
85
90
  say_status :skip, "no #{routes_file} found; skipping engine mount", :yellow
@@ -107,9 +112,16 @@ module Rails
107
112
  def print_summary
108
113
  say ""
109
114
  say_status :done, "hyperdrive initialized", :green
110
- say " Mount: #{mount_path} (in config/routes.rb)"
111
- say " Server: #{::Rails::Hyperdrive::McpServer::TOOLS.size} MCP tools at http://localhost:3000#{mount_path}/mcp"
115
+ if options[:skip_mcp]
116
+ say " MCP: skipped (--skip-mcp)"
117
+ else
118
+ say " Mount: #{mount_path} (in config/routes.rb)"
119
+ say " Server: #{::Rails::Hyperdrive::McpServer::TOOLS.size} MCP tools at http://localhost:3000#{mount_path}/mcp"
120
+ end
112
121
  runner.summary_lines.each { |line| say line } unless options[:skip_content]
122
+ # Every next step is about reaching the MCP endpoint.
123
+ return if options[:skip_mcp]
124
+
113
125
  say ""
114
126
  say " Next steps:"
115
127
  say " 1. bin/rails server"
@@ -4,8 +4,8 @@ module Rails
4
4
  module Generators
5
5
  module Hyperdrive
6
6
  module InstallSummary
7
- KIND_WIDTH = "guideline".length
8
- KIND_ORDER = %w[skill guideline].freeze
7
+ KIND_ORDER = ::Rails::Hyperdrive::InstallLayout.content_kinds.map(&:lock_kind).freeze
8
+ KIND_WIDTH = KIND_ORDER.map(&:length).max
9
9
 
10
10
  module_function
11
11
 
@@ -38,7 +38,7 @@ module Rails
38
38
 
39
39
  def installed_counts(entries)
40
40
  counts = entries.group_by { |e| e.kind.to_s }.transform_values(&:size)
41
- "Installed #{quantify(counts["skill"].to_i, "skill")}, #{quantify(counts["guideline"].to_i, "guideline")}"
41
+ "Installed #{KIND_ORDER.map { |kind| quantify(counts[kind].to_i, kind) }.join(", ")}"
42
42
  end
43
43
 
44
44
  def group_by_source(entries)
@@ -1,9 +1,10 @@
1
1
  Description:
2
2
  Syncs Rails Hyperdrive content to match the current bundle (run after
3
3
  `bundle update` or after adding a companion gem):
4
- - discovers companion-gem skills + guidelines in the bundle and installs
5
- them (skills to .claude/skills/, guidelines to
6
- .claude/hyperdrive/guidelines/)
4
+ - discovers companion-gem skills, guidelines, agents, and commands in the
5
+ bundle and installs them (skills to .claude/skills/, guidelines to
6
+ .claude/hyperdrive/guidelines/, agents to .claude/agents/, commands to
7
+ .claude/commands/)
7
8
  - maintains the .claude/hyperdrive/index.md aggregator and the single
8
9
  @-include line in CLAUDE.md — both only while a companion gem ships a
9
10
  guideline, and both removed when the last one goes
@@ -24,8 +25,9 @@ Description:
24
25
  upstream wholesale. Either way the next sync is quiet: the lock already
25
26
  records the delivered upstream, so the same version is never re-offered.
26
27
 
27
- Touches no bootstrap artifact: .mcp.json, the engine mount, the optional
28
- initializer, and .gitignore belong to hyperdrive:init and are left alone.
28
+ Touches no bootstrap artifact: .mcp.json, the engine mount, the Gemfile
29
+ plugin directive, and .gitignore belong to hyperdrive:init and are left
30
+ alone.
29
31
 
30
32
  Examples:
31
33
  bin/rails hyperdrive:sync