Packaging proposal
June 18, 2026 · View on GitHub
Status: proposal, gemspecs scaffolded &
gem build-clean, not yet published. The compiler surfaces the tools depend on (--debug,--emit-rbs,--emit-types, native backtrace) have now merged intomatz/spinel, so the upstream gate is cleared — a release can target upstream rather than thefeat/typingfork. The remaining gates are thespinel-engineresolver decision and freezing the--emit-typesJSON shape. This doc fixes the boundaries so the split is concrete and consumers (e.g. spinelgems' Localizer) can target stable names.
Principle: compiler features upstream, harness as gems
Two layers, two destinations:
- Compiler features →
matz/spinel(not gems):--debug/#linestepping,--emit-rbs,--emit-types, nativeException#backtrace. These are observability hooks inside the compiler; they belong upstream, opt-in and output-neutral. (Roadmap F1 — all merged:--emit-rbs#1276,--debug#1292,--emit-types#1298, native backtrace #1300, FloatArray ops #1301.) - Harness → gems: the tools that consume those hooks — the LSP addon, the differential bisector, the one-shot doctor. These are what we package.
So packaging is entangled with upstreaming: the gems get smaller and cleaner as more of the compiler half merges, because they stop carrying fork-specific glue.
Proposed split — three small gems, not one
Driven by who consumes what (different audiences/deps → separate gems):
1. ruby-lsp-spinel (already scaffolded)
- The ruby-lsp addon: inferred types on hover + degrade warnings.
- Pure Ruby. Dep:
ruby-lsp >= 0.23. Runtime: aspinelthat supports--emit-types. - Audience: editor users. Nothing else should drag in
ruby-lsp. - Already has a gemspec (
tools/ruby-lsp-spinel/). Publishable first, once--emit-typesis upstream and its JSON shape is frozen.
2. spinel-bisect
- The differential value-bisection harness:
bisect.sh,compare.py,cruby_trace.rb,spinel_lldb_trace.py,triage.sh. - Polyglot by necessity: the lldb trace must be Python (lldb's scripting API
is Python-only), so this gem is not pure Ruby — it ships the scripts as gem
data with thin
exe/wrappers (spinel-bisect,spinel-triage) that locate them via__dir__. Runtime prereqs:python3,lldb, aspinelcheckout. - Audience: anyone localizing a miscompile. Crucially, spinelgems'
Localizershould depend on this gem — a gem install gives it a stable path, replacing theSPINEL_BISECT› sibling-checkout ›~/sites/spinel-devprobing (spinelgems#11).
3. spinel-doctor
- The one-shot health check (compile-probe + degrade scan + optional bisect).
- Thin:
doctor.sh+ anexe/spinel-doctor. Dep:spinel-bisect(for the behavior leg) + aspinelcheckout. - Kept separate from
spinel-bisectprecisely so consumers who want only the bisector (spinelgems) don't pull doctor's orchestration. doctor is the opinionated front-end; bisect is the engine. - Re-scope (2026-06). Upstream matz/spinel now ships a first-party
spinel-doctor(we contributed matz#1476/#1482), so the one-shot health check is owned upstream. spinel-dev shouldn't re-ship a competingspinel-doctorname; this layer's distinct value is the differential bisector (spinel-bisect) plus the migration/perf tooling on top of the upstream doctor — not a parallel one-shot. Package accordingly: keepspinel-bisectas the engine and let the upstream tool own the front-end.
Cross-cutting concerns
- Engine resolution. All tools locate the compiler via
SPINEL_DIR(default~/sites/spinel). bundler-spinel already has a richer resolver (SPINEL_DIR›~/.cache/spinel/current›PATH). Rather than duplicate, factor a tinyspinel-engineresolver that both bundler-spinel and these tools depend on. (Optional 4th micro-gem; until then, keep the env-var contract identical across tools so they're swappable.) - Polyglot reality.
spinel-bisectcan't be pure Ruby (lldb→Python). We could rewritecompare.py/cruby_trace.rbin Ruby, butspinel_lldb_trace.pystays. Accept it; declarepython3/lldbas runtime prerequisites in the gem description and fail fast with a clear message when absent. - Versioning against a moving target. The debug/emit surfaces are pre-upstream
and still changing. Like bundler-spinel's ledger, key compatibility on the
spinel revision, and don't publish until the upstream API (flag names,
--emit-typesJSON schema) stabilizes through review.
What blocks publishing (the gate)
Upstream merge of the compiler surfaces into— done: all five surfaces merged (#1276/#1292/#1298/#1300/#1301), so a release can target upstreammatz/spinel(F1)spinelrather than the fork.- The
--emit-typesJSON shape + flag names are now settled (merged in #1298) — the "settle the API on real apps first" gate is cleared. - The
spinel-engineresolver decision (shared micro-gem vs. duplicated env-var contract).
Status — scaffolded, not yet published
The upstream API has landed (--emit-rbs, --debug, --emit-types, native
backtrace, FloatArray ops all merged), so the gemspecs are scaffolded and
gem build-clean and the upstream-merge gate is no longer blocking:
tools/ruby-lsp-spinel/ruby-lsp-spinel.gemspec(pure Ruby; depruby-lsp).tools/value-bisect/spinel-bisect.gemspec+exe/spinel-bisect,exe/spinel-triage— ships the sh/python/ruby scripts as gem data; the exe launchersexecthem. Runtime prereqs declared in the description (aspinelcheckout,python3,lldb).tools/doctor/spinel-doctor.gemspec+exe/spinel-doctor— depends onspinel-bisectfor the behavior leg.
cd tools/value-bisect && gem build spinel-bisect.gemspec # -> spinel-bisect-0.1.0.gem
Not published. Publish order when ready: ruby-lsp-spinel first (most
self-contained, once --emit-types lands), then spinel-bisect → spinel-doctor.
Still open before a push: the shared spinel-engine resolver decision and pinning
the --emit-types JSON shape (its PR invites that). gem build artifacts are
gitignored.