rails-hyperdrive 0.6.0 → 0.7.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: 19c65882e51a2efb328a453fec5a561af7b9b012e09623f39eba055b7898bcab
4
+ data.tar.gz: e2bdf0a95fe1ed81db2bb56f0f649c924f502b28a7577f6931778ebb95ca81c0
5
5
  SHA512:
6
- metadata.gz: 891f3bcced1126be5b1eb48d456e369b05dcb87cda0f87c82b5d27d7eeea9e57c4ed39ca7549f5a573b13ef800efe7b56f75c39c6cfb544f15cfb1518a9b0d82
7
- data.tar.gz: 218fe629a71711e1915af9ad10a1cc58923ee9dc41ab16514224191526d19f249ed2df6658c5d281c25b1fa9b6430cfd75c163ee4b9d669853164f5889427780
6
+ metadata.gz: e2f083bf83220b970052d44b95c11f6d69b26a58801094cdce349de803b4497983925ba4082d1167e17226d0e5511ac328dc644d71239f7c5694c853f49f6dad
7
+ data.tar.gz: 955afb3426b981ae68136de0f8f3e1d8ecceb70abcb7a56579b7aaeeb00824915cc273b00244315ba2d49d94a8d0a73bfbe7e68d5c5750a5830a5e1fbaeba52e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,270 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - **Two new companion artifact kinds: agents and commands.** A companion gem can
15
+ now ship Claude Code subagents from `agents/*.md` (installed to
16
+ `.claude/agents/<name>.md`) and slash commands from `commands/*.md` (installed
17
+ to `.claude/commands/<name>.md`), alongside its skills and guidelines. Both
18
+ are flat single files that install byte-identical to what the gem ships, and
19
+ both ride the whole existing machine: gating, the `hyperdrive_version:` fence,
20
+ sha-based drift, `--overwrite`/`--sidecar`/`--merge`, `disabled:`, the stale
21
+ sweep, cross-source collision postfixing, and the additive top-up on
22
+ `bundle install`. Agents require `name` + `description` frontmatter like a
23
+ skill; a command's frontmatter is optional and never validated, and its
24
+ identity is its filename stem, so `commands/analyze.md` becomes `/analyze`.
25
+ Neither is wired into `CLAUDE.md` or `index.md` — Claude Code registers them
26
+ by file presence. Installing flat (no per-gem subdirectory) keeps the
27
+ `skills/`/`agents/`/`commands/` sibling geometry a gem ships, so relative
28
+ links between them resolve unchanged after install.
29
+ - Manifest additions in `hyperdrive.yml`: `agents:` and `commands:` gating
30
+ sections keyed by filename, taking the same values `guidelines:` entries do;
31
+ `agents_dir:` and `commands_dir:` to name additional roots (resolved like
32
+ `skills_dir:`); and `command_prefix:`, an optional gem-wide scalar inside
33
+ `commands:` that installs every command of that gem as `<prefix>-<filename>`,
34
+ for a companion that also ships as a Claude Code plugin and wants its
35
+ `/name` namespaced. `rake hyperdrive:manifest:check` lints all of them.
36
+
37
+ - `require "hyperdrive/skill_tasks"` is the require path for the companion-repo
38
+ rake tasks (`hyperdrive:skills:render`, `hyperdrive:skills:check`,
39
+ `hyperdrive:manifest:check`) — framework-neutral, since the tasks run in a
40
+ plain gem repo with no Rails involved.
41
+ - The `hyperdrive_version:` fence now carries a stated guarantee for companion
42
+ authors: it fences on whichever gem implements artifact discovery and install
43
+ — today rails-hyperdrive — and that version numbering is guaranteed
44
+ continuous across any future restructuring of the gem, so a fence like
45
+ `">= 0.8"` keeps its meaning permanently.
46
+ - `rake hyperdrive:manifest:check`, a strict author-side lint of a companion
47
+ gem's `hyperdrive.yml`, on the same companion-repo rake surface as
48
+ `hyperdrive:skills:*` (add `require "hyperdrive/skill_tasks"` to the
49
+ Rakefile; takes the same optional gemspec-path argument). Where the installer
50
+ is permissive so a manifest written for a newer schema never blocks an
51
+ install, the lint fails: unknown keys at every level — the top level,
52
+ `skills:`/`guidelines:` entries, and `conditional:` entries — plus any
53
+ `gem:`/`gems:` or `hyperdrive_version:` value the installer cannot parse, and
54
+ `skills:`/`guidelines:`/`conditional:` keys naming nothing the gem ships. The
55
+ retired `versions:` key and its `version:` near-miss are named pointedly.
56
+ A manifest that lints clean draws no gating warning at install time.
57
+ - A template/content-paired skill's supporting files can be templated too: a
58
+ `*.md.erb` in the template directory renders against the app's bundle and
59
+ installs as `x.md`, so generic skills.sh consumers never copy raw ERB. Within
60
+ a paired skill a template-side file owns its target path — the content
61
+ directory's same-named file never installs, whether the template renders, is
62
+ gated out by `conditional:`, or fails to render.
63
+ `rake hyperdrive:skills:render` now writes each supporting template's
64
+ fail-open canonical face into the paired content directory, and
65
+ `rake hyperdrive:skills:check` byte-gates those faces and fails on any
66
+ `*.md.erb` found under a public skills root. A supporting `*.md.erb` shipped
67
+ under a public skills root still renders, but draws a warning steering it to
68
+ the template directory.
69
+ - `hyperdrive:init --skip-mcp` skips MCP setup entirely: no `rails-hyperdrive`
70
+ entry in `.mcp.json` and no engine mount in `config/routes.rb`. Content
71
+ install, the discover-cache `.gitignore` rule, and the bundler-plugin Gemfile
72
+ directive all still run, and the summary reports `MCP: skipped (--skip-mcp)`
73
+ in place of the mount/server lines. Like `--skip-content`, it only suppresses
74
+ writes — existing MCP configuration is left untouched — and the two flags
75
+ combine.
76
+ - `gems:` is an exact alias of `gem:` at every position the key is read —
77
+ gem-wide defaults, `skills:`/`guidelines:` entries, and per-file
78
+ `conditional:` entries — for every value shape. A map carrying both keys is a
79
+ stylistic slip rather than an error: `gems:` wins, with a warning.
80
+ - Companion-manifest `gem:` gating accepts a map with exactly one of `any:` or
81
+ `all:`, so an artifact can require *every* listed target rather than any one
82
+ of them: `gems: {all: [devise, pundit]}`. `any:` is an explicit spelling of the
83
+ existing any-match, which every bare form (single name, comma-separated
84
+ string, YAML list, `"*"`) keeps by default. The map values take those same
85
+ flat forms, and the form is accepted everywhere `gem:` is — gem-wide defaults,
86
+ `skills:`/`guidelines:` entries, and per-file `conditional:` entries. A `"*"`
87
+ inside `all:` is always satisfied, so it is dropped with a warning; a
88
+ malformed map takes the usual fail-open path (warn, install ungated).
89
+ - Companion manifests can version-fence artifacts against the running
90
+ rails-hyperdrive with a `hyperdrive_version:` requirement, valid gem-wide at
91
+ the manifest top level and per `skills:`/`guidelines:` entry. It is matched
92
+ against the installer's own version rather than the bundle — a constraint
93
+ `gem:` cannot express, since it is an any-match gate across its targets. An
94
+ unsatisfied fence skips the artifact and names the upgrade, both in
95
+ `hyperdrive:init`/`hyperdrive:sync` output and during `bundle install` via the
96
+ bundler plugin.
97
+
98
+ ### Changed
99
+
100
+ - **Breaking (installer skew).** `.hyperdrive/lock.yml` is now written at schema
101
+ version 2, so every machine working on an app that has synced with this
102
+ release needs this release too. Older installers ship no schema guard and
103
+ cannot refuse: a 0.6.0 `init`, `sync`, or bundler-plugin top-up run against a
104
+ lock this release wrote silently degrades instead — it never discovers agents
105
+ or commands, so it orphan-warns their lock entries on every run, drops their
106
+ `disabled:` lists, and rewrites the lock back to version 1. Installed files
107
+ are never deleted. From this release on, the read guard (below) makes the
108
+ same skew halt with the upgrade remedy instead of degrading.
109
+ - **Breaking (bundler plugin).** The `bundler-rails-hyperdrive` plugin gem is
110
+ renamed `bundler-hyperdrive` — its directory, gem name, Gemfile directive
111
+ (`plugin "bundler-hyperdrive"`), and release tag namespace
112
+ (`bundler-hyperdrive/vX.Y.Z`). `hyperdrive:init` writes and matches the new
113
+ directive only; an app carrying the old one gets the new line appended and
114
+ should drop the old. Nothing else changes: the `Bundler::Hyperdrive`
115
+ namespace, the hook, and its behavior are untouched.
116
+ - **Breaking (companion gemspecs).** The two directory overrides move out of
117
+ gemspec metadata and into the gem-root manifest as top-level keys:
118
+ `hyperdrive_skills_dir` → `skills_dir:` in `hyperdrive.yml`, and
119
+ `hyperdrive_skill_templates_dir` → `skill_templates_dir:`. The metadata keys
120
+ are no longer read at all — under either spelling, the `rails_`-prefixed one
121
+ 0.6.0 shipped included, and with no deprecation or dual-read — so a gem still
122
+ declaring them ships from the default roots, and they no longer count as
123
+ companion opt-in signals (the manifest that now carries them is one).
124
+ Gemspec metadata is left as strictly the pre-install surface rubygems
125
+ serves: `hyperdrive_targets`, `hyperdrive_artifacts`, and the
126
+ `hyperdrive_manifest` bootstrap pointer. Discovery resolves both roots
127
+ fail-open like every other manifest value: a non-string or `..`-containing
128
+ value is warned about and the default roots are used (where the metadata keys
129
+ were ignored silently), and a blank one falls back silently. The
130
+ companion-repo rake tasks stay strict and now raise on a manifest that will
131
+ not parse, rather than rendering with default roots over a file the
132
+ installer cannot read. `rake hyperdrive:manifest:check` validates the two
133
+ new keys.
134
+ - **Breaking (companion gemspecs).** The remaining companion-gem gemspec
135
+ metadata keys drop their `rails_` prefix: `rails_hyperdrive_targets` →
136
+ `hyperdrive_targets`, `rails_hyperdrive_artifacts` →
137
+ `hyperdrive_artifacts`, and `rails_hyperdrive_manifest` →
138
+ `hyperdrive_manifest`. The contract is not Rails-specific, and the keys now
139
+ read that way. The old spellings are no longer read, with no deprecated
140
+ alias: a gem still declaring them is not opted in as a companion, its
141
+ manifest override is ignored, and `hyperdrive:discover` — which now queries
142
+ rubygems for `metadata.hyperdrive_targets:*` — no longer surfaces it.
143
+ Companion gems must update their gemspecs.
144
+ - **Breaking (companion manifests).** Version constraints move onto the gate
145
+ members and the sibling `versions:` key is gone. Wherever `gem:` takes a YAML
146
+ list — the bare list and the `any:`/`all:` values alike — a member is now
147
+ either a bare gem name or a single-pair map carrying that member's own
148
+ requirement: `gems: [railties: ">= 7.0"]`. The requirement is a
149
+ `Gem::Requirement` (comma-separated string or YAML list); a pair value of
150
+ `"*"` or nothing means unconstrained. Scalar and comma-separated string forms
151
+ stay name-only, and a pair value is never target-split, so a compound
152
+ requirement like `">= 4.9, < 6"` is passed whole. A `"*"` used as a pair key
153
+ is meaningless, so it is dropped with a warning in every list context; when it
154
+ was the sole member the gate resolves universal.
155
+
156
+ A `versions:` key remaining anywhere it used to be valid (top level,
157
+ `skills:`/`guidelines:` entries, `conditional:` entries) is warned about and
158
+ ignored: the named targets keep gating, unconstrained. Because requirements
159
+ now travel with the targets, an entry's gate replaces the gem-wide default
160
+ **wholesale** — the per-axis inheritance that let a top-level `versions:`
161
+ apply to an entry naming a different `gem:` is gone. `hyperdrive_version:`
162
+ inheritance is unchanged.
163
+
164
+ No compatibility shim: older rails-hyperdrive releases read a pair member as a
165
+ malformed `gem:` and install the artifact ungated.
166
+ - `.hyperdrive/lock.yml` is now read-guarded against its own schema version. The
167
+ lock is git-tracked and shared across branches that pin different
168
+ rails-hyperdrive versions, so an installer can meet a lock a newer one wrote.
169
+ It previously rewrote the file anyway: unknown top-level keys survive the
170
+ round-trip, but anything a newer schema stores inside the keys it recognizes
171
+ — `disabled:` lists for kinds it does not know, or a reshaped `enabled:` or
172
+ `claude_md` — was silently dropped.
173
+ `hyperdrive:init` and `hyperdrive:sync` now fail with the upgrade remedy
174
+ before any content write (`--dry-run` included, and init's bootstrap steps
175
+ still complete), and `bundle install`'s auto-install prints the same reason
176
+ and installs nothing.
177
+ - An artifact that changes destination — a companion renaming a skill, or a
178
+ cross-source name collision appearing or resolving, flipping between
179
+ `.claude/skills/<name>/` and `.claude/skills/<name>--<source_gem>/` — now has
180
+ its old copy removed, supporting files and emptied directories included,
181
+ instead of being left behind as a byte-duplicate that warns on every sync.
182
+ Removal requires the source gem to still be bundled and to have lost no
183
+ artifact to a discovery skip this run, so a broken companion release or a
184
+ version fence never deletes a good install; a locally-modified copy is always
185
+ warned about and left. The bundler plugin's auto-install removes nothing, as
186
+ before.
187
+ - Orphan warnings no longer claim an artifact is "no longer shipped by" a gem
188
+ that is still in the bundle, in both `hyperdrive:sync` output and the
189
+ "need attention" lines printed during `bundle install`.
190
+ - A `disabled:` entry naming a skill by its postfixed name (`foo--gem_a`) now
191
+ opts that source's artifact out permanently, rather than only while the
192
+ collision that produced the postfix exists.
193
+ - `hyperdrive:init`, `hyperdrive:sync`, and `hyperdrive:discover` are Rails
194
+ commands rather than rake tasks, so their flags now work bare —
195
+ `bin/rails hyperdrive:sync --merge` instead of
196
+ `bin/rails hyperdrive:sync -- --merge` — and
197
+ `bin/rails hyperdrive:sync --help` prints usage. The `-- --flag` form keeps
198
+ working, so existing scripts and docs need no change.
199
+
200
+ ### Removed
201
+
202
+ - **BREAKING:** `require "rails/hyperdrive/skill_tasks"`. The companion-repo
203
+ rake tasks are required as `hyperdrive/skill_tasks`; a Rakefile using the old
204
+ spelling raises `LoadError` and needs the one-line change. There is one path,
205
+ under no framework's namespace.
206
+
207
+ - **BREAKING:** the `hyperdrive:*` rake tasks. `bundle exec rake hyperdrive:init`
208
+ (and `:sync` / `:discover`) is no longer available, and the tasks no longer
209
+ appear in `bin/rails -T`. Use `bin/rails hyperdrive:<command>`, which is
210
+ unchanged.
211
+
212
+ ### Fixed
213
+
214
+ - A parseable `hyperdrive_version:` fence now survives an unusable `gem:`,
215
+ both per entry and in the gem-wide defaults. The fence is resolved before
216
+ the gate, so fail-open reads "install ungated unless fenced out": a manifest
217
+ written in a value shape an older installer cannot parse is fenced out of
218
+ that installer instead of installing everywhere unconstrained — the exact
219
+ case `hyperdrive_version:` exists to cover. The two gem-wide defaults are
220
+ independent too: an unusable `gem:` default no longer drops a parseable
221
+ fence, and a malformed fence no longer drops a usable `gem:` default; each
222
+ warns for its own axis. An entry whose *own* `hyperdrive_version:` is
223
+ unparsable still installs ungated and unfenced — the gem-wide fence is not
224
+ substituted for a constraint the entry never asked for.
225
+ - A `conditional:` key spelled as a template-backed supporting file's rendered
226
+ face (`references/x.md`) now gates that template, instead of silently
227
+ matching nothing while the file installed unconditionally. Either spelling —
228
+ the shipped `references/x.md.erb` or the face — resolves to the same gate;
229
+ a manifest carrying both for one file draws a warning and the shipped `.erb`
230
+ spelling wins. `rake hyperdrive:manifest:check` agrees with discovery on
231
+ both points, and no longer reports a face-spelled key as naming nothing
232
+ shipped.
233
+ - A `SKILL.md.erb` that fails to render because it reaches for a helper a
234
+ newer rails-hyperdrive added now reports its `hyperdrive_version:` fence
235
+ rather than a bare `ERB render failed (NameError)`, and that line reaches the
236
+ bundler-plugin surface like every other fence warning.
237
+ - An ordinary gate miss — a well-formed `gem:` whose target simply is not
238
+ bundled — no longer marks its source gem as having lost content, so the
239
+ stale-destination sweep keeps converging for companions that gate different
240
+ skills on different stacks. A renamed skill's old directory is now removed
241
+ instead of being orphan-warned indefinitely.
242
+ - `hyperdrive:sync` sweeps a stale `<dest>.new` sidecar when it removes the
243
+ destination that sidecar belonged to, under the same rule as everywhere else
244
+ (machine-pristine → removed; edited → warned about and left). A pristine
245
+ leftover no longer strands itself or keeps an emptied skill directory alive.
246
+ - `rake hyperdrive:manifest:check` fails when a `hyperdrive_manifest` gemspec
247
+ metadata key names a path that is not a file — previously read as "this gem
248
+ ships no manifest" and reported green, while the dangling key still counted
249
+ as companion opt-in and every artifact installed ungated.
250
+ - `rake hyperdrive:skills:render` / `:check` work for a companion that declares
251
+ neither `hyperdrive_skills_dir` nor `hyperdrive_skill_templates_dir`: the
252
+ content root now defaults to top-level `skills/` (matching discovery) rather
253
+ than to the same lib-convention path as the templates root, which made the
254
+ tasks hard-error with "content dir equals template dir".
255
+ - `init`/`sync` no longer count advisory discovery warnings as dropped
256
+ artifacts. Warnings that dropped shipped content — a whole artifact, or one
257
+ supporting file of one — print under `discovery skipped N item(s):` (was
258
+ `discovery skipped N artifact(s):`, which counted every warning and named
259
+ them all artifacts); everything that installed anyway prints under
260
+ `discovery reported M advisory warning(s):`.
261
+ - The bundler-plugin hook now prints discovery advisories during
262
+ `bundle install`, so a retired `versions:` key — content this release
263
+ deliberately installs unconstrained — is no longer silent there. Ordinary
264
+ artifact skips stay with `init`/`sync`.
265
+ - The `installing ungated` warning on a malformed manifest entry that keeps its
266
+ fence now reads `installing ungated unless fenced out`, so it can no longer
267
+ contradict a fence-skip line for the same artifact. Only an unparsable
268
+ `hyperdrive_version:` says `installing ungated and unfenced`.
269
+ - The warning for a manifest that will not parse now names the parser error
270
+ (`ignoring manifest hyperdrive.yml: malformed YAML (<reason>)`), matching what
271
+ `rake hyperdrive:manifest:check` already reported. Behaviour is unchanged:
272
+ gating that cannot be read still resolves to an absent manifest.
273
+
10
274
  ## [0.6.0] - 2026-08-17
