Skip to content

feat: package bough as a Claude Code plugin#103

Merged
ikeikeikeike merged 3 commits into
mainfrom
feat/claude-code-plugin
Jul 14, 2026
Merged

feat: package bough as a Claude Code plugin#103
ikeikeikeike merged 3 commits into
mainfrom
feat/claude-code-plugin

Conversation

@ikeikeikeike

Copy link
Copy Markdown
Member

Why

bough could only be driven from a shell (bough <verb>). This packages the repo
as a Claude Code plugin so it can be driven from inside a session
(/bough:* slash commands), and installing it auto-wires the continuous-learning

  • worktree hooks — closing the one packaging gap versus a slash-command/skill
    continuous-learning plugin. The binary still comes from the existing release
    channels; the plugin is markdown + JSON only.

Install

/plugin marketplace add threecorp/bough
/plugin install bough@bough

What's in it

Component Detail
.claude-plugin/{marketplace,plugin}.json one bough plugin, source ./; version omitted → tracks git SHA
commands/*.md (11) create / remove / list / status / verify / doctor / instinct-status / instinct-list / instinct-promote / evolve / config-validate — thin bough <verb> wrappers
hooks/hooks.json all 8 events → bough hook handle --event <E>, verbatim mirror of bough hook install; installing auto-wires observe→instinct→evolve→preserve + WorktreeCreate/Remove
skills/using-bough/SKILL.md model-invoked orchestration + command -v bough PATH preflight
internal/hooks/plugin_sync_test.go fails CI if hooks.json drifts from CanonicalCommand / AllEvents
bough doctor warns when bough hooks are in settings.json (a settings.json copy + the plugin double-fires every event)

Don't double-wire: use the plugin or bough hook install, not both.

Verification

claude plugin validate . passes; an isolated (CLAUDE_CONFIG_DIR) install
smoke shows plugin list = bough@bough enabled and plugin details = 11
commands + using-bough + 8 hooks recognized, without touching the real
~/.claude. go test ./internal/..., go test ./conformance/hooks/, and
golangci-lint all green.

Ship the repo as a Claude Code plugin so bough can be driven from inside a
session (/bough:* slash commands) instead of only from a shell, closing the
one packaging gap versus a slash-command/skill continuous-learning plugin.

- .claude-plugin/{marketplace,plugin}.json: a single `bough` plugin whose
  source is the repo root; version omitted so it tracks the git commit SHA.
- commands/*.md: 11 user-facing verbs (create/remove/list/status/verify/
  doctor/instinct-status/instinct-list/instinct-promote/evolve/config-validate),
  each a thin wrapper that shells out to `bough` on PATH.
- hooks/hooks.json: mirrors, verbatim, the `bough hook handle --event <E>`
  dispatcher `bough hook install` writes for all 8 events, so installing the
  plugin auto-wires the observe->instinct->evolve->preserve + WorktreeCreate/
  Remove loop without editing settings.json.
- skills/using-bough/SKILL.md: model-invoked orchestration + `command -v bough`
  PATH preflight.
- internal/hooks/plugin_sync_test.go: fails if hooks.json ever drifts from
  hooks.CanonicalCommand / hooks.AllEvents (the manifest is hand-authored).
- bough doctor: warns when bough hooks live in settings.json, since a
  settings.json copy plus the installed plugin double-fires every event.

The plugin ships markdown + JSON only; the binary still comes from the existing
release channels (tarball / nix / go install). Validated with
`claude plugin validate .`.
Comment thread internal/hooks/hooks.go Outdated
// plugin from here (its registry is Claude Code's, not bough's),
// so this is a conditional heads-up, not an assertion the plugin
// is present.
fmt.Fprintln(w, " note: these bough entries live in settings.json. If you ALSO installed the")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note renders whenever hasBoughSettingsHooks() is true - i.e. whenever bough hooks are in settings.json at all, not just when the plugin is ALSO installed. Since bough cannot see the plugin from here, this fires for essentially every existing bough hook install user (the standard, pre-plugin workflow), not just the double-wire case the wording implies.

Rewording so it explicitly says this is unconditional guidance (not a diagnosed problem) rather than adding detection logic that would depend on Claude Code's undocumented internal plugin registry. Pushing that change now.

Comment thread internal/hooks/plugin_sync_test.go Outdated
}

// loadPluginHooks reads + parses the committed plugin hooks.json.
func loadPluginHooks(t *testing.T) map[HookEvent][]HookGroup {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reinvents settings.json hook parsing (pluginHooksFile / hand-rolled unmarshal) instead of reusing decodeHookSet, which already exists in this package and is what bough hook install/List/Doctor actually decode with. Since this test never exercises decodeHookSet, a bug in the production decode path wouldn't be caught here, and any future change to its semantics would need to be hand-duplicated to keep this drift guard in sync - exactly the kind of drift this file exists to prevent elsewhere.

Switching loadPluginHooks to parse via decodeHookSet directly. Pushing that change now.

…uses decodeHookSet

- hooks.go: the plugin double-wire note fired whenever bough hooks
  were in settings.json at all, not just when the Claude Code plugin
  was ALSO installed -- bough cannot detect the plugin from here, so
  this rendered for essentially every existing `bough hook install`
  user (the standard, non-plugin workflow). Reworded to say explicitly
  that this is unconditional guidance, not a diagnosed problem, rather
  than adding detection logic that would depend on Claude Code's
  undocumented internal plugin registry.
- plugin_sync_test.go: loadPluginHooks reinvented settings.json hook
  parsing instead of reusing decodeHookSet, so the drift guard never
  exercised the actual production decode path and would need manual
  updates if decodeHookSet's semantics ever changed. Now parses via
  decodeHookSet directly.

Found by code review on PR #103.
@ikeikeikeike
ikeikeikeike merged commit 76caa58 into main Jul 14, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant