docs: keep JetWhale out of release builds, and a skill that wires it in - #208
Open
kitakkun wants to merge 5 commits into
Open
docs: keep JetWhale out of release builds, and a skill that wires it in#208kitakkun wants to merge 5 commits into
kitakkun wants to merge 5 commits into
Conversation
A debug-only dependency stops being enough as soon as shared code calls startJetWhale: the release variant needs those symbols to compile, so the dependency comes back. Android variant source sets do not help a KMP commonMain, and a BuildConfig.DEBUG guard still ships every class. Document the seam instead: an app-owned interface with a no-op binding contributed from an always-present module, displaced on the debug classpath by a JetWhale-backed implementation via Metro's `@ContributesBinding` replaces. HTTP client capture is a multibinding rather than a replacement, since release simply contributes no element. The Metro semantics here were checked against 1.3.2 by compiling both directions: with replaces the graph merges, without it the build fails with Metro/DuplicateBinding naming both contributions.
The QA skill serves plugin authors inside this repository; nothing served the far larger group adding JetWhale to an app they want to debug. That task is decided by two things the guide cannot assume — how the project splits debug from release, and which DI framework it uses — so the skill surveys both before writing anything, and prefers riding an existing debug-only module or initializer abstraction over inventing a new seam. Wiring is routed per framework: metro.md carries the verified detail, with anvil.md, dagger-hilt.md, koin.md and no-di.md covering the rest. Only Metro's semantics were compile-checked here, so the others tell the agent to confirm annotation parameters against the project's own versions. Verification targets the release classpath rather than the wiring, since a graph that compiles proves nothing about what ships.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR expands JetWhale’s documentation and Claude Code plugin skill set to help consumers integrate JetWhale in a way that keeps all JetWhale symbols off release compile/runtime classpaths, even when shared code needs to call startJetWhale { }.
Changes:
- Added a new long-form guide explaining the “DI seam” approach for excluding JetWhale from release builds (with Metro-focused examples and Gradle wiring for Android + KMP).
- Added a new Claude Code skill (
/jetwhale:integrate) plus DI-framework-specific reference pages to drive safe integrations. - Updated plugin manifests/README and docs-site navigation to reflect the broader scope and expose the new guide.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| plugins/jetwhale/skills/integrate/SKILL.md | New /jetwhale:integrate skill instructions and routing logic (survey → choose wiring reference). |
| plugins/jetwhale/skills/integrate/references/no-di.md | Reference wiring for projects without a DI framework (variant source sets / module-based seam). |
| plugins/jetwhale/skills/integrate/references/metro.md | Metro reference wiring for debug/release displacement via classpath contribution merging. |
| plugins/jetwhale/skills/integrate/references/koin.md | Koin reference wiring using variant-specific module definitions. |
| plugins/jetwhale/skills/integrate/references/dagger-hilt.md | Dagger/Hilt reference wiring using variant source sets and (optional) multibinding for decorators. |
| plugins/jetwhale/skills/integrate/references/anvil.md | Anvil / kotlin-inject-anvil deltas relative to the Metro-shaped approach. |
| plugins/jetwhale/README.md | Updates plugin README to include the new integrate skill and revised positioning. |
| plugins/jetwhale/.claude-plugin/plugin.json | Bumps plugin metadata to 0.2.0 with broadened description. |
| docs/guide/getting-started.md | Links readers to the new “Excluding from Release Builds” guide once shared code calls JetWhale APIs. |
| docs/guide/excluding-from-release-builds.md | New guide page describing the seam/module layout, DI bindings, and verification steps. |
| docs-site/.vitepress/config.mts | Adds the new guide page to the VitePress sidebar navigation. |
| .claude-plugin/marketplace.json | Updates marketplace metadata (description + version 0.2.0). |
Comments suppressed due to low confidence (1)
plugins/jetwhale/skills/integrate/references/dagger-hilt.md:94
@Providesmethods must be declared inside a@Module(and for Hilt, also in something@InstallIn(...)). As written, this top-level@Providesfunction won’t compile / be picked up by Dagger/Hilt.
// src/debug
@Provides
@IntoSet
fun provideJetWhaleDecorator(agent: JetWhaleNetworkAgentPlugin): HttpClientDecorator =
HttpClientDecorator { client ->
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+12
to
+14
| interface DebugToolingInitializer { | ||
| fun initialize() | ||
| } |
Comment on lines
+29
to
+31
| interface DebugToolingInitializer { | ||
| fun initialize() | ||
| } |
Shipping a skill that tells an agent how to wire five DI frameworks, while having compiled only one of them, put the burden of being wrong on whoever ran it. Each pattern was rebuilt as a four-module project and run, so the recorded result is the binding that actually resolved. Three claims did not survive: - kotlin-inject has no `@Multibinds(allowEmpty = true)`, so an empty set fails KSP outright. The HTTP decorator has to be an ordinary binding with a no-op default, displaced by `replaces` like the initializer. - Square Anvil generates through the Kotlin compiler plugin, which runs after KSP reads source. With Dagger on KSP nothing is generated at all and the build fails much later on an unresolved DaggerAppComponent; kapt is required. - Hilt does not need the same-name module pair this previously described. `@InstallIn` is discovered from the classpath, so `@BindsOptionalOf` in src/main plus a module in src/debug is enough. The pair is only unavoidable for plain Dagger, which lists its modules explicitly. Dagger's `@Multibinds` also allows an empty set with no parameter, unlike Metro's. Android isolation was checked on the artifacts rather than the wiring: the debug-only project is absent from releaseRuntimeClasspath and its classes are absent from the release APK's dex.
Nobody adopts Anvil today — 2.7.0 is pinned to Kotlin 2.2.20 and Metro is its successor — so a how-to shaped section invited the wrong reading and buried the one thing that costs real time. Lead with the kapt constraint, since it has to be checked before any wiring is written and it fails silently: no warning, nothing generated, and a build error much later at an unresolved DaggerAppComponent. The wiring itself collapses to a few lines now that it is stated as a delta from Metro, and the migration exit gets its own closing section. The file header also names the two libraries as unrelated, which they are — they share a word and nothing else.
The reference files are prose a person reads, not instructions an agent executes, and the Anvil section had drifted into commanding the reader: "Do not reach for Anvil to solve this" tells someone already on Anvil not to make a choice they are not making, and reads as a judgement of their stack. The imperatives in SKILL.md stay — those genuinely direct an agent's next action. Also trims the dramatised description of the KSP failure to the fact itself: there is no warning, nothing is generated, and the build fails later somewhere unrelated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Getting Started only said "add it to debug builds". That is true right up until shared code calls
startJetWhale { }— from then on the release variant needs those symbols to compile and the dependency comes straight back. Android variant source sets do not reach a KMPcommonMain, and aBuildConfig.DEBUGguard still ships every class it guards.What
A guide —
docs/guide/excluding-from-release-builds.md. Production code depends on an app-owned interface; a no-op binding is contributed from an always-present module and displaced on the debug classpath by a JetWhale-backed implementation. HTTP client capture is modelled as a multibinding instead, since release simply contributes no element. Covers the Gradle wiring for both Android variants and KMP, how to verify against the release classpath, and the failure modes.A skill —
/jetwhale:integrate. The QA skill serves plugin authors working inside this repository; nothing served the larger group adding JetWhale to an app they want to debug. The skill surveys the project's targets, DI framework, HTTP client and existing debug seam before writing anything, prefers riding an existing debug-only module over inventing one, then routes to a per-framework reference:metro.md,anvil.md,dagger-hilt.md,koin.md,no-di.md.Plugin bumped to 0.2.0 and both manifests reworded, since the plugin is no longer only about developing host plugins.
Verification
Every documented pattern was built as a four-module project (
:seam,:tooling,:app-debugdepending on both,:app-releasedepending on:seamonly) and run, so each recorded result is the binding that actually resolved rather than one inferred from the annotations.noop+ empty decorator set; debug real binding;@SingleInholder identical across both injection sitesreplacesresolves both ways; an emptySet<T>fails KSP outrightreplacesresolves both ways — only after moving Dagger from KSP to kapt@BindsOptionalOf→Optional.empty()in release, present in debug;@Multibindsallows empty with no parameterOptional.of(...)in debug vsOptional.empty()in release, with no release-side modulegetOrNullnull in release;getAllempty;singleshares one instanceIsolation was checked on the built artifacts, not the wiring: the debug-only project appeared on
debugRuntimeClasspath, was absent fromreleaseRuntimeClasspath, and its classes were absent from the release APK's dex.Three claims from the first draft did not survive contact with a compiler and were corrected:
@Multibinds(allowEmpty = true). An empty set fails withCannot find an @Inject constructor or provider for: Set<…>, so the HTTP decorator must be an ordinary binding with a no-op default, displaced byreplaceslike the initializer.DaggerAppComponent.@InstallInis discovered from the classpath, so@BindsOptionalOfinsrc/mainplus a module insrc/debugis enough. The pair is only unavoidable for plain Dagger, which lists its modules explicitly.Metro semantics were additionally checked inside this repository: with
replacesthe graph merges, without it the build fails withMetro/DuplicateBindingnaming both contributions. That probe was removed.@Providesfollows this repository's own working idiom (a@ContributesTointerface).VitePress builds clean, dead-link checking included.
Note
The verification projects live outside the repository. Committing them so CI keeps the skill honest as these libraries move is a reasonable follow-up, at the cost of repository weight and CI time.