11
275
 
12
276
  ### Changed
@@ -452,7 +716,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
452
716
  - `hyperdrive:init` generator that installs architecture skills and auto-discovers
453
717
  per-gem skills.
454
718
 
455
- [Unreleased]: https://github.com/rails-hyperdrive/rails-hyperdrive/compare/v0.6.0...HEAD
719
+ [Unreleased]: https://github.com/rails-hyperdrive/rails-hyperdrive/compare/v0.7.0...HEAD
720
+ [0.7.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.7.0
456
721
  [0.6.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.6.0
457
722
  [0.5.0]: https://github.com/rails-hyperdrive/rails-hyperdrive/releases/tag/v0.5.0
458
723
  [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.
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
@@ -32,18 +32,18 @@ module Rails
32
32
 
33
33
  def discover_artifacts(skip: false)
34
34
  @artifacts ||= skip ? [] : ::Rails::Hyperdrive::BundlerArtifactDiscovery.discover(
35
- warnings: warnings, enabled_gems: enabled_gems, notices: notices
35
+ enabled_gems: enabled_gems, report: report
36
36
  )
37
37
  end
38
38
 
39
39
  def install(mode:)
40
+ verify_lock_schema!
40
41
  @pipeline = ::Rails::Hyperdrive::InstallPipeline.new(
41
42
  root: root,
42
43
  shell: @shell,
43
44
  artifacts: discover_artifacts,
44
45
  mode: mode,
45
- warnings: warnings,
46
- notices: notices
46
+ report: report
47
47
  )
48
48
  @pipeline.call
49
49
  end
@@ -54,24 +54,34 @@ module Rails
54
54
 
55
55
  private
56
56
 
57
+ # Raised from install, so it stops the run before any content write —
58
+ # a dry run included.
59
+ def verify_lock_schema!
60
+ return unless lock.schema_ahead?
61
+
62
+ message = lock.schema_ahead_message(::Rails::Hyperdrive::InstallLayout::LOCK_PATH)
63
+ @shell.say_status :error, message, :red
64
+ raise Thor::Error, "hyperdrive: #{message}"
65
+ end
66
+
57
67
  # Resolved lazily so verify_environment! can report a missing Rails app
58
68
  # before anything dereferences ::Rails.root.
59
69
  def root
60
70
  @root ||= ::Rails.root.to_s
61
71
  end
62
72
 
63
- def warnings
64
- @warnings ||= []
73
+ def report
74
+ @report ||= ::Rails::Hyperdrive::BundlerArtifactDiscovery::Report.new
65
75
  end
66
76
 
67
- def notices
68
- @notices ||= []
77
+ def lock
78
+ @lock ||= ::Rails::Hyperdrive::LockFile.load(
79
+ File.join(root, ::Rails::Hyperdrive::InstallLayout::LOCK_PATH)
80
+ )
69
81
  end
70
82
 
71
83
  def enabled_gems
72
- @enabled_gems ||= ::Rails::Hyperdrive::LockFile.load(
73
- File.join(root, ::Rails::Hyperdrive::InstallLayout::LOCK_PATH)
74
- ).enabled_gems
84
+ lock.enabled_gems
75
85
  end
76
86
 
77
87
  def lock_entries