From 9756dd92d30ed1bac6afdb59e92666037cfd4943 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:57:01 -0400 Subject: [PATCH 1/6] docs(playbook): add reintegration (consumer-adoption) section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migration playbook documented the forward path (migrate -> publish) but stopped there. Add the back-half: how a consumer repo retires its in-repo hook and adopts the published plugin, preserving its specifics through its OWN extension seams (settings env, CLAUDE.md/rules, and especially its telemetry sink) rather than teaching the plugin consumer-specific behavior. Captures the verified key insight from the biome-format/medley audit: the telemetry-sink envelope contract is the adaptation seam — a consumer restores a divergent observability contract (e.g. status=error on a violation, a legacy hook name) by remapping the generic envelope in its own sink, after verifying how its observability actually keys events. Includes a cutover checklist and the bootstrap-direction caveat (don't flip a still-source repo to consumer incidentally). Decision context: medley's biome-format reintegration is deferred — medley stays the harvest source for now — so this captures the reusable pattern without changing any consumer. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU --- docs/MIGRATION-PLAYBOOK.md | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index 9b2843b0f..d7273801a 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -105,6 +105,49 @@ claude --plugin-dir ./plugins/ - **Then ship.** Run `claude plugin validate` before opening a PR; after merge, consumers pull the change with `/plugin marketplace update melodic-software`, gated by the `version` bump in `plugin.json`. +## Reintegration — a consumer adopts the published plugin + +The forward migration (above) ends at *publish*. The lifecycle closes when the source repo stops running +its in-repo copy and instead **consumes the published plugin** — one source of truth, and the repo +dogfoods the marketplace. Reintegration is a *consumer-side* change: adapt through the documented +extension points, never by teaching the plugin a consumer's specifics. + +**The plugin is generic; the consumer's own seams restore its specifics.** Map each behavior the +in-repo hook had that the generalized plugin dropped to one of these, in order: + +- **Kill switch / toggles** → the plugin's own env var, set in the consumer's `settings.json` `env` + (the name changes from the in-repo `HOOK__ENABLED` to the plugin's `HOOK__ENABLED`). +- **Project conventions** → the consumer's `CLAUDE.md` / `.claude/rules`, which the plugin already reads. +- **Telemetry / observability** → the consumer's own **telemetry sink**. This is the key seam: the + plugin emits the generic telemetry envelope contract to `HOOK_TELEMETRY_SINK`, and the consumer's sink + script translates that envelope into the consumer's local observability shape. A consumer whose prior + hook emitted a different status or hook-identity (e.g. `status=error` on a surfaced violation, or a + legacy hook name) restores that contract **in its own sink**, by remapping the plugin's native envelope + (`status=ok` + populated `findings`) — not by changing the plugin. Before remapping, verify how the + consumer's observability actually keys events (e.g. on `status` vs a derived `exit_code`/findings count), + so the remap preserves the real contract rather than a guessed one. + +If a genuine specific has **no** seam, that is a real plugin gap → add a declared extension +(`userConfig`, or a new env var consistent with the plugin's existing ones) — but only when it carries +real behavior, not cosmetic prose a consumer's `CLAUDE.md` already establishes. Resist adding config +surface to a published plugin for a single consumer's low-value nicety. + +**Cutover checklist:** + +1. Register the marketplace in the consumer's `extraKnownMarketplaces` and enable the plugin in + `enabledPlugins` (project `settings.json`, so clones inherit). Headless/CI needs an explicit + `claude plugin marketplace add` — the auto-registration trust dialog is interactive-only. +2. Rewire the kill-switch env var to the plugin's name; keep the `HOOK_TELEMETRY_SINK` wiring and the + sink script (the bridge), adapting the sink for any observability-contract divergence. +3. Remove the in-repo hook's `settings.json` registration and delete the hook script **and its test**. +4. Verify the plugin hook fires (edit a governed file, confirm format/lint + surfaced findings), and that + the consumer's hard gate (commit hooks, CI) is untouched — those are independent of the edit-time hook. + +**Bootstrap-direction caveat.** While a repo is still the harvest *source* (its hooks are mid-migration +out), reintegrating one plugin makes it consume one plugin while still running the rest in-repo — a mixed +state. Flip a repo from source to consumer deliberately, not incidentally, and ideally once its ported +plugins can move together. + ## What to wait on / avoid for now - Don't pre-build cross-plugin `dependencies` graphs until two plugins genuinely share a need. From 69d47722b623f4980c0770d08c4f74d3a619170c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:01:52 -0400 Subject: [PATCH 2/6] docs(playbook): address review on reintegration section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #21 review fixes: - Cutover checklist: verify-before-retire (blue-green) — reorder so the plugin is confirmed working (incl. the telemetry sink receiving the expected envelope) before the in-repo hook is deleted, closing the no-active-hook window. Note the brief both-hooks overlap is harmless (formatting is idempotent). - Fold the telemetry-sink check into the verify step. - Fresh-docs: cite the 2026-06-29 verification of extraKnownMarketplaces / enabledPlugins against the discover-plugins "Configure team marketplaces" guide, in the intro provenance line. - Disambiguate "its" -> "the repo's" ported plugins. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU --- docs/MIGRATION-PLAYBOOK.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index d7273801a..bf7171d81 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -4,8 +4,10 @@ How skills, hooks, and agents become reusable plugins in this marketplace. One p time: lift it out, make it work in plugin form and in any repo, build in configuration and extensibility, vet it against best practices, then publish. -All schema and behavior claims below were verified against the official docs on 2026-06-22. Re-verify -fresh before acting — see `CLAUDE.md` "Fresh-docs mandate". +All schema and behavior claims below were verified against the official docs on 2026-06-22 (the +"Reintegration" section's marketplace-settings claims — `extraKnownMarketplaces` / `enabledPlugins` in a +project's `settings.json` — on 2026-06-29, against the discover-plugins "Configure team marketplaces" +guide). Re-verify fresh before acting — see `CLAUDE.md` "Fresh-docs mandate". ## Intent @@ -135,18 +137,23 @@ surface to a published plugin for a single consumer's low-value nicety. **Cutover checklist:** 1. Register the marketplace in the consumer's `extraKnownMarketplaces` and enable the plugin in - `enabledPlugins` (project `settings.json`, so clones inherit). Headless/CI needs an explicit + `enabledPlugins` (project `settings.json`, so clones inherit on trust). Headless/CI needs an explicit `claude plugin marketplace add` — the auto-registration trust dialog is interactive-only. 2. Rewire the kill-switch env var to the plugin's name; keep the `HOOK_TELEMETRY_SINK` wiring and the sink script (the bridge), adapting the sink for any observability-contract divergence. -3. Remove the in-repo hook's `settings.json` registration and delete the hook script **and its test**. -4. Verify the plugin hook fires (edit a governed file, confirm format/lint + surfaced findings), and that - the consumer's hard gate (commit hooks, CI) is untouched — those are independent of the edit-time hook. +3. **Verify before retiring** the old hook (blue-green — never leave a window with no active hook). With + the plugin enabled, edit a governed file and confirm: the plugin formats/lints and surfaces findings, + the telemetry sink receives the expected envelope (so a broken remap is caught now, not when + observability is next needed), and the consumer's hard gate (commit hooks, CI) is untouched — those are + independent of the edit-time hook. The brief overlap where both hooks fire is harmless: formatting is + idempotent. +4. Only then remove the in-repo hook's `settings.json` registration and delete the hook script **and its + test**. **Bootstrap-direction caveat.** While a repo is still the harvest *source* (its hooks are mid-migration out), reintegrating one plugin makes it consume one plugin while still running the rest in-repo — a mixed -state. Flip a repo from source to consumer deliberately, not incidentally, and ideally once its ported -plugins can move together. +state. Flip a repo from source to consumer deliberately, not incidentally, and ideally once the repo's +ported plugins can move together. ## What to wait on / avoid for now From 7358e0b314fc9e4d5f7c23a53b06c7507ada5fef Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:07:05 -0400 Subject: [PATCH 3/6] docs(playbook): correct convention seam for hook-based plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #21 re-review: the "project conventions -> CLAUDE.md" bullet was inaccurate for hook plugins. Hook scripts (biome-format, bash-lint) never receive CLAUDE.md — only a plugin's skill/agent components do (model context). Hooks read project conventions from the consumer's own file-based tool config (biome.json, .shellcheckrc, .editorconfig). A consumer routing a convention to CLAUDE.md for a hook plugin would have it silently ignored. Scope the bullet to file-based config for hooks and note CLAUDE.md reaches only skill/agent components. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU --- docs/MIGRATION-PLAYBOOK.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index bf7171d81..416475994 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -119,7 +119,10 @@ in-repo hook had that the generalized plugin dropped to one of these, in order: - **Kill switch / toggles** → the plugin's own env var, set in the consumer's `settings.json` `env` (the name changes from the in-repo `HOOK__ENABLED` to the plugin's `HOOK__ENABLED`). -- **Project conventions** → the consumer's `CLAUDE.md` / `.claude/rules`, which the plugin already reads. +- **Project conventions** → for a hook plugin, the consumer's own tool config files that the hook already + reads (`biome.json`, `.shellcheckrc`, `.editorconfig`, …) — that is where these plugins pick up project + conventions, **not** `CLAUDE.md`. (`CLAUDE.md` / `.claude/rules` reach only a plugin's *skill/agent* + components, which run in Claude's model context; hook scripts see only env vars and file-based config.) - **Telemetry / observability** → the consumer's own **telemetry sink**. This is the key seam: the plugin emits the generic telemetry envelope contract to `HOOK_TELEMETRY_SINK`, and the consumer's sink script translates that envelope into the consumer's local observability shape. A consumer whose prior From 4f4721871d62ad712f156dee28008821a04d88ae Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:13:20 -0400 Subject: [PATCH 4/6] docs(playbook): disable old hook during verify (no concurrent formatters) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #21 review: the verify step called the both-hooks overlap "harmless: formatting is idempotent" — incorrect. Matching PostToolUse hooks run concurrently, so the old in-repo formatter and the plugin would race on the just-edited file (last-writer-wins clobbering + doubled telemetry/ context). Idempotence only makes serial re-runs converge, not concurrent writes safe. Fix: disable the old hook (kill switch / comment out its registration, don't delete) before verifying the plugin, then delete after. This keeps the blue-green property (plugin active throughout, old hook recoverable if verify fails) AND avoids concurrent mutation. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU --- docs/MIGRATION-PLAYBOOK.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index 416475994..3265fad38 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -144,14 +144,18 @@ surface to a published plugin for a single consumer's low-value nicety. `claude plugin marketplace add` — the auto-registration trust dialog is interactive-only. 2. Rewire the kill-switch env var to the plugin's name; keep the `HOOK_TELEMETRY_SINK` wiring and the sink script (the bridge), adapting the sink for any observability-contract divergence. -3. **Verify before retiring** the old hook (blue-green — never leave a window with no active hook). With - the plugin enabled, edit a governed file and confirm: the plugin formats/lints and surfaces findings, - the telemetry sink receives the expected envelope (so a broken remap is caught now, not when - observability is next needed), and the consumer's hard gate (commit hooks, CI) is untouched — those are - independent of the edit-time hook. The brief overlap where both hooks fire is harmless: formatting is - idempotent. -4. Only then remove the in-repo hook's `settings.json` registration and delete the hook script **and its - test**. +3. **Verify before retiring** the old hook (blue-green — keep it recoverable, but never run both on the + same edit). First **disable** the in-repo hook — flip its kill switch or comment out its `settings.json` + registration; do **not** delete it yet. Don't leave both registered: matching `PostToolUse` hooks run + concurrently, so two formatters would race on the just-edited file (last-writer-wins clobbering, plus + doubled telemetry and context) — idempotence only makes *serial* re-runs converge, it doesn't make + concurrent writes safe. With the old hook off and the plugin enabled, edit a governed file and confirm: + the plugin formats/lints and surfaces findings, the telemetry sink receives the expected envelope (so a + broken remap is caught now, not when observability is next needed), and the consumer's hard gate (commit + hooks, CI) is untouched — those are independent of the edit-time hook. If anything is wrong, re-enable + the old hook; the plugin stays active throughout, so there is never a no-hook gap. +4. Only once verified, remove the in-repo hook's `settings.json` registration and delete the hook script + **and its test**. **Bootstrap-direction caveat.** While a repo is still the harvest *source* (its hooks are mid-migration out), reintegrating one plugin makes it consume one plugin while still running the rest in-repo — a mixed From df82e0f6e679817368cc95cccc2f10a336df8fcd Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:17:55 -0400 Subject: [PATCH 5/6] docs(playbook): fix disable mechanism + failure-path concurrency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #21 review, two flaws in the verify step: - "comment out its settings.json registration" — settings.json is JSON; a // comment makes it invalid. Disable via the kill-switch env var (set to "false"), or remove the registration entry if it has none. - On verify failure I had "re-enable the old hook while the plugin stays active" — that recreates the both-active concurrent race. Corrected to disable the plugin first, then re-enable the old hook: exactly one formatter is active at any time, with no no-hook gap. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU --- docs/MIGRATION-PLAYBOOK.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index 3265fad38..050798117 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -145,15 +145,18 @@ surface to a published plugin for a single consumer's low-value nicety. 2. Rewire the kill-switch env var to the plugin's name; keep the `HOOK_TELEMETRY_SINK` wiring and the sink script (the bridge), adapting the sink for any observability-contract divergence. 3. **Verify before retiring** the old hook (blue-green — keep it recoverable, but never run both on the - same edit). First **disable** the in-repo hook — flip its kill switch or comment out its `settings.json` - registration; do **not** delete it yet. Don't leave both registered: matching `PostToolUse` hooks run - concurrently, so two formatters would race on the just-edited file (last-writer-wins clobbering, plus - doubled telemetry and context) — idempotence only makes *serial* re-runs converge, it doesn't make - concurrent writes safe. With the old hook off and the plugin enabled, edit a governed file and confirm: - the plugin formats/lints and surfaces findings, the telemetry sink receives the expected envelope (so a - broken remap is caught now, not when observability is next needed), and the consumer's hard gate (commit - hooks, CI) is untouched — those are independent of the edit-time hook. If anything is wrong, re-enable - the old hook; the plugin stays active throughout, so there is never a no-hook gap. + same edit). Matching `PostToolUse` hooks run concurrently, so leaving both registered would race two + formatters on the just-edited file (last-writer-wins clobbering, plus doubled telemetry and context) — + idempotence only makes *serial* re-runs converge, not concurrent writes safe. So **exactly one is + active at a time**: disable the in-repo hook by setting its kill-switch env var to `"false"` (or, if it + has none, removing its registration entry — `settings.json` is JSON, so toggling the value or removing + the entry is the edit, never a `//` comment). With the old hook off and the plugin enabled, edit a + governed file and confirm: the plugin formats/lints and surfaces findings, the telemetry sink receives + the expected envelope (so a broken remap is caught now, not when observability is next needed), and the + consumer's hard gate (commit hooks, CI) is untouched — those are independent of the edit-time hook. If + verification fails, **disable the plugin first, then re-enable the old hook** (always flip one off as you + turn the other on) and debug before retrying — so the two never run together and there is never a + no-hook gap. 4. Only once verified, remove the in-repo hook's `settings.json` registration and delete the hook script **and its test**. From 56bebfca83c726c5c9498ad520719371fe2f97da Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:26:41 -0400 Subject: [PATCH 6/6] docs(playbook): headless cutover must install the plugin, not just add the marketplace PR #21 review: the headless/CI note only called `claude plugin marketplace add`, but registering a marketplace does not install its plugins (the interactive trust prompt is what installs the enabled plugin). Without an install, a headless consumer disables the old hook in step 3 while the plugin is still absent, so the verify edit runs with no hook. Add the explicit `claude plugin install @ --scope project`. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU --- docs/MIGRATION-PLAYBOOK.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index 050798117..540d41758 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -140,8 +140,11 @@ surface to a published plugin for a single consumer's low-value nicety. **Cutover checklist:** 1. Register the marketplace in the consumer's `extraKnownMarketplaces` and enable the plugin in - `enabledPlugins` (project `settings.json`, so clones inherit on trust). Headless/CI needs an explicit - `claude plugin marketplace add` — the auto-registration trust dialog is interactive-only. + `enabledPlugins` (project `settings.json`, so clones inherit it on trust — the interactive trust prompt + both registers and installs the enabled plugin). Headless/CI has no such prompt, and registering a + marketplace does not install its plugins, so do both explicitly: `claude plugin marketplace add ` + then `claude plugin install @ --scope project` — otherwise the marketplace is known + but the plugin is absent, and step 3's verify edit would run with no plugin hook. 2. Rewire the kill-switch env var to the plugin's name; keep the `HOOK_TELEMETRY_SINK` wiring and the sink script (the bridge), adapting the sink for any observability-contract divergence. 3. **Verify before retiring** the old hook (blue-green — keep it recoverable, but never run both on the