From 4dbf9270b9f72e805384a7349c96bd7b5668dfdf Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:48:35 -0400 Subject: [PATCH 1/9] feat(hook-utils): add a non-exiting hook::is_enabled predicate hook::check_enabled exits 0 when a plugin is gated off, which is correct for a hook -- no output means no action. It is wrong for any caller that must keep running after a "disabled" answer. The statusline tee is exactly that caller: it wraps the user's real statusline, so an exit would suppress the wrapped command's stdout and blank the status line rather than merely skipping the tee's own write. Split the resolution into a predicate and make check_enabled delegate to it; check_enabled is unchanged. Adds Test 20 covering both functions, including that an empty value is treated as unset -- Claude Code exports every declared option, so an option the user never answered arrives as an empty string and must mean "default", not "disabled" -- and that is_enabled returns rather than terminating its caller. Copies synced with scripts/sync-hook-utils.sh; CI rejects drift. Co-Authored-By: Claude Opus 5 --- lib/hook-utils.sh | 30 ++++++-- lib/hook-utils.test.sh | 72 +++++++++++++++++++ plugins/actionlint/hooks/hook-utils.sh | 30 ++++++-- plugins/autonomy/hooks/hook-utils.sh | 30 ++++++-- plugins/bash-format/hooks/hook-utils.sh | 30 ++++++-- plugins/biome-format/hooks/hook-utils.sh | 30 ++++++-- plugins/claude-ops/hooks/hook-utils.sh | 30 ++++++-- plugins/context-guard/hooks/hook-utils.sh | 30 ++++++-- .../desktop-notification/hooks/hook-utils.sh | 30 ++++++-- plugins/eol-normalizer/hooks/hook-utils.sh | 30 ++++++-- plugins/go-format/hooks/hook-utils.sh | 30 ++++++-- plugins/guardrails/hooks/hook-utils.sh | 30 ++++++-- plugins/markdown-format/hooks/hook-utils.sh | 30 ++++++-- plugins/powershell-format/hooks/hook-utils.sh | 30 ++++++-- plugins/rate-limit-guard/hooks/hook-utils.sh | 30 ++++++-- plugins/ruff-format/hooks/hook-utils.sh | 30 ++++++-- plugins/source-control/hooks/hook-utils.sh | 30 ++++++-- plugins/typos-format/hooks/hook-utils.sh | 30 ++++++-- 18 files changed, 514 insertions(+), 68 deletions(-) diff --git a/lib/hook-utils.sh b/lib/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/lib/hook-utils.sh +++ b/lib/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/lib/hook-utils.test.sh b/lib/hook-utils.test.sh index 79865d1d8..2f3524c2c 100755 --- a/lib/hook-utils.test.sh +++ b/lib/hook-utils.test.sh @@ -2295,6 +2295,78 @@ else fail "jq_fields early-return flag: expected 0, got '$HOOK_JQ_FIELDS_NUL'" fi +# --- Test 20: hook::is_enabled / hook::check_enabled -------------------------- +# `check_enabled` exits the process when a plugin is gated off, so a case cannot +# be asserted in-process: each runs in a child bash that sources the lib and +# prints a sentinel only if the gate let it through. Absence of the sentinel IS +# the "skipped" signal. Each probe unsets the control variable first so an +# ambient value in the test runner's own environment cannot mask a regression. +# +# Scope note: this gate reads ONLY the plugin's own userConfig mirror. Turning +# the whole fleet off is Claude Code's job (`--safe-mode`, `disableAllHooks`, +# `claude plugin disable`), not this library's — a second fleet-wide switch here +# would be a competing source of truth for the same question. +ce_probe() { + # ce_probe [VAR=VALUE ...] + local want="$1" desc="$2" + shift 2 + local got + got=$( + env -u CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED \ + "$@" bash -c 'source "$0"; hook::check_enabled "RATE_LIMIT_GUARD"; printf RUN' \ + "$HOOK_DIR/hook-utils.sh" 2>/dev/null + ) + got="${got:-SKIP}" + if [[ "$got" == "$want" ]]; then + ok "check_enabled: $desc" + else + fail "check_enabled: $desc — got '$got', want '$want'" + fi +} + +# The default must not move: every existing installation leaves this unset, so a +# regression here silently changes behavior for every user of the marketplace. +ce_probe RUN "unset stays enabled (backward compatibility)" +ce_probe RUN "explicit true" CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED=true +ce_probe SKIP "explicit false" CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED=false +# Anything that is not exactly "true" is off — a typo must not read as enabled. +ce_probe SKIP "a non-boolean value is not 'true'" CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED=yes +# An EMPTY value is treated as unset, not as false: `${var:-true}` falls back to +# the default. This is long-standing behavior, asserted here so a future change +# to the parameter expansion (`:-` to `-`) can't flip it silently. It matters +# because Claude Code exports every declared userConfig option to the hook +# environment, so an option the user never answered arrives as an empty string — +# and that must mean "default", not "disabled". +ce_probe RUN "empty value falls back to the default (treated as unset)" \ + CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED= + +# hook::is_enabled is the predicate form: same answer, but it RETURNS instead of +# exiting. The statusline tee depends on this — it wraps the user's real +# statusline, so an exit would blank the status line instead of just skipping +# the tee's own write. +ie_probe() { + local want="$1" desc="$2" + shift 2 + local got + got=$( + env -u CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED \ + "$@" bash -c 'source "$0" + if hook::is_enabled "RATE_LIMIT_GUARD"; then printf ENABLED; else printf DISABLED; fi + printf "+SURVIVED"' "$HOOK_DIR/hook-utils.sh" 2>/dev/null + ) + if [[ "$got" == "$want" ]]; then + ok "is_enabled: $desc" + else + fail "is_enabled: $desc — got '$got', want '$want'" + fi +} +ie_probe "ENABLED+SURVIVED" "unset returns true and does not exit" +ie_probe "ENABLED+SURVIVED" "explicit true returns true" \ + CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED=true +# The critical one: a disabled answer must NOT terminate the caller. +ie_probe "DISABLED+SURVIVED" "explicit false returns false WITHOUT exiting" \ + CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED=false + echo echo "PASS=$PASS FAIL=$FAIL" [[ $FAIL -eq 0 ]] diff --git a/plugins/actionlint/hooks/hook-utils.sh b/plugins/actionlint/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/actionlint/hooks/hook-utils.sh +++ b/plugins/actionlint/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/autonomy/hooks/hook-utils.sh b/plugins/autonomy/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/autonomy/hooks/hook-utils.sh +++ b/plugins/autonomy/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/bash-format/hooks/hook-utils.sh b/plugins/bash-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/bash-format/hooks/hook-utils.sh +++ b/plugins/bash-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/biome-format/hooks/hook-utils.sh b/plugins/biome-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/biome-format/hooks/hook-utils.sh +++ b/plugins/biome-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/claude-ops/hooks/hook-utils.sh b/plugins/claude-ops/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/claude-ops/hooks/hook-utils.sh +++ b/plugins/claude-ops/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/context-guard/hooks/hook-utils.sh b/plugins/context-guard/hooks/hook-utils.sh index dcb0e820f..e88315e72 100755 --- a/plugins/context-guard/hooks/hook-utils.sh +++ b/plugins/context-guard/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/desktop-notification/hooks/hook-utils.sh b/plugins/desktop-notification/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/desktop-notification/hooks/hook-utils.sh +++ b/plugins/desktop-notification/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/eol-normalizer/hooks/hook-utils.sh b/plugins/eol-normalizer/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/eol-normalizer/hooks/hook-utils.sh +++ b/plugins/eol-normalizer/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/go-format/hooks/hook-utils.sh b/plugins/go-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/go-format/hooks/hook-utils.sh +++ b/plugins/go-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/guardrails/hooks/hook-utils.sh b/plugins/guardrails/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/guardrails/hooks/hook-utils.sh +++ b/plugins/guardrails/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/markdown-format/hooks/hook-utils.sh b/plugins/markdown-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/markdown-format/hooks/hook-utils.sh +++ b/plugins/markdown-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/powershell-format/hooks/hook-utils.sh b/plugins/powershell-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/powershell-format/hooks/hook-utils.sh +++ b/plugins/powershell-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/rate-limit-guard/hooks/hook-utils.sh b/plugins/rate-limit-guard/hooks/hook-utils.sh index dcb0e820f..e88315e72 100755 --- a/plugins/rate-limit-guard/hooks/hook-utils.sh +++ b/plugins/rate-limit-guard/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/ruff-format/hooks/hook-utils.sh b/plugins/ruff-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/ruff-format/hooks/hook-utils.sh +++ b/plugins/ruff-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/source-control/hooks/hook-utils.sh b/plugins/source-control/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/source-control/hooks/hook-utils.sh +++ b/plugins/source-control/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- diff --git a/plugins/typos-format/hooks/hook-utils.sh b/plugins/typos-format/hooks/hook-utils.sh index dcb0e820f..e88315e72 100644 --- a/plugins/typos-format/hooks/hook-utils.sh +++ b/plugins/typos-format/hooks/hook-utils.sh @@ -16,11 +16,33 @@ readonly _HOOK_UTILS_LOADED=1 # read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. # Exits 0 (allow) if disabled. Place after source, before stdin parsing. # hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED -hook::check_enabled() { +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" - if [[ "${!var_name:-true}" != "true" ]]; then - exit 0 - fi + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 } # --- Prerequisite visibility -------------------------------------------------- From 07502007ebb56c91ea2dab43c4733daaead5899c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:48:36 -0400 Subject: [PATCH 2/9] fix(rate-limit-guard): honor rate_limit_guard_enabled in the statusline tee scripts/statusline-tee.sh is invoked by absolute path from the user's settings.json statusLine, not by the plugin hook runner, so it ran whatever the plugin enablement said. It was the one code path in this plugin that kept writing rate-limits.json while the plugin was disabled. Gate the snapshot on the plugin's own option via hook::is_enabled. The passthrough stays unconditional and byte-identical either way; check_enabled would have exited 0 and blanked the status line. If the shared library cannot be read the tee still runs, consistent with this script's rule that no tee outcome ever alters the wrapped statusline. Co-Authored-By: Claude Opus 5 --- .../.claude-plugin/plugin.json | 2 +- plugins/rate-limit-guard/CHANGELOG.md | 18 ++++++++++++++ .../scripts/statusline-tee.sh | 24 ++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/plugins/rate-limit-guard/.claude-plugin/plugin.json b/plugins/rate-limit-guard/.claude-plugin/plugin.json index fdc2835fc..0170c3310 100644 --- a/plugins/rate-limit-guard/.claude-plugin/plugin.json +++ b/plugins/rate-limit-guard/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "rate-limit-guard", - "version": "0.5.4", + "version": "0.5.5", "description": "Shared rate-limit guard for loop lanes: a statusline wrapper tees the subscription rate-limit windows to a fixed machine-scope file, a StopFailure hook records rate-limit stops reactively, and a reader contract fixes how consuming sessions pause and resume.", "author": { "name": "Melodic Software", diff --git a/plugins/rate-limit-guard/CHANGELOG.md b/plugins/rate-limit-guard/CHANGELOG.md index 660bf6bc1..87358d1e6 100644 --- a/plugins/rate-limit-guard/CHANGELOG.md +++ b/plugins/rate-limit-guard/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to the `rate-limit-guard` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.5] + +### Fixed + +- **The statusline tee ignored `rate_limit_guard_enabled` and wrote on every render regardless.** + `scripts/statusline-tee.sh` is invoked by absolute path from the user's `settings.json` + `statusLine`, not by the plugin hook runner, so it was reached whatever the plugin's enablement + said — it was the one code path in this plugin that kept running while the plugin was disabled, + rewriting `~/.claude/rate-limit-guard/rate-limits.json` on the statusline's refresh cadence. It + now consults the option before taking the snapshot. + + The gate uses the new `hook::is_enabled` predicate rather than `hook::check_enabled`. The tee is + a **transparent wrapper** around the user's real statusline: `check_enabled` exits 0, which here + would have suppressed the wrapped command's stdout and blanked the status line. Only the tee's + own write is skipped; the passthrough is unconditional and byte-identical either way. If the + shared library cannot be read the tee still runs, consistent with this script's existing rule + that no tee outcome ever alters the wrapped statusline. + ## [0.5.4] ### Fixed diff --git a/plugins/rate-limit-guard/scripts/statusline-tee.sh b/plugins/rate-limit-guard/scripts/statusline-tee.sh index 4da949155..5190b7fe5 100755 --- a/plugins/rate-limit-guard/scripts/statusline-tee.sh +++ b/plugins/rate-limit-guard/scripts/statusline-tee.sh @@ -248,7 +248,29 @@ tee_snapshot() { return 0 } -tee_snapshot +# The tee's WRITE is plugin behavior and is gated like any other hook's. Two +# things make this call site different from a normal `hook::check_enabled`: +# +# 1. This script is a TRANSPARENT statusline wrapper. `hook::check_enabled` +# exits 0 on "disabled", which here would suppress the wrapped command's +# stdout and blank the user's status line. The predicate form skips only +# the snapshot; the passthrough below stays unconditional. +# 2. This script is invoked BY ABSOLUTE PATH from settings.json `statusLine`, +# not by the plugin hook runner, so it is reached whatever the plugin's +# enablement says. Without this gate it is the one code path in the plugin +# that keeps running when the plugin is off. +# +# The library is the plugin's own synced copy in the sibling hooks/ directory. +# If it cannot be read the tee still runs, consistent with this script's rule +# that no tee outcome ever alters the wrapped statusline. +_rlg_hook_utils="$(cd "$(dirname "${BASH_SOURCE[0]}")/../hooks" 2>/dev/null && pwd)/hook-utils.sh" +if [[ -r "$_rlg_hook_utils" ]]; then + # shellcheck source=../hooks/hook-utils.sh + source "$_rlg_hook_utils" +fi +if ! declare -F hook::is_enabled >/dev/null 2>&1 || hook::is_enabled "RATE_LIMIT_GUARD"; then + tee_snapshot +fi if (($#)); then # Wrapped mode: transparent passthrough. The wrapped command sees the same From 65b396a4772970785f20530ba245990bd5f10d7a Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:48:37 -0400 Subject: [PATCH 3/9] fix(source-control): declare worktree_create_gate_enabled hooks/worktree-create-gate.sh reads CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED and names the option in its own skip message, but the option was never declared in the manifest. Claude Code exports CLAUDE_PLUGIN_OPTION_ only for declared options, so the variable was never set, the hook's :-true fallback always won, and the gate could not be turned off. Setting it produced no effect and no error. Declared with default true, so behaviour is unchanged for anyone who does not set it. Co-Authored-By: Claude Opus 5 --- plugins/source-control/.claude-plugin/plugin.json | 8 +++++++- plugins/source-control/CHANGELOG.md | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/source-control/.claude-plugin/plugin.json b/plugins/source-control/.claude-plugin/plugin.json index 240883c2a..506dd596f 100644 --- a/plugins/source-control/.claude-plugin/plugin.json +++ b/plugins/source-control/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "source-control", - "version": "0.51.6", + "version": "0.51.7", "description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-Authored-By trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop — safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and standing merge-rung raises binding from the team-tracked layer only — with one named exception, where an invocation line explicitly typing both the autopilot tier keyword and the dedicated raise argument --merge c3-this-run widens that single invocation's merge authority up to C3 behind a fresh independent frontier-tier resolver, while C4-structural and C5-untrusted-provenance stay unconditionally human-merge), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply — interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep — never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.", "author": { "name": "Melodic Software", @@ -40,6 +40,12 @@ "description": "Block a GitHub MCP create_pull_request/update_pull_request whose PR body would fail the repository's required pr-issue-linkage check — the MCP-surface sibling of pr-body-linkage-gate, covering cloud/remote sessions that open PRs without the gh CLI. Same policy scope: enforced only in a repository that carries .github/workflows/pr-issue-linkage.yml, and only for the repository the origin remote names.", "default": true }, + "worktree_create_gate_enabled": { + "type": "boolean", + "title": "worktree-create-gate hook", + "description": "Redirect a WorktreeCreate away from Claude Code's default location, which may be inside the repository, to the configured worktree_root. The hook already reads this option and names it in its own skip message, but the option was never declared here — so Claude Code never exported CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED, the hook's `:-true` fallback always won, and the gate could not be turned off.", + "default": true + }, "babysit_watched_owners": { "type": "string", "multiple": true, diff --git a/plugins/source-control/CHANGELOG.md b/plugins/source-control/CHANGELOG.md index ed0c285f5..d1787e7ea 100644 --- a/plugins/source-control/CHANGELOG.md +++ b/plugins/source-control/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to the `source-control` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.51.7] + +### Fixed + +- **`worktree_create_gate_enabled` could not be turned off.** `hooks/worktree-create-gate.sh` + reads `CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED` and names the option in its own skip + message, but the option was never declared in `.claude-plugin/plugin.json`. Claude Code exports + `CLAUDE_PLUGIN_OPTION_` only for **declared** options, so the variable was never set, the + hook's `:-true` fallback always won, and the gate ran unconditionally. Setting the option + produced no effect and no error — the failure was silent in both directions. The declaration is + now present with `default: true`, so behaviour is unchanged for anyone who does not set it, and + the documented routes for setting it now work. + ## [0.51.6] ### Changed From ce2d500a392abc7b639ce3ba2d39856636807362 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:48:51 -0400 Subject: [PATCH 4/9] docs(plugins): generate a per-plugin options reference and gate it in CI A plugin's userConfig options were only discoverable by reading its manifest. The /plugin UI is awkward to browse, and 54 of 65 READMEs carried a hand-written Configuration section whose completeness nothing enforced -- an option added later was silently undocumented. Generate an options-reference block into each plugin README from its own manifest: every option with its type, default, the CLAUDE_PLUGIN_OPTION_ environment variable each hook reads it from, and the three supported ways to set it (the /plugin configure prompt, claude plugin install --config, and a pluginConfigs entry in user settings), plus links to the upstream docs. Notes the trap that plugin option values are read from user, --settings and managed settings only, never from a project's .claude/settings.json. The block is marker-fenced and generated, so hand-written prose is preserved untouched -- actionlint's account of its stdin timeout is nuance a generator cannot produce. The generator guarantees completeness and freshness; the prose keeps the nuance. Both live in the plugin's own folder so they change together. Same contract as the shared hook library: the manifest is the single source of truth, the block is never hand-edited, and CI runs --check and rejects drift. The gate is wired into the ci-status needs graph, so it is required rather than informational. 32 plugins declare 130 options. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 17 +++ plugins/actionlint/README.md | 57 +++++++++ plugins/ai-briefing/README.md | 56 ++++++++ plugins/autonomy/README.md | 62 +++++++++ plugins/bash-format/README.md | 56 ++++++++ plugins/biome-format/README.md | 56 ++++++++ plugins/bug-report/README.md | 56 ++++++++ plugins/claude-ops/README.md | 69 ++++++++++ plugins/context-guard/README.md | 58 +++++++++ plugins/desktop-notification/README.md | 59 +++++++++ plugins/discipline/README.md | 59 +++++++++ plugins/disk-hygiene/README.md | 56 ++++++++ plugins/dometrain/README.md | 56 ++++++++ plugins/education/README.md | 57 +++++++++ plugins/eol-normalizer/README.md | 56 ++++++++ plugins/github/README.md | 56 ++++++++ plugins/go-format/README.md | 56 ++++++++ plugins/guardrails/README.md | 73 +++++++++++ plugins/knowledge/README.md | 60 +++++++++ plugins/machine-health/README.md | 56 ++++++++ plugins/markdown-format/README.md | 58 +++++++++ plugins/miro/README.md | 56 ++++++++ plugins/planning/README.md | 57 +++++++++ plugins/powershell-format/README.md | 56 ++++++++ plugins/rate-limit-guard/README.md | 56 ++++++++ plugins/repo-hygiene/README.md | 56 ++++++++ plugins/ruff-format/README.md | 56 ++++++++ plugins/session-flow/README.md | 62 +++++++++ plugins/skill-quality/README.md | 56 ++++++++ plugins/source-control/README.md | 89 +++++++++++++ plugins/typos-format/README.md | 57 +++++++++ plugins/visualization/README.md | 56 ++++++++ plugins/work-items/README.md | 62 +++++++++ scripts/sync-plugin-options-docs.py | 171 +++++++++++++++++++++++++ 34 files changed, 2079 insertions(+) create mode 100644 scripts/sync-plugin-options-docs.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c22b59fdc..53d00b513 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -424,6 +424,22 @@ jobs: - name: Check plugin hook configs for bare userConfig argv tokens run: scripts/check-hook-userconfig-argv.sh + # Every option a plugin declares must be documented in that plugin's README. + # The block is generated from the manifest, so the failure this catches is an + # option added to plugin.json whose README block was never regenerated -- an + # undocumented knob is indistinguishable from one that does not exist, and the + # only place a user can discover it is the manifest itself. + plugin-options-docs-gate: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Check plugin option docs are in sync with each manifest + run: python3 scripts/sync-plugin-options-docs.py --check + silent-skip-gate: runs-on: ubuntu-24.04 timeout-minutes: 15 @@ -1030,6 +1046,7 @@ jobs: - cross-plugin-source-drift - skill-leaf-name-gate - userconfig-argv-gate + - plugin-options-docs-gate - silent-skip-gate - plugin-manifest-presence-gate - orphaned-fixture-gate diff --git a/plugins/actionlint/README.md b/plugins/actionlint/README.md index 8af4b5d63..3d0f0f052 100644 --- a/plugins/actionlint/README.md +++ b/plugins/actionlint/README.md @@ -79,6 +79,63 @@ here applies across every project. To disable actionlint for a single repository, disable the plugin in that project's `enabledPlugins` rather than setting `actionlint_enabled`. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `actionlint_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_ACTIONLINT_ENABLED` | Lint GitHub Actions workflow files on edit via actionlint | +| `stdin_read_timeout` | number | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure actionlint`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install actionlint@melodic-software --config actionlint_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "actionlint@melodic-software": { + "options": { + "actionlint_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/ai-briefing/README.md b/plugins/ai-briefing/README.md index f8183925a..bf8c9e150 100644 --- a/plugins/ai-briefing/README.md +++ b/plugins/ai-briefing/README.md @@ -79,3 +79,59 @@ cannot escape that boundary. Machine-local state and generated artifacts live under `${CLAUDE_PLUGIN_DATA}`, keyed by profile. Tracked source, audience, and brand configuration always stays in the consumer repository. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `active_profile` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_ACTIVE_PROFILE` | Portable 1-63 character lowercase-kebab name of the ai-briefing profile to use; reserved Windows device names are not allowed. Leave unset when there is a single profile (or only the default). A per-invocation --profile argument overrides this. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure ai-briefing`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install ai-briefing@melodic-software --config active_profile= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "ai-briefing@melodic-software": { + "options": { + "active_profile": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/autonomy/README.md b/plugins/autonomy/README.md index f4e6b4b37..3127804b0 100644 --- a/plugins/autonomy/README.md +++ b/plugins/autonomy/README.md @@ -174,3 +174,65 @@ config outlives any plugin restructure). Personal overlays follow the marketplac convention: `.claude/autonomy/**/*.local.*` stays gitignored; layers resolve per the binding-seam ladder — user-global → org binding (when pointed) → project → local overlay — additively. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `lane_stop_gate_enabled` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_ENABLED` | Opt an autonomous lane into the deterministic Stop-hook completion gate. Default OFF — a Stop-blocking hook must never engage for an interactive session. Honored from user or managed settings only (the gate reads those files itself); per-session lanes are armed by the claude-ops lane launcher instead. The env mirror is never authority (#1784). | +| `lane_stop_gate_sentinel` | string | `"LANE-STOP-OK"` | `CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_SENTINEL` | The exact token the agent emits in its final message to declare the lane's goal met and authorize a stop. Matched only when alone on its own line. Honored from user/managed settings or the launcher's arm record, never the bare environment. | +| `lane_stop_gate_marker` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_MARKER` | Optional path to a completion-marker file whose existence also authorizes a stop (absolute, or relative to the session cwd). Empty disables the file signal. Honored from user/managed settings or the launcher's arm record, never the bare environment. | +| `lane_stop_gate_arm_id` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_ARM_ID` | Written by the lane launcher at launch: names this session's arm record in the plugin's own data directory (hooks/lane-stop-gate-arm.sh). A capability pointer, never authority by itself — the gate validates it, honors only a record in its install-derived store, and binds it to the first presenting session. Not set by hand. | +| `lane_notify_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_LANE_NOTIFY_ENABLED` | Master switch for the operator alert fired when a lane stops without signaling completion. | +| `lane_notify_os_toast_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_LANE_NOTIFY_OS_TOAST_ENABLED` | OS-native desktop toast (macOS/Linux) for the lane-stop alert. | +| `lane_notify_terminal_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_LANE_NOTIFY_TERMINAL_ENABLED` | Audible bell + OSC 9 notification written to the controlling terminal for the lane-stop alert. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure autonomy`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install autonomy@melodic-software --config lane_stop_gate_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "autonomy@melodic-software": { + "options": { + "lane_stop_gate_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/bash-format/README.md b/plugins/bash-format/README.md index c78cfcfda..bf08d89b0 100644 --- a/plugins/bash-format/README.md +++ b/plugins/bash-format/README.md @@ -101,6 +101,62 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the hook off for a single repository, disable the whole plugin in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `bash_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BASH_FORMAT_ENABLED` | Lint and format shell scripts on edit via ShellCheck + shfmt | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure bash-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install bash-format@melodic-software --config bash_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "bash-format@melodic-software": { + "options": { + "bash_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/biome-format/README.md b/plugins/biome-format/README.md index c17c304a9..8984de91e 100644 --- a/plugins/biome-format/README.md +++ b/plugins/biome-format/README.md @@ -87,6 +87,62 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the hook off for a single repository, disable the whole plugin in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `biome_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BIOME_FORMAT_ENABLED` | Format and lint JS/TS/JSX/JSON on edit via Biome | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure biome-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install biome-format@melodic-software --config biome_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "biome-format@melodic-software": { + "options": { + "biome_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/bug-report/README.md b/plugins/bug-report/README.md index 6dabb914e..e4c24029a 100644 --- a/plugins/bug-report/README.md +++ b/plugins/bug-report/README.md @@ -80,6 +80,62 @@ Otherwise the emitted report is the deliverable — copy it into your tracker. /plugin install bug-report@melodic-software ``` + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `output_dir` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_OUTPUT_DIR` | Where --file writes reports. When unset, reports go to the plugin's own persistent data directory. Set this to a path in your repository if you want bug reports committed alongside your code. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure bug-report`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install bug-report@melodic-software --config output_dir= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "bug-report@melodic-software": { + "options": { + "output_dir": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/claude-ops/README.md b/plugins/claude-ops/README.md index 3b65fe961..e35c49a1b 100644 --- a/plugins/claude-ops/README.md +++ b/plugins/claude-ops/README.md @@ -237,6 +237,75 @@ project-relative defaults; the bundled scripts make no outbound network calls except `gh`/`curl` reads of GitHub and Claude status pages in the known-issues skill. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `registry_dir` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_REGISTRY_DIR` | Optional contained project-relative directory holding the known-issues registry (registry.json). Absolute, drive, UNC, traversal, and escaping-symlink paths are invalid. Leave unset to use ${CLAUDE_PLUGIN_DATA}. | +| `skill_usage_dir` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_SKILL_USAGE_DIR` | Optional contained relative directory where the skill-usage-audit hooks write skill-usage.jsonl, resolved under the skill_usage_scope root (repo scope: the project root; user scope: $HOME). Absolute, drive, UNC, traversal, and escaping-symlink paths are invalid in every scope. Ignored by the data-dir scope (plugin-owned layout). Leave unset to use .claude/observability. | +| `skill_usage_scope` | string | `"repo"` | `CLAUDE_PLUGIN_OPTION_SKILL_USAGE_SCOPE` | Where the skill-usage store lives. Valid values: "repo" (default — project tree under the repo root, kept out of git status via a machine-local .git/info/exclude entry), "user" (the skill_usage_dir subpath under $HOME, one cross-repo store; rows carry a project field), "data-dir" (${CLAUDE_PLUGIN_DATA}/skill-usage/, plugin-owned and update-safe). The manifest schema has no enum type, so this validates in prose; any other value is treated as "repo" with a one-time advisory. | +| `skill_usage_git_exclude` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_SKILL_USAGE_GIT_EXCLUDE` | When the repo-scope store sits inside a git work tree, idempotently add its directory to .git/info/exclude (machine-local; never touches .gitignore or tracked files) so git status stays clean. Set false if your team deliberately commits the telemetry. | +| `install_new` | string | `"ask"` | `CLAUDE_PLUGIN_OPTION_INSTALL_NEW` | Controls what `sync` does with catalog plugins that aren't installed yet. Valid values: "ask" (default — offer them in one batched multi-select prompt), "all" (install every one automatically), "none" (report only, never install). The manifest schema has no enum type, so this validates in prose, not JSON Schema; any other value is treated as "ask". | +| `api_error_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_API_ERROR_AUDIT_ENABLED` | Emit turn-failure telemetry on API errors | +| `config_change_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_CONFIG_CHANGE_AUDIT_ENABLED` | Emit telemetry on config-source mutations | +| `instructions_loaded_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_INSTRUCTIONS_LOADED_AUDIT_ENABLED` | Emit telemetry on rule/instruction file loads | +| `permission_denied_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_PERMISSION_DENIED_AUDIT_ENABLED` | Emit telemetry on permission denials | +| `pre_compact_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_PRE_COMPACT_AUDIT_ENABLED` | Emit telemetry on context-compaction events | +| `skill_usage_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_SKILL_USAGE_AUDIT_ENABLED` | Emit telemetry on skill usage; shared by both skill-usage audit hooks (the Skill-tool and slash-command expansion paths) and also gates the shared skill-usage.jsonl store | +| `tool_failure_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_TOOL_FAILURE_AUDIT_ENABLED` | Emit telemetry on Write/Edit/Bash tool failures | +| `instructions_loaded_audit_log_session_start` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_INSTRUCTIONS_LOADED_AUDIT_LOG_SESSION_START` | Opt back into logging session_start instruction loads (dropped by default as deterministic and high-volume) | +| `stdin_read_timeout` | number | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure claude-ops`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install claude-ops@melodic-software --config registry_dir= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "claude-ops@melodic-software": { + "options": { + "registry_dir": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/context-guard/README.md b/plugins/context-guard/README.md index 57735a976..f19921e82 100644 --- a/plugins/context-guard/README.md +++ b/plugins/context-guard/README.md @@ -122,6 +122,64 @@ The plugin's own zone-crossing hooks are the first shipped consumer. Next: the ` audit skill (zone-informed dispatch and evidence-flush decisions, conservative on `unknown`). Any session or tool on the machine may read the same files under the same contract. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `context_guard_hooks_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_CONTEXT_GUARD_HOOKS_ENABLED` | Master switch for the zone-crossing injection, blocking gate, and PostCompact marker hooks | +| `zone_hook_mode` | string | `"advisory"` | `CLAUDE_PLUGIN_OPTION_ZONE_HOOK_MODE` | advisory (default) injects guidance only; blocking additionally denies new Write/Edit/NotebookEdit/Agent/Workflow calls on a fresh dumb-zone snapshot past the grace budget (fail-open on unknown; handoff-path writes, reads, Bash, and Skill stay allowed) | +| `zone_gate_grace_calls` | string | `"20"` | `CLAUDE_PLUGIN_OPTION_ZONE_GATE_GRACE_CALLS` | Blocking mode only: number of matched tool calls allowed after the session first resolves dumb before the gate denies (in-script default 20) | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure context-guard`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install context-guard@melodic-software --config context_guard_hooks_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "context-guard@melodic-software": { + "options": { + "context_guard_hooks_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/desktop-notification/README.md b/plugins/desktop-notification/README.md index 5acd7e136..82105cdb7 100644 --- a/plugins/desktop-notification/README.md +++ b/plugins/desktop-notification/README.md @@ -90,6 +90,65 @@ When the consumer sets `HOOK_TELEMETRY_SINK` to an executable, the hook emits on `hook: "desktop-notification"`, `hook_event: "Notification"`, and a `data` payload of `notification_type` plus the `channels` that fired. Unset → exact no-op. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `desktop_notification_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_DESKTOP_NOTIFICATION_ENABLED` | Master switch for the whole notification hook | +| `desktop_notification_bell_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_DESKTOP_NOTIFICATION_BELL_ENABLED` | Audible terminal bell (bare BEL) | +| `desktop_notification_terminal_notify_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_DESKTOP_NOTIFICATION_TERMINAL_NOTIFY_ENABLED` | OSC 9 terminal notification emitted via the hook's terminalSequence output | +| `desktop_notification_os_toast_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_DESKTOP_NOTIFICATION_OS_TOAST_ENABLED` | OS-native desktop toast (macOS/Linux) | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure desktop-notification`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install desktop-notification@melodic-software --config desktop_notification_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "desktop-notification@melodic-software": { + "options": { + "desktop_notification_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/discipline/README.md b/plugins/discipline/README.md index 0e47d74a3..f3bca3e95 100644 --- a/plugins/discipline/README.md +++ b/plugins/discipline/README.md @@ -388,3 +388,62 @@ read-only (it never writes config — reconfiguration stays the native flow). Batch membership and order otherwise live in each corrector's own colocated tier metadata (`metadata.discipline-batch` + `discipline-batch-rank`), so changing a shipped tier is a PR to that corrector. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `batch_exclude` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BATCH_EXCLUDE` | Comma-separated corrector skill names to drop from the posture batch (for example: point-dont-copy). Overrides the corrector's own declared tier. Empty runs the tiers exactly as the correctors declare them. | +| `batch_promote` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BATCH_PROMOTE` | Comma-separated situational corrector skill names to always run in the batch instead of gating them on relevance to the conversation. | +| `batch_demote` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BATCH_DEMOTE` | Comma-separated core corrector skill names to run only when relevant to the conversation instead of every session. | +| `research_deep_verification` | string | `"tiered"` | `CLAUDE_PLUGIN_OPTION_RESEARCH_DEEP_VERIFICATION` | Default verification depth for do-your-research-deep: 'tiered' (the default — resolve trivial and non-load-bearing inventory items inline, fan fresh-context subagents out only over the load-bearing ones) or 'full' (subagent-verify every inventory item). An invocation argument overrides this. An empty value, an unexpanded ${user_config.…} token, or an unrecognized string all fall back to tiered. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure discipline`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install discipline@melodic-software --config batch_exclude= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "discipline@melodic-software": { + "options": { + "batch_exclude": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/disk-hygiene/README.md b/plugins/disk-hygiene/README.md index 4ff4b4326..c781a179a 100644 --- a/plugins/disk-hygiene/README.md +++ b/plugins/disk-hygiene/README.md @@ -250,6 +250,62 @@ Verified 2026-07-16 against current primary documentation: [Linux `unlink(2)`](https://man7.org/linux/man-pages/man2/unlink.2.html) — open-file unlink semantics motivate an explicit preflight rather than relying on deletion failure. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `disk_hygiene_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLED` | Allow the clean skill’s execution tiers; false = audit-only mode | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure disk-hygiene`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install disk-hygiene@melodic-software --config disk_hygiene_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "disk-hygiene@melodic-software": { + "options": { + "disk_hygiene_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). See the repository root `LICENSE`. diff --git a/plugins/dometrain/README.md b/plugins/dometrain/README.md index 60c6cb349..03eee3efd 100644 --- a/plugins/dometrain/README.md +++ b/plugins/dometrain/README.md @@ -145,3 +145,59 @@ remains Dometrain's proprietary content, accessible under your Dometrain Pro sub This plugin ships no server code — the MCP server is Dometrain-hosted. There is no build step; `claude plugin validate plugins/dometrain` is the only local check. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `dometrain_api_key` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_DOMETRAIN_API_KEY` | **Sensitive** — stored in the OS keychain or protected credentials file. Dometrain account API key from https://dometrain.com/dashboard/account/ (MCP API keys section). Required — the remote MCP server rejects requests without it. Stored by Claude Code in secure credential storage, never settings.json. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure dometrain`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install dometrain@melodic-software --config dometrain_api_key= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "dometrain@melodic-software": { + "options": { + "dometrain_api_key": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/education/README.md b/plugins/education/README.md index c85870719..51e944847 100644 --- a/plugins/education/README.md +++ b/plugins/education/README.md @@ -92,6 +92,63 @@ Configure them through the `/plugin` dialog, or headless at install time with non-home `report_library_dir` may be rejected by the hardcoded-path guardrails until the #798 path-indirection work lands. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `quiz_policy` | string | `"on-request"` | `CLAUDE_PLUGIN_OPTION_QUIZ_POLICY` | When quiz-me offers a post-work comprehension quiz. One of: off (never offers), on-request (only when asked), always (after each completed change), above-threshold (when the change is large). Governs offer cadence only — a report is never generated without your confirmation. Unknown values are treated as on-request. | +| `report_library_dir` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_REPORT_LIBRARY_DIR` | Where quiz-me stores generated reports and quizzes. Unset uses the plugin's own persistent data directory; set it to a corpus checkout to redirect the library root there. Artifacts never land in the consuming repo's tree. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure education`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install education@melodic-software --config quiz_policy= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "education@melodic-software": { + "options": { + "quiz_policy": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/eol-normalizer/README.md b/plugins/eol-normalizer/README.md index f82daa1e9..eadf55894 100644 --- a/plugins/eol-normalizer/README.md +++ b/plugins/eol-normalizer/README.md @@ -78,6 +78,62 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the plugin off for a single repository, disable it in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `eol_normalizer_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_EOL_NORMALIZER_ENABLED` | Normalize a written file's line endings to its .gitattributes eol value | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure eol-normalizer`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install eol-normalizer@melodic-software --config eol_normalizer_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "eol-normalizer@melodic-software": { + "options": { + "eol_normalizer_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/github/README.md b/plugins/github/README.md index b99f55238..66a83d1a7 100644 --- a/plugins/github/README.md +++ b/plugins/github/README.md @@ -76,3 +76,59 @@ From whichever marketplace distributes this plugin: /plugin marketplace add / /plugin install github@ ``` + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `offer_browser_automation` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_OFFER_BROWSER_AUTOMATION` | When a settings surface is UI-only, offer opt-in browser automation (never auto-fired; every action individually confirmed). Set false to suppress the offer entirely. Advisory: honored by the skills' prose, layered under the per-action confirm. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure github`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install github@melodic-software --config offer_browser_automation= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "github@melodic-software": { + "options": { + "offer_browser_automation": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/go-format/README.md b/plugins/go-format/README.md index 15c84d3f6..bcf7de560 100644 --- a/plugins/go-format/README.md +++ b/plugins/go-format/README.md @@ -96,6 +96,62 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the plugin off for a single repository, disable it in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `go_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_GO_FORMAT_ENABLED` | Run goimports -w on edit of a Go file | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure go-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install go-format@melodic-software --config go_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "go-format@melodic-software": { + "options": { + "go_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/guardrails/README.md b/plugins/guardrails/README.md index 1e7f7996d..982959c73 100644 --- a/plugins/guardrails/README.md +++ b/plugins/guardrails/README.md @@ -252,6 +252,79 @@ Then verify the runtime prerequisites and live guard surface with `/guardrails:setup check`; `/guardrails:setup apply` resolves anything the check reports with guidance. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `secret_pattern_detection_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_SECRET_PATTERN_DETECTION_ENABLED` | Block writes containing high-confidence secret/credential patterns | +| `hardcoded_path_check_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_HARDCODED_PATH_CHECK_ENABLED` | Block writes containing hardcoded machine-specific paths | +| `block_no_verify_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_ENABLED` | Block git hook-bypass attempts (--no-verify, core.hooksPath=, hook-manager env-var disables for a configurable set — lefthook/husky/pre-commit/simple-git-hooks by default) | +| `block_dangerous_git_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BLOCK_DANGEROUS_GIT_ENABLED` | Block irreversible git operations (push --force, push --force-with-lease leasing against a value git resolves at push time — either no expected value, or an expectation that is not an object id of the repository's own hash width — reset --hard, clean -f, worktree-wide checkout/restore discards) | +| `block_hook_bypass_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BLOCK_HOOK_BYPASS_ENABLED` | Block Bash file-write workarounds that circumvent Write/Edit hook gates | +| `block_noncanonical_commit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BLOCK_NONCANONICAL_COMMIT_ENABLED` | Block `git commit -m` when the message actually contains a newline (multi-line `-m` mangles across shells — pipe it via `-F -` instead; single-line `-m` passes); --amend, -C/-c, --fixup/--squash, -F , and an in-progress merge/rebase are exempt | +| `block_convention_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_BLOCK_CONVENTION_GATE_ENABLED` | Block a commit subject or `gh pr create --title` that violates the team-tracked convention pattern in .claude/source-control.md (no tracked pattern = no enforcement; same exemptions as block-noncanonical-commit) | +| `cli_flag_verify_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_CLI_FLAG_VERIFY_ENABLED` | Advise on hallucinated CLI flags written to files (never blocks) | +| `skill_reference_verify_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_SKILL_REFERENCE_VERIFY_ENABLED` | Advise when markdown cites a /plugin:skill reference this repo owns but cannot resolve (never blocks) | +| `stale_path_verify_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_STALE_PATH_VERIFY_ENABLED` | Advise when markdown cites a repo-relative path this repo's own history shows was removed and that is gone from the working tree (never blocks) | +| `workflow_resilience_check_enabled` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_WORKFLOW_RESILIENCE_CHECK_ENABLED` | Advise on un-throttled Workflow fan-out (never blocks). Default off since 0.20.0: a behavioral-class prose injector, config-disabled per the instruction-economy evidence gate (#2021) — set true to opt back in | +| `flag_commit_pr_skill_bypass_enabled` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_FLAG_COMMIT_PR_SKILL_BYPASS_ENABLED` | Advise when a direct gh pr create bypasses the source-control pull-request skill (never blocks). Default off since 0.20.0: a behavioral-class prose injector, config-disabled per the instruction-economy evidence gate (#2021) — set true to opt back in | +| `cli_flag_verify_bins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_CLI_FLAG_VERIFY_BINS` | Comma-separated binaries cli-flag-verify scans; empty uses the built-in default set | +| `cli_flag_verify_skip_bins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_CLI_FLAG_VERIFY_SKIP_BINS` | Comma-separated binaries cli-flag-verify must never scan | +| `block_dangerous_git_allow` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BLOCK_DANGEROUS_GIT_ALLOW` | Comma-separated forms block-dangerous-git permits: push-force, push-lease-unsafe, reset-hard, clean-force, checkout-dot, restore-dot, checkout-force; empty blocks all | +| `block_noncanonical_commit_allow` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BLOCK_NONCANONICAL_COMMIT_ALLOW` | Comma-separated form tokens to allow (currently: message-flag, which permits `-m` even when the message contains a newline) | +| `block_no_verify_hook_manager_prefixes` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_HOOK_MANAGER_PREFIXES` | Comma-separated hook-manager env-var name prefixes block-no-verify treats as a bypass when set to 0/false (e.g. lefthook,husky); empty uses the built-in default set (lefthook, husky, pre_commit, simple_git_hooks) | +| `stdin_read_timeout` | number | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long a silent pipe is tolerated before a blocking guard fails closed | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure guardrails`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install guardrails@melodic-software --config secret_pattern_detection_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "guardrails@melodic-software": { + "options": { + "secret_pattern_detection_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/knowledge/README.md b/plugins/knowledge/README.md index d2a049563..eb29b3df8 100644 --- a/plugins/knowledge/README.md +++ b/plugins/knowledge/README.md @@ -110,6 +110,66 @@ options above tune yt-dlp authentication and throttling; **course-platform credentials are intentionally not** `userConfig` — they stay in shell env vars because a `sensitive` option persists as plaintext on Windows today. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `library_dir` | directory | `"."` | `CLAUDE_PLUGIN_OPTION_LIBRARY_DIR` | Directory where synthesized knowledge artifacts land. Default is the consuming repo root; a relative value is resolved against the project directory. Portable non-project roots: an absolute path, a leading ~ (home-relative), or an environment-variable reference ${NAME} / %NAME% (e.g. ${KNOWLEDGE_CORPUS_DIR}) so a machine-varying root never needs a literal machine path in this stored value. A working-notes or artifacts convention declared in your own project's CLAUDE.md or rules takes precedence. | +| `yt_dlp_js_runtimes` | string | `"node"` | `CLAUDE_PLUGIN_OPTION_YT_DLP_JS_RUNTIMES` | JavaScript runtime yt-dlp uses for YouTube signature deciphering. Default 'node'. Set to 'off' to omit the --js-runtimes flag entirely. | +| `yt_dlp_cookies_file` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_YT_DLP_COOKIES_FILE` | Path to a Netscape-format cookies.txt for authenticated YouTube acquisition. Empty by default (unauthenticated, with automatic browser-cookie fallback on a bot check). Never commit cookie files. | +| `yt_dlp_cookies_from_browser` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_YT_DLP_COOKIES_FROM_BROWSER` | Browser to pull YouTube cookies from (e.g. chrome, firefox, edge), forcing one instead of the automatic platform-ordered fallback. Empty by default. A cookies file, when set, wins over this. | +| `max_concurrent_acquires` | number | `1` | `CLAUDE_PLUGIN_OPTION_MAX_CONCURRENT_ACQUIRES` | Cap on concurrent yt-dlp acquisition runs during a batch. Default 1; raising it increases HTTP 429 throttling risk. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure knowledge`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install knowledge@melodic-software --config library_dir= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "knowledge@melodic-software": { + "options": { + "library_dir": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/machine-health/README.md b/plugins/machine-health/README.md index 1c880e9d0..3689eb99d 100644 --- a/plugins/machine-health/README.md +++ b/plugins/machine-health/README.md @@ -82,6 +82,62 @@ mocks Win32/MSFT CIM types that resolve only there: pwsh -NoProfile -File plugins/machine-health/skills/audit/tests/Invoke-MachineHealthTests.ps1 ``` + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `report_dir` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_REPORT_DIR` | Directory where per-run health reports (reports/health-.md, e.g. health-2026-07-12T153327123Z.md) are written. Leave unset to use the default: Documents\MachineHealth under your user profile. Machine state (history, approvals, logs) is separate and always lives in the plugin data directory. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure machine-health`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install machine-health@melodic-software --config report_dir= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "machine-health@melodic-software": { + "options": { + "report_dir": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/markdown-format/README.md b/plugins/markdown-format/README.md index d6d817a8e..6561cc0db 100644 --- a/plugins/markdown-format/README.md +++ b/plugins/markdown-format/README.md @@ -170,6 +170,64 @@ These options are user-scoped (stored in your user settings, not the project's). To disable formatting for a single repository, disable the whole plugin in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `markdown_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED` | Auto-format and lint Markdown on Write/Edit of .md/.mdc files (runs only when the repo carries a markdownlint config) | +| `markdown_format_lint_gitignored` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_LINT_GITIGNORED` | By default the hook leaves gitignored files alone — a scratch tier the repo excludes is neither rewritten nor reported on. Set true to bypass THIS HOOK's git check; markdownlint-cli2's own ignores/gitignore config still applies downstream, so a path your markdownlint config also excludes stays untouched. | +| `markdown_format_max_findings` | number | `20` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_MAX_FINDINGS` | How many individual markdownlint violations are listed per run. The total count and the leading rule codes are always reported regardless. 0 = unlimited. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure markdown-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install markdown-format@melodic-software --config markdown_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "markdown-format@melodic-software": { + "options": { + "markdown_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/miro/README.md b/plugins/miro/README.md index 7afd3af3c..2a9648d8a 100644 --- a/plugins/miro/README.md +++ b/plugins/miro/README.md @@ -82,3 +82,59 @@ npm run verify-bundle # fail if dist/index.min.js drifts from src/ After editing `src/`, run `npm run bundle` and commit the regenerated `dist/index.min.js` alongside the source change. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `miro_api_token` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_MIRO_API_TOKEN` | **Sensitive** — stored in the OS keychain or protected credentials file. Miro REST API token from https://miro.com/app/settings/user-profile/apps. Required — the bundled MCP server exits at startup without it. Stored by Claude Code in secure credential storage, never settings.json. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure miro`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install miro@melodic-software --config miro_api_token= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "miro@melodic-software": { + "options": { + "miro_api_token": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/planning/README.md b/plugins/planning/README.md index 5aae9e4c8..0d320ad1c 100644 --- a/plugins/planning/README.md +++ b/plugins/planning/README.md @@ -65,6 +65,63 @@ self-ignoring `//` (default `.work/`). Run concern file `.claude/topic-docs.yaml` (`contract_dir`, `memory_dir`, `contract_tier: branch | local`); absent keys mean those documented defaults. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `use_ask_user_question` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_USE_ASK_USER_QUESTION` | When enabled, the planning skills' question rounds (interview, prd, design, plan) render a round of up to 4 independent questions through the AskUserQuestion tool instead of inline prose. Default: inline prose (dictation-friendly). | +| `use_emoji_question_markers` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_USE_EMOJI_QUESTION_MARKERS` | When enabled, each inline interview round question leads with a ❓ anchor on its Q line and its 'My recommendation:' line leads with ➡️. Purely presentational — Q numbering stays the functional handle, and persisted artifacts (ledger, register, Brief) never carry the emoji. Default: plain text. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure planning`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install planning@melodic-software --config use_ask_user_question= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "planning@melodic-software": { + "options": { + "use_ask_user_question": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/powershell-format/README.md b/plugins/powershell-format/README.md index c61e777e9..0e584be58 100644 --- a/plugins/powershell-format/README.md +++ b/plugins/powershell-format/README.md @@ -115,6 +115,62 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the hook off for a single repository, disable the whole plugin in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `powershell_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_POWERSHELL_FORMAT_ENABLED` | Format and lint PowerShell on edit via PSScriptAnalyzer | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure powershell-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install powershell-format@melodic-software --config powershell_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "powershell-format@melodic-software": { + "options": { + "powershell_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/rate-limit-guard/README.md b/plugins/rate-limit-guard/README.md index 3a56afd1c..5e67adc7f 100644 --- a/plugins/rate-limit-guard/README.md +++ b/plugins/rate-limit-guard/README.md @@ -105,6 +105,62 @@ Written for the loop-lane convention's three lanes (work-items `work-loop` and ` source-control `babysit-loop`), which inline the reader contract's operable floor. Any session or tool on the machine may read the same files under the same contract. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `rate_limit_guard_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED` | Master switch for the StopFailure detection hook | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure rate-limit-guard`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install rate-limit-guard@melodic-software --config rate_limit_guard_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "rate-limit-guard@melodic-software": { + "options": { + "rate_limit_guard_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/repo-hygiene/README.md b/plugins/repo-hygiene/README.md index 3c3a8ab0b..252a6be2c 100644 --- a/plugins/repo-hygiene/README.md +++ b/plugins/repo-hygiene/README.md @@ -90,6 +90,62 @@ and the `tree` tier's default-preserve classes to keep additional paths safe. A declared per-consumer override for the script-enforced protected list is a known extension point, not yet exposed as configuration. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `clean_destructive_guard_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_CLEAN_DESTRUCTIVE_GUARD_ENABLED` | Session-scoped PreToolUse guard blocking destructive Bash commands while the clean skill is active | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure repo-hygiene`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install repo-hygiene@melodic-software --config clean_destructive_guard_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "repo-hygiene@melodic-software": { + "options": { + "clean_destructive_guard_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/ruff-format/README.md b/plugins/ruff-format/README.md index d04d6181c..34c3f565e 100644 --- a/plugins/ruff-format/README.md +++ b/plugins/ruff-format/README.md @@ -96,6 +96,62 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the plugin off for a single repository, disable it in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `ruff_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_RUFF_FORMAT_ENABLED` | Run Ruff check --fix and format on edit of a Python file | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure ruff-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install ruff-format@melodic-software --config ruff_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "ruff-format@melodic-software": { + "options": { + "ruff_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/session-flow/README.md b/plugins/session-flow/README.md index b4038a7c4..56dff1fde 100644 --- a/plugins/session-flow/README.md +++ b/plugins/session-flow/README.md @@ -316,3 +316,65 @@ network-free (retro and running-retro use the same stdlib-only Python 3.10+ pars parser, and `reconcile` reads them read-only and mutates only the in-session task ledger); `continue-in-background` spawns a local `claude --bg` process, a new Claude Code session with ordinary session network access, but the skill itself performs no egress. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `observer_enabled` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_OBSERVER_ENABLED` | Opt in to the SessionStart hook that arms the detached running-retro observer for every real interactive session. Default off: installing session-flow changes no behavior until this is enabled. The manual `/session-flow:running-retro arm` action works regardless of this toggle. | +| `observer_analysis_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_OBSERVER_ANALYSIS_ENABLED` | When armed, run a headless post-session running-retro checkpoint after the observer detects the session ended (mtime-idle), writing the findings to the running-retro ledger. Off = the observer only distills observations and retains them under its plugin work dir for manual inspection; it does not analyze or write the ledger (no per-session Claude spend, no automatic in-session consumer). | +| `observer_analysis_model` | string | `"claude-haiku-4-5"` | `CLAUDE_PLUGIN_OPTION_OBSERVER_ANALYSIS_MODEL` | Model id for the headless post-end analysis run (the dominant cost lever). Defaults to the cheapest active tier; pin a different id to trade cost for depth. | +| `observer_analysis_bare` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_OBSERVER_ANALYSIS_BARE` | Drop auto-discovery (a further cost lever) on the analysis run. Off by default because --bare fails on OAuth-login installs (the run reports 'Not logged in'); enable only where auth is an env-var API key that survives it. See reference/observer.md. | +| `observer_idle_seconds` | number | `900` | `CLAUDE_PLUGIN_OPTION_OBSERVER_IDLE_SECONDS` | How long the transcript must stop growing before the observer treats the session as ended. Keep it above the longest expected single turn (large fan-outs, long builds) or a mid-turn pause will be misread as end and fire analysis on a partial transcript. | +| `observer_poll_seconds` | number | `5` | `CLAUDE_PLUGIN_OPTION_OBSERVER_POLL_SECONDS` | How often the observer re-reads the transcript to distill new observations and re-check the mtime-idle threshold. Lower costs more wakeups for a faster end-detection; raise it on a busy machine. The idle threshold, not this, decides when the session is over. Bounded below at 1: 0 spins the detached observer continuously, and a negative value raises at its sleep and kills it silently. | +| `observer_max_seconds` | number | `86400` | `CLAUDE_PLUGIN_OPTION_OBSERVER_MAX_SECONDS` | Absolute cap on observer lifetime. Reaching it exits WITHOUT running analysis (it is a safety valve, not an end signal); mtime-idle is the intended terminator. Default 24h. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure session-flow`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install session-flow@melodic-software --config observer_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "session-flow@melodic-software": { + "options": { + "observer_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/skill-quality/README.md b/plugins/skill-quality/README.md index 714a35e3c..98e66b6a7 100644 --- a/plugins/skill-quality/README.md +++ b/plugins/skill-quality/README.md @@ -104,3 +104,59 @@ stands alone. exits 2. - `npx` (Node) is optional; without it the markdownlint check downgrades to a warning and the other twenty-one still gate. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `skills_root` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_SKILLS_ROOT` | Directory holding your skills (each a subdirectory with a SKILL.md). When unset, resolves to .claude/skills under the project root. Set this only when your skills live elsewhere. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure skill-quality`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install skill-quality@melodic-software --config skills_root= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "skill-quality@melodic-software": { + "options": { + "skills_root": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/source-control/README.md b/plugins/source-control/README.md index 901f4563b..402158367 100644 --- a/plugins/source-control/README.md +++ b/plugins/source-control/README.md @@ -269,3 +269,92 @@ The plugin-scope finding-classification gate accepts extra posting identities vi merge authority. - Bundled scripts are read-only against the GitHub API except where the skill body documents a write. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `lane_instance` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_LANE_INSTANCE` | Writer identity for this machine's loop-lane telemetry, per the loop-lane convention's lane-instance identity rule. It becomes the suffix of the babysit-loop telemetry sentinel marker (`source-control:babysit-loop@`), so each concurrently running lane instance owns its own comment and none can overwrite another's durable state. Must match ^\[a-z0-9\]\[a-z0-9-\]{0,31}$, be stable across restarts, and be distinct across concurrent instances; two lanes on one machine each need an explicit value. Absent: the sanitized lowercased hostname. The value appears verbatim in tracker comments — set an opaque id if a machine name should not be published in a public tracker. | +| `pr_body_linkage_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_PR_BODY_LINKAGE_GATE_ENABLED` | Block a `gh pr create`/`gh pr edit` whose statically-readable PR body would fail the repository's required pr-issue-linkage check (missing a closing keyword, or a missing/empty `## Related` section). Enforced only in a repository that carries .github/workflows/pr-issue-linkage.yml; a body the hook cannot read statically always passes. | +| `pr_linkage_mcp_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_PR_LINKAGE_MCP_GATE_ENABLED` | Block a GitHub MCP create_pull_request/update_pull_request whose PR body would fail the repository's required pr-issue-linkage check — the MCP-surface sibling of pr-body-linkage-gate, covering cloud/remote sessions that open PRs without the gh CLI. Same policy scope: enforced only in a repository that carries .github/workflows/pr-issue-linkage.yml, and only for the repository the origin remote names. | +| `worktree_create_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED` | Redirect a WorktreeCreate away from Claude Code's default location, which may be inside the repository, to the configured worktree_root. The hook already reads this option and names it in its own skip message, but the option was never declared here — so Claude Code never exported CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED, the hook's `:-true` fallback always won, and the gate could not be turned off. | +| `babysit_watched_owners` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_WATCHED_OWNERS` | GitHub owners (users/orgs) babysit-prs may act under. Absent: the current repo's owner is inferred per run. | +| `babysit_self_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_SELF_LOGINS` | Extra GitHub posting identities (e.g. a project bot account) added to your `gh api user` login — the self set babysit-prs treats as its own: self-comment suppression, same-login classification, readiness-gate classification rows, the merge-gate self-exemption, and the resolve-thread bot-only test (a self-authored reply to a bot thread no longer counts as a disqualifying human participant). Not a discovery filter — which authors' PRs the queue discovers is `--author`'s job, independent of this set. Absent: your gh login alone. | +| `babysit_intended_write_identity` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_INTENDED_WRITE_IDENTITY` | The single GitHub login babysit-prs's own writes are intended to land under — typically the bot posting identity. When a write the orchestrator recorded performing lands under a different `babysit_self_logins` identity (e.g. a bot-token mint failed and the write silently fell back to your personal login), the cycle status surfaces an attribution-drift material finding instead of proceeding silently. Set it to one of your self logins; a value that is not actually a posting identity would flag every write. Absent: the check is dormant. | +| `babysit_default_tier` | string | `"safe"` | `CLAUDE_PLUGIN_OPTION_BABYSIT_DEFAULT_TIER` | Tier an explicit bare /source-control:babysit-prs invocation runs: safe, worker, or autopilot. Never applies to auto-routed invocations. | +| `babysit_merge_method` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_MERGE_METHOD` | Merge method for gate-proven merges: merge, squash, or rebase. Absent: repo convention, then squash. | +| `babysit_autopilot_merge_tier` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_BABYSIT_AUTOPILOT_MERGE_TIER` | Enable the #476 autopilot merge tier: a distinct bot account submits a genuine approving review, then the gate merges only when every criterion holds (issue-linked, lane-authored, no do-not-merge label, distinct-bot approval on the live head, no human blocking comment). Ships DISABLED; a deliberate operator opt-in. Requires babysit_lane_logins, babysit_approver_bot_logins, and babysit_merge_block_labels to be set. Absent/false: the tier does not exist and PRs go to the human merge-ready list. | +| `babysit_lane_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_LANE_LOGINS` | Author logins recognized as pipeline lanes for the autopilot merge tier's lane-authored criterion. Absent: the tier (when enabled) refuses fail-closed. | +| `babysit_approver_bot_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_APPROVER_BOT_LOGINS` | Bot logins whose approving review satisfies the autopilot merge tier's author != approver criterion. Absent: the tier (when enabled) refuses fail-closed. | +| `babysit_merge_block_labels` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_MERGE_BLOCK_LABELS` | Labels that veto an autopilot-merge-tier merge, e.g. do-not-merge. Absent: the tier (when enabled) refuses fail-closed. | +| `babysit_review_trigger_phrase` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_TRIGGER_PHRASE` | Comment phrase that requests an AI re-review (posted and recognized). Absent: the review-trigger module stays dormant. | +| `babysit_review_bot_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_BOT_LOGINS` | Logins of the AI review bots the trigger phrase addresses, and whose review of the live head the merge gate waits for. Absent: the review-trigger module stays dormant and the merge gate's review-settle hold stays dormant. | +| `babysit_review_settle_minutes` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_SETTLE_MINUTES` | How long after a head appears a review bot's re-review may still be in flight. The merge gate holds a head that bot has not reviewed yet until the window elapses, then stops waiting. Requires babysit_review_bot_logins; absent, the hold stays dormant. Set it above the reviewer's observed latency. | +| `babysit_review_gate_context` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_GATE_CONTEXT` | Check/status context name of the AI-review gate. Absent: gate treated as absent (degrade). | +| `babysit_ci_gateway_context` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_CI_GATEWAY_CONTEXT` | Check/status context name of a CI gateway check. Absent: gateway classification unused. | +| `babysit_extra_bot_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_EXTRA_BOT_LOGINS` | Additional logins to treat as bots when structural detection cannot identify them. Absent: structural detection only. | +| `babysit_extra_dependency_manager_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_EXTRA_DEPENDENCY_MANAGER_LOGINS` | Additional dependency-manager bot logins beyond the built-in dependabot/renovate set whose PRs the merge gate holds absent --allow-dependency, the same as the built-ins. Absent: built-in dependency-manager set only. | +| `babysit_approval_downgrade_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_APPROVAL_DOWNGRADE_LOGINS` | AI reviewer logins whose approval is surfaced as a `material` finding instead of `ignored` in the one case the structural approval-downgrade reaches: a review body carrying blocking-looking prose that still parses as an approval verdict (no CRITICAL/IMPORTANT or required-fix marker). Every bot's such approval is downgraded to non-blocking regardless; naming a login opts its own into the more-conservative `material` bucket rather than being ignored. Does not affect a review already in the APPROVED state or a plain clean approval with no blocking-looking prose — both are ignored regardless. Absent: such approvals are ignored for every bot. | +| `babysit_skip_downgrade_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_SKIP_DOWNGRADE_LOGINS` | AI reviewer logins whose skip/no-op review is not treated as an approval. Absent: the downgrade heuristic stays dormant. | +| `babysit_max_quiet_recheck_seconds` | number | `14400` | `CLAUDE_PLUGIN_OPTION_BABYSIT_MAX_QUIET_RECHECK_SECONDS` | Longest a quiet PR may go without a worker recheck. | +| `babysit_stuck_check_age_seconds` | number | `1800` | `CLAUDE_PLUGIN_OPTION_BABYSIT_STUCK_CHECK_AGE_SECONDS` | Minimum age before a pending non-required check under UNSTABLE is reported stuck (stuck_queued / never_settling material finding). Orphaned status contexts with no backing run are detected structurally and ignore this threshold. | +| `babysit_advisory_fix_round_cap` | number | `100` | `CLAUDE_PLUGIN_OPTION_BABYSIT_ADVISORY_FIX_ROUND_CAP` | Per-PR cap on advisory-only fix rounds (never caps blocking defects). | +| `babysit_worker_concurrency_cap` | number | `10` | `CLAUDE_PLUGIN_OPTION_BABYSIT_WORKER_CONCURRENCY_CAP` | Maximum per-PR workers dispatched concurrently in one cycle. | +| `babysit_worktree_root` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_WORKTREE_ROOT` | Root directory for babysit-managed ephemeral worktrees. Absent: the worktrees/ subdirectory of the plugin data dir. | +| `worktree_root` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_WORKTREE_ROOT` | External root under which /worktree create places worktrees, as /-- — a path OUTSIDE every repository (on Windows, the same drive as the repo). Absent: the worktrees/ subdirectory of the plugin data dir, which the skill supplies explicitly rather than reading from the environment (not per-plugin in a Bash-tool subprocess). Deliberately outside the repository tree AND outside repository-discovery roots such as a ghq root, which a checkout-relative default would land inside. Never the in-repo .claude/worktrees/ default: from a worktree nested inside a checkout, a read matching a path-scoped rule's glob also loads the parent checkout's copy of that rule. | +| `worktree_stale_days` | number | `14` | `CLAUDE_PLUGIN_OPTION_WORKTREE_STALE_DAYS` | Days since last commit before /worktree status classifies a worktree as stale | +| `fetch_logs_max_bytes` | number | `52428800` | `CLAUDE_PLUGIN_OPTION_FETCH_LOGS_MAX_BYTES` | Abort a CI-log ZIP fetch larger than this | +| `branch_issue_pattern` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BRANCH_ISSUE_PATTERN` | POSIX ERE for extracting the numeric GitHub issue number from the current branch name; the LAST capture group holds it and must resolve to digits (Closes #N honors only a numeric issue). Set this for a non-default branch scheme that places the number differently, e.g. '^\[^/\]+/(\[0-9\]+)-' for 'alice/1234-slug' or '-(\[0-9\]+)$' for 'feat/add-widget-1234'. Absent: the built-in '/-' (and routine-issue-) convention. | +| `setup_inference_window` | string | `"1 year"` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_WINDOW` | git log --since window /source-control:setup samples for commit-subject convention inference (any git-approxidate, e.g. '1 year', '6 months'). Absent: 1 year. | +| `setup_inference_recency_days` | number | `90` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_RECENCY_DAYS` | Boundary for the recency split in /source-control:setup's convention-inference report — subjects newer than this many days are the 'recent' bucket, weighted as the live convention when its share diverges from the older bucket. Absent: 90. | +| `setup_inference_min_commits` | number | `50` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_MIN_COMMITS` | Below this many classifiable subjects in the window, /source-control:setup widens inference to full history; still below it, the inference is reported low-confidence rather than authoritative. Absent: 50. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure source-control`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install source-control@melodic-software --config lane_instance= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "source-control@melodic-software": { + "options": { + "lane_instance": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/typos-format/README.md b/plugins/typos-format/README.md index b6a6e66f1..8e2cd1f3f 100644 --- a/plugins/typos-format/README.md +++ b/plugins/typos-format/README.md @@ -115,6 +115,63 @@ These options are user-scoped (stored in your user settings, not the project's). To turn the plugin off for a single repository, disable it in that project's `enabledPlugins` instead. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `typos_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_TYPOS_FORMAT_ENABLED` | Spell-check on edit of any file, unconditionally (report-only unless typos_format_write_changes is on) | +| `typos_format_write_changes` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_TYPOS_FORMAT_WRITE_CHANGES` | Rewrite the file in place. Off by default: findings are reported without modifying the file. Turning this on accepts last-writer-wins ordering with any sibling formatter hook that rewrites the same file. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure typos-format`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install typos-format@melodic-software --config typos_format_enabled= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "typos-format@melodic-software": { + "options": { + "typos_format_enabled": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/visualization/README.md b/plugins/visualization/README.md index 676f8438d..a727ca158 100644 --- a/plugins/visualization/README.md +++ b/plugins/visualization/README.md @@ -85,3 +85,59 @@ its own. maintained security posture (a self-hosted AntV deployment is the current candidate) — until then the skill relies only on native rendering surfaces and the presence-gated craft capabilities. + + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `medium` | string | `"auto"` | `CLAUDE_PLUGIN_OPTION_MEDIUM` | Preferred delivery medium when the skill auto-selects. One of: 'auto' (decide by content and available surfaces), 'terminal' (always render inline, degrading richer forms to their best terminal approximation), 'file' (render richer forms as a self-contained local HTML file, never published off the machine), 'artifact' (prefer a published Artifact when that surface is available, else fall back to a local HTML file, else terminal). An unrecognized value is reported and treated as 'auto'. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure visualization`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install visualization@melodic-software --config medium= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "visualization@melodic-software": { + "options": { + "medium": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + diff --git a/plugins/work-items/README.md b/plugins/work-items/README.md index f4c21274f..1c3ddb466 100644 --- a/plugins/work-items/README.md +++ b/plugins/work-items/README.md @@ -142,6 +142,68 @@ and its own `CLAUDE.md` / rules for write-identity policy (e.g. routing tracker writes through a bot wrapper) and development workflow. The skills degrade gracefully when any of these are absent. + + +### Options reference + +Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code +will prompt for when the plugin is enabled, with the environment variable each hook +reads it from. + +| Option | Type | Default | Environment variable | Description | +| --- | --- | --- | --- | --- | +| `lane_instance` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_LANE_INSTANCE` | Writer identity for this machine's loop-lane telemetry, per the loop-lane convention's lane-instance identity rule. It becomes the suffix of the lane's telemetry sentinel marker (`work-items:work-loop@`), so each concurrently running lane instance owns its own comment and none can overwrite another's durable state — including first_drain_complete, whose loss would end one machine's earn-trust ratification gate because a different machine finished a drain. Must match ^\[a-z0-9\]\[a-z0-9-\]{0,31}$, be stable across restarts, and be distinct across concurrent instances; two lanes on one machine each need an explicit value. Absent: the sanitized lowercased hostname. The value appears verbatim in tracker comments — set an opaque id if a machine name should not be published in a public tracker. | +| `work_dispatch_concurrency_cap` | number | *(none)* | `CLAUDE_PLUGIN_OPTION_WORK_DISPATCH_CONCURRENCY_CAP` | Maximum concurrent dispatch waves /work-items:work's autonomous execute step allows per invocation (it runs exactly one item per invocation). Give a whole number of waves; a fractional value is floored to whole waves since a wave is discrete. When set, /work-items:work threads it into /implementation:implement-dispatch as that skill's --wave-cap ceiling. Leave unset to let implement-dispatch apply its own internal 3-5 wave default — this key declares no default, so an unset value stays distinguishable from a configured one (which a declared default would collapse into a hard cap). | +| `work_loop_item_cap_start` | number | `2` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_START` | Where the work-loop lane's adaptive per-cycle item cap starts. The cap ramps up by one after three consecutive clean items (never while a rate-limit warning is latched) and drops by one on any dirty item; enforcement is the loop body's own arithmetic. | +| `work_loop_item_cap_ceiling` | number | `3` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_CEILING` | Upper bound the work-loop lane's adaptive item cap can ramp to for non-frontier-tier items. Frontier-tier items are bounded separately by work_loop_frontier_item_cap_ceiling. | +| `work_loop_item_cap_floor` | number | `1` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_FLOOR` | Lower bound the work-loop lane's adaptive item cap can drop to on dirty items. | +| `work_loop_frontier_item_cap_ceiling` | number | `2` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_FRONTIER_ITEM_CAP_CEILING` | Quota guard for frontier-capability-tier items in the work-loop lane: such items run at concurrency 1 and their adaptive cap is bounded by this ceiling instead of the general one. Keep it at or below work_loop_item_cap_ceiling. The frontier tier is read from the item body, which any item author can write, so a frontier ceiling above the general one would let a body claim buy higher throughput; the lane detects that inversion and ignores this ceiling, bounding the item by the general one instead. The manifest cannot enforce the ordering — userConfig min/max are static bounds with no cross-key validation. | +| `work_loop_no_progress_threshold` | number | `3` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_NO_PROGRESS_THRESHOLD` | Consecutive no-progress cycles (actionable work in view, no item advanced and no PR opened) before the work-loop lane raises its stall escalation. The lane escalates and keeps looping; it never stops on a stall. Idle cycles with nothing actionable neither count nor reset. | + +### How to set these + +Three supported routes, in the order most people want them: + +1. **Interactively** — Claude Code prompts for declared options when you enable the + plugin. To change them later: `/plugin configure work-items`. +2. **Headless, at install time** — repeat `--config` for each option: + + ```shell + claude plugin install work-items@melodic-software --config lane_instance= + ``` + +3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** + settings (`~/.claude/settings.json`): + + ```json + { + "pluginConfigs": { + "work-items@melodic-software": { + "options": { + "lane_instance": + } + } + } + } + ``` + + Plugin option values are read from **user**, `--settings`, and managed settings + only — **not** from a project's `.claude/settings.json`. To vary behavior per + repository, enable or disable the plugin in that project's `enabledPlugins` + instead of setting an option there. + +Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code +hands a configured value to a hook process; the value comes from the routes above. + +### Upstream documentation + +- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export +- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs` +- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence +- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list` + + + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/scripts/sync-plugin-options-docs.py b/scripts/sync-plugin-options-docs.py new file mode 100644 index 000000000..672aa615a --- /dev/null +++ b/scripts/sync-plugin-options-docs.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Generate the options reference block in each plugin README from its manifest. + + scripts/sync-plugin-options-docs.py rewrite every plugin README + scripts/sync-plugin-options-docs.py --check fail if any README is stale + +SINGLE SOURCE OF TRUTH: `plugins//.claude-plugin/plugin.json` -> `userConfig`. +The block between the markers below is GENERATED. Never hand-edit it: add or change +the option in the manifest and re-run this script. CI runs `--check` and rejects drift, +the same contract `scripts/sync-hook-utils.sh` uses for the shared hook library. + +Why a generated block rather than prose alone: a hand-written Configuration section +carries nuance a generator cannot (see plugins/actionlint/README.md, which explains the +stdin timeout's slicing behavior). That prose is preserved untouched. What it cannot +guarantee is COMPLETENESS and FRESHNESS -- an option added to the manifest six months +from now is silently undocumented. The generated block guarantees both; the prose keeps +the nuance. They sit next to each other in the plugin's own folder so they change together. +""" + +from __future__ import annotations + +import json +import pathlib +import sys + +BEGIN = "" +END = "" + +REPO = pathlib.Path(__file__).resolve().parent.parent +PLUGINS = REPO / "plugins" + + +def env_var(key: str) -> str: + """Claude Code exports each declared option as CLAUDE_PLUGIN_OPTION_, uppercased.""" + return f"CLAUDE_PLUGIN_OPTION_{key.upper()}" + + +def render(plugin: str, marketplace: str, options: dict) -> str: + lines = [ + BEGIN, + "", + "### Options reference", + "", + "Generated from this plugin's `.claude-plugin/plugin.json`. Every option Claude Code", + "will prompt for when the plugin is enabled, with the environment variable each hook", + "reads it from.", + "", + "| Option | Type | Default | Environment variable | Description |", + "| --- | --- | --- | --- | --- |", + ] + for key, spec in options.items(): + typ = spec.get("type", "string") + default = spec.get("default", "") + # MD049: this repo's markdownlint requires asterisk emphasis, not underscore. + default = "*(none)*" if default == "" else f"`{json.dumps(default)}`" + desc = " ".join(str(spec.get("description", spec.get("title", ""))).split()) + # A description is arbitrary prose from the manifest and lands inside a table + # cell. Escape the characters that would otherwise be read as markdown: a pipe + # ends the cell, and a bracketed run such as `[a-z0-9-]` in a regex reads as an + # undefined reference link (MD052). + desc = desc.replace("|", "\\|").replace("[", "\\[").replace("]", "\\]") + if spec.get("sensitive"): + desc = "**Sensitive** — stored in the OS keychain or protected credentials file. " + desc + lines.append(f"| `{key}` | {typ} | {default} | `{env_var(key)}` | {desc} |") + + first = next(iter(options)) + lines += [ + "", + "### How to set these", + "", + "Three supported routes, in the order most people want them:", + "", + "1. **Interactively** — Claude Code prompts for declared options when you enable the", + f" plugin. To change them later: `/plugin configure {plugin}`.", + "2. **Headless, at install time** — repeat `--config` for each option:", + "", + " ```shell", + f" claude plugin install {plugin}@{marketplace} --config {first}=", + " ```", + "", + "3. **By hand, in settings** — add the value under `pluginConfigs` in your **user**", + " settings (`~/.claude/settings.json`):", + "", + " ```json", + " {", + ' "pluginConfigs": {', + f' "{plugin}@{marketplace}": {{', + ' "options": {', + f' "{first}": ', + " }", + " }", + " }", + " }", + " ```", + "", + " Plugin option values are read from **user**, `--settings`, and managed settings", + " only — **not** from a project's `.claude/settings.json`. To vary behavior per", + " repository, enable or disable the plugin in that project's `enabledPlugins`", + " instead of setting an option there.", + "", + "Do not set the `CLAUDE_PLUGIN_OPTION_*` variables yourself. They are how Claude Code", + "hands a configured value to a hook process; the value comes from the routes above.", + "", + "### Upstream documentation", + "", + "- [User configuration](https://code.claude.com/docs/en/plugins-reference#user-configuration) — the `userConfig` schema and the `CLAUDE_PLUGIN_OPTION_` export", + "- [Plugin settings](https://code.claude.com/docs/en/settings#plugin-settings) — `enabledPlugins`, `extraKnownMarketplaces`, `pluginConfigs`", + "- [Configuration scopes](https://code.claude.com/docs/en/settings#configuration-scopes) — user vs project vs local precedence", + "- [Manage installed plugins](https://code.claude.com/docs/en/discover-plugins#manage-installed-plugins) — enabling, disabling, `/plugin list`", + "", + END, + ] + return "\n".join(lines) + + +def splice(readme: str, block: str) -> str: + if BEGIN in readme and END in readme: + head = readme[: readme.index(BEGIN)] + tail = readme[readme.index(END) + len(END) :] + return head + block + tail + # First insertion: before the License heading when there is one, else at the end. + marker = "\n## License" + if marker in readme: + i = readme.index(marker) + return readme[:i] + "\n" + block + "\n" + readme[i:] + return readme.rstrip("\n") + "\n\n" + block + "\n" + + +def main() -> int: + check = "--check" in sys.argv + stale, wrote, skipped = [], 0, 0 + for d in sorted(PLUGINS.iterdir()): + manifest = d / ".claude-plugin" / "plugin.json" + readme = d / "README.md" + if not manifest.exists(): + continue + try: + options = json.loads(manifest.read_text(encoding="utf-8")).get("userConfig") or {} + except json.JSONDecodeError as exc: + print(f" MANIFEST UNPARSEABLE: {d.name}: {exc}", file=sys.stderr) + return 2 + if not options: + skipped += 1 + continue + if not readme.exists(): + print(f" MISSING README: {d.name} declares {len(options)} option(s)", file=sys.stderr) + stale.append(d.name) + continue + current = readme.read_text(encoding="utf-8") + updated = splice(current, render(d.name, "melodic-software", options)) + if updated != current: + if check: + stale.append(d.name) + else: + readme.write_text(updated, encoding="utf-8", newline="\n") + wrote += 1 + print(f" synced: plugins/{d.name}/README.md ({len(options)} options)") + + if check: + if stale: + print(f"\nSTALE options docs in {len(stale)} plugin(s): {', '.join(stale)}", file=sys.stderr) + print("Run: python scripts/sync-plugin-options-docs.py", file=sys.stderr) + return 1 + print("plugin options docs: up to date") + return 0 + print(f"\nsynced {wrote} README(s); {skipped} plugin(s) declare no options") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From cb2c1f9705b3dd17e73ca04f75a44cab8eec0adc Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 23:08:40 -0400 Subject: [PATCH 5/9] fix(ci): satisfy the repo's own gates for the options-docs change Four gate failures on the first push, all legitimate repo doctrine: - hook-utils-sync: every plugin carrying a copy of the shared library must bump its version when the library changes, so consumers receive it. Bumps the 14 carrying plugins with a CHANGELOG entry each. - plugin-gate: plugins/github/github.test.sh enforces marketplace agnosticism, and the generated install example hardcoded a marketplace name. The generator now emits a placeholder with an instruction to substitute it, which is also more correct: a plugin can be installed from a fork, a mirror, or a private catalog under a different name. - hygiene/shellcheck: SC2016 on the two `bash -c 'source "$0"'` probes, where non-expansion is the point -- $0 is the child shell's positional, bound to the library path passed after the -c string. Directives added with that rationale. - hygiene/exec-bit: the new generator needed the exec bit its siblings carry. Co-Authored-By: Claude Opus 5 --- lib/hook-utils.test.sh | 4 ++++ plugins/actionlint/.claude-plugin/plugin.json | 2 +- plugins/actionlint/CHANGELOG.md | 10 ++++++++++ plugins/actionlint/README.md | 7 ++++--- plugins/ai-briefing/README.md | 7 ++++--- plugins/autonomy/.claude-plugin/plugin.json | 2 +- plugins/autonomy/CHANGELOG.md | 10 ++++++++++ plugins/autonomy/README.md | 7 ++++--- plugins/bash-format/.claude-plugin/plugin.json | 2 +- plugins/bash-format/CHANGELOG.md | 10 ++++++++++ plugins/bash-format/README.md | 7 ++++--- plugins/biome-format/.claude-plugin/plugin.json | 2 +- plugins/biome-format/CHANGELOG.md | 10 ++++++++++ plugins/biome-format/README.md | 7 ++++--- plugins/bug-report/README.md | 7 ++++--- plugins/claude-ops/.claude-plugin/plugin.json | 2 +- plugins/claude-ops/CHANGELOG.md | 10 ++++++++++ plugins/claude-ops/README.md | 7 ++++--- plugins/context-guard/.claude-plugin/plugin.json | 2 +- plugins/context-guard/CHANGELOG.md | 10 ++++++++++ plugins/context-guard/README.md | 7 ++++--- .../desktop-notification/.claude-plugin/plugin.json | 2 +- plugins/desktop-notification/CHANGELOG.md | 10 ++++++++++ plugins/desktop-notification/README.md | 7 ++++--- plugins/discipline/README.md | 7 ++++--- plugins/disk-hygiene/README.md | 7 ++++--- plugins/dometrain/README.md | 7 ++++--- plugins/education/README.md | 7 ++++--- plugins/eol-normalizer/.claude-plugin/plugin.json | 2 +- plugins/eol-normalizer/CHANGELOG.md | 10 ++++++++++ plugins/eol-normalizer/README.md | 7 ++++--- plugins/github/README.md | 7 ++++--- plugins/go-format/.claude-plugin/plugin.json | 2 +- plugins/go-format/CHANGELOG.md | 10 ++++++++++ plugins/go-format/README.md | 7 ++++--- plugins/guardrails/.claude-plugin/plugin.json | 2 +- plugins/guardrails/CHANGELOG.md | 10 ++++++++++ plugins/guardrails/README.md | 7 ++++--- plugins/knowledge/README.md | 7 ++++--- plugins/machine-health/README.md | 7 ++++--- plugins/markdown-format/.claude-plugin/plugin.json | 2 +- plugins/markdown-format/CHANGELOG.md | 10 ++++++++++ plugins/markdown-format/README.md | 7 ++++--- plugins/miro/README.md | 7 ++++--- plugins/planning/README.md | 7 ++++--- plugins/powershell-format/.claude-plugin/plugin.json | 2 +- plugins/powershell-format/CHANGELOG.md | 10 ++++++++++ plugins/powershell-format/README.md | 7 ++++--- plugins/rate-limit-guard/README.md | 7 ++++--- plugins/repo-hygiene/README.md | 7 ++++--- plugins/ruff-format/.claude-plugin/plugin.json | 2 +- plugins/ruff-format/CHANGELOG.md | 10 ++++++++++ plugins/ruff-format/README.md | 7 ++++--- plugins/session-flow/README.md | 7 ++++--- plugins/skill-quality/README.md | 7 ++++--- plugins/source-control/README.md | 7 ++++--- plugins/typos-format/.claude-plugin/plugin.json | 2 +- plugins/typos-format/CHANGELOG.md | 10 ++++++++++ plugins/typos-format/README.md | 7 ++++--- plugins/visualization/README.md | 7 ++++--- plugins/work-items/README.md | 7 ++++--- scripts/sync-plugin-options-docs.py | 11 +++++++++-- 62 files changed, 295 insertions(+), 112 deletions(-) mode change 100644 => 100755 scripts/sync-plugin-options-docs.py diff --git a/lib/hook-utils.test.sh b/lib/hook-utils.test.sh index 2f3524c2c..619ec9e16 100755 --- a/lib/hook-utils.test.sh +++ b/lib/hook-utils.test.sh @@ -2312,6 +2312,8 @@ ce_probe() { shift 2 local got got=$( + # shellcheck disable=SC2016 # $0 must NOT expand here: it is the child bash's + # own positional, bound to the library path passed after the -c string. env -u CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED \ "$@" bash -c 'source "$0"; hook::check_enabled "RATE_LIMIT_GUARD"; printf RUN' \ "$HOOK_DIR/hook-utils.sh" 2>/dev/null @@ -2349,6 +2351,8 @@ ie_probe() { shift 2 local got got=$( + # shellcheck disable=SC2016 # $0 must NOT expand here: it is the child bash's + # own positional, bound to the library path passed after the -c string. env -u CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED \ "$@" bash -c 'source "$0" if hook::is_enabled "RATE_LIMIT_GUARD"; then printf ENABLED; else printf DISABLED; fi diff --git a/plugins/actionlint/.claude-plugin/plugin.json b/plugins/actionlint/.claude-plugin/plugin.json index 6aabb755c..949cfdec2 100644 --- a/plugins/actionlint/.claude-plugin/plugin.json +++ b/plugins/actionlint/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "actionlint", - "version": "0.8.3", + "version": "0.8.4", "description": "Lint GitHub Actions workflow files on edit via actionlint, surfacing findings as advisory context.", "author": { "name": "Melodic Software", diff --git a/plugins/actionlint/CHANGELOG.md b/plugins/actionlint/CHANGELOG.md index eb8196ad4..5da000f02 100644 --- a/plugins/actionlint/CHANGELOG.md +++ b/plugins/actionlint/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `actionlint` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.8.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.8.3] ### Fixed diff --git a/plugins/actionlint/README.md b/plugins/actionlint/README.md index 3d0f0f052..046917e0e 100644 --- a/plugins/actionlint/README.md +++ b/plugins/actionlint/README.md @@ -98,10 +98,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure actionlint`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install actionlint@melodic-software --config actionlint_enabled= + claude plugin install actionlint@ --config actionlint_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -110,7 +111,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "actionlint@melodic-software": { + "actionlint@": { "options": { "actionlint_enabled": } diff --git a/plugins/ai-briefing/README.md b/plugins/ai-briefing/README.md index bf8c9e150..21018241f 100644 --- a/plugins/ai-briefing/README.md +++ b/plugins/ai-briefing/README.md @@ -98,10 +98,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure ai-briefing`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install ai-briefing@melodic-software --config active_profile= + claude plugin install ai-briefing@ --config active_profile= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -110,7 +111,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "ai-briefing@melodic-software": { + "ai-briefing@": { "options": { "active_profile": } diff --git a/plugins/autonomy/.claude-plugin/plugin.json b/plugins/autonomy/.claude-plugin/plugin.json index bce46582e..ffcee745b 100644 --- a/plugins/autonomy/.claude-plugin/plugin.json +++ b/plugins/autonomy/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "autonomy", - "version": "0.14.3", + "version": "0.14.4", "description": "Governed autonomous agent operation: role-topology, binding-seam, wiring-vs-advisor, telemetry, return-accounting, trigger-dispatch, per-work-class guardrail-matrix, standing-routine-catalog, and design-only runner-charter contracts for climbing the AI-adoption ladder, plus a guided-setup skill that discovers an adopting org's state, writes its schema-versioned binding, wires standards-pinned OTLP emission with a zero-cost file-artifact default, wires human-attested return capture at the task boundary, wires signal adapters with one governed dispatch entrypoint, binds the five-class guardrail matrix to an org's isolation substrates with an in-boundary live-validation probe before recording each fail-closed binding, and stands up standing-routine-catalog classes as scheduled temporal signal adapters behind the one governed queue with free scheduling defaults wired as reviewable changes and each routine's work-class mapping homed on the security surface.", "author": { "name": "Melodic Software", diff --git a/plugins/autonomy/CHANGELOG.md b/plugins/autonomy/CHANGELOG.md index 8becb4e81..e300e9574 100644 --- a/plugins/autonomy/CHANGELOG.md +++ b/plugins/autonomy/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to the `autonomy` plugin are documented here. Format follows Versions 0.1.0–0.7.0 predate this file (introduced with 0.7.1); their history lives in the merged work-package PRs (#333, #343, #356, #372, #377, #600, #676). +## [0.14.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.14.3] ### Fixed diff --git a/plugins/autonomy/README.md b/plugins/autonomy/README.md index 3127804b0..b619ab312 100644 --- a/plugins/autonomy/README.md +++ b/plugins/autonomy/README.md @@ -199,10 +199,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure autonomy`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install autonomy@melodic-software --config lane_stop_gate_enabled= + claude plugin install autonomy@ --config lane_stop_gate_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -211,7 +212,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "autonomy@melodic-software": { + "autonomy@": { "options": { "lane_stop_gate_enabled": } diff --git a/plugins/bash-format/.claude-plugin/plugin.json b/plugins/bash-format/.claude-plugin/plugin.json index efc813aa4..56452cd48 100644 --- a/plugins/bash-format/.claude-plugin/plugin.json +++ b/plugins/bash-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "bash-format", - "version": "0.7.3", + "version": "0.7.4", "description": "Auto-format and lint shell scripts on edit via shfmt + ShellCheck, using the consuming repo's own .editorconfig and .shellcheckrc.", "author": { "name": "Melodic Software", diff --git a/plugins/bash-format/CHANGELOG.md b/plugins/bash-format/CHANGELOG.md index f15ed048c..ef40bf15e 100644 --- a/plugins/bash-format/CHANGELOG.md +++ b/plugins/bash-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `bash-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.7.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.7.3] ### Fixed diff --git a/plugins/bash-format/README.md b/plugins/bash-format/README.md index bf08d89b0..14c5d9cc2 100644 --- a/plugins/bash-format/README.md +++ b/plugins/bash-format/README.md @@ -119,10 +119,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure bash-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install bash-format@melodic-software --config bash_format_enabled= + claude plugin install bash-format@ --config bash_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -131,7 +132,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "bash-format@melodic-software": { + "bash-format@": { "options": { "bash_format_enabled": } diff --git a/plugins/biome-format/.claude-plugin/plugin.json b/plugins/biome-format/.claude-plugin/plugin.json index 2afc5fa7b..0e56144f1 100644 --- a/plugins/biome-format/.claude-plugin/plugin.json +++ b/plugins/biome-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "biome-format", - "version": "0.6.3", + "version": "0.6.4", "description": "Auto-format and lint JS/TS/JSX/JSON on edit via Biome, only when a biome.json governs the repo — using the consuming repo's own Biome config.", "author": { "name": "Melodic Software", diff --git a/plugins/biome-format/CHANGELOG.md b/plugins/biome-format/CHANGELOG.md index c166cc4a7..b7525f86d 100644 --- a/plugins/biome-format/CHANGELOG.md +++ b/plugins/biome-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `biome-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.6.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.6.3] ### Fixed diff --git a/plugins/biome-format/README.md b/plugins/biome-format/README.md index 8984de91e..8095931de 100644 --- a/plugins/biome-format/README.md +++ b/plugins/biome-format/README.md @@ -105,10 +105,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure biome-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install biome-format@melodic-software --config biome_format_enabled= + claude plugin install biome-format@ --config biome_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -117,7 +118,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "biome-format@melodic-software": { + "biome-format@": { "options": { "biome_format_enabled": } diff --git a/plugins/bug-report/README.md b/plugins/bug-report/README.md index e4c24029a..481761415 100644 --- a/plugins/bug-report/README.md +++ b/plugins/bug-report/README.md @@ -98,10 +98,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure bug-report`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install bug-report@melodic-software --config output_dir= + claude plugin install bug-report@ --config output_dir= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -110,7 +111,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "bug-report@melodic-software": { + "bug-report@": { "options": { "output_dir": } diff --git a/plugins/claude-ops/.claude-plugin/plugin.json b/plugins/claude-ops/.claude-plugin/plugin.json index 5c9958702..0d5b70319 100644 --- a/plugins/claude-ops/.claude-plugin/plugin.json +++ b/plugins/claude-ops/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-ops", - "version": "0.28.5", + "version": "0.28.6", "description": "Claude Code operations toolkit. Seven skills: observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view — queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action — an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.", "author": { "name": "Melodic Software", diff --git a/plugins/claude-ops/CHANGELOG.md b/plugins/claude-ops/CHANGELOG.md index 5b70d4a95..866f9c535 100644 --- a/plugins/claude-ops/CHANGELOG.md +++ b/plugins/claude-ops/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `claude-ops` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.28.6] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.28.5] ### Changed diff --git a/plugins/claude-ops/README.md b/plugins/claude-ops/README.md index e35c49a1b..7106bc2b0 100644 --- a/plugins/claude-ops/README.md +++ b/plugins/claude-ops/README.md @@ -268,10 +268,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure claude-ops`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install claude-ops@melodic-software --config registry_dir= + claude plugin install claude-ops@ --config registry_dir= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -280,7 +281,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "claude-ops@melodic-software": { + "claude-ops@": { "options": { "registry_dir": } diff --git a/plugins/context-guard/.claude-plugin/plugin.json b/plugins/context-guard/.claude-plugin/plugin.json index 74d43cbdd..ea621b6a7 100644 --- a/plugins/context-guard/.claude-plugin/plugin.json +++ b/plugins/context-guard/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "context-guard", - "version": "0.6.4", + "version": "0.6.5", "description": "Per-session context-window observability plus the first shipped consumer: a statusline wrapper tees each session's context_window fields to a per-session snapshot file, a zone resolver classifies usage into smart/acceptable/dumb bands (percentage bands plus window-class token bands, conservative-min combination, zones.json SSOT with shipped defaults), a reader contract fixes how consuming sessions interpret the snapshots, and zone-crossing hooks report once per transition into a worse zone across two channels — the continuation menu to the operator, who owns that choice, and to the model only the zone determination plus the counter-steer that a zone word is not a decay signal (advisory by default; an optional blocking mode gates new mutating work on a fresh dumb-zone snapshot with handoff-writing exempt), with a PostCompact hook persisting an evidence-degraded marker.", "author": { "name": "Melodic Software", diff --git a/plugins/context-guard/CHANGELOG.md b/plugins/context-guard/CHANGELOG.md index 7f8bb7848..11522c20f 100644 --- a/plugins/context-guard/CHANGELOG.md +++ b/plugins/context-guard/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to the `context-guard` plugin. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.5] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.6.4] ### Fixed diff --git a/plugins/context-guard/README.md b/plugins/context-guard/README.md index f19921e82..a091c0cd0 100644 --- a/plugins/context-guard/README.md +++ b/plugins/context-guard/README.md @@ -142,10 +142,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure context-guard`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install context-guard@melodic-software --config context_guard_hooks_enabled= + claude plugin install context-guard@ --config context_guard_hooks_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -154,7 +155,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "context-guard@melodic-software": { + "context-guard@": { "options": { "context_guard_hooks_enabled": } diff --git a/plugins/desktop-notification/.claude-plugin/plugin.json b/plugins/desktop-notification/.claude-plugin/plugin.json index e1c7ce944..83ba69b5f 100644 --- a/plugins/desktop-notification/.claude-plugin/plugin.json +++ b/plugins/desktop-notification/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "desktop-notification", - "version": "0.6.3", + "version": "0.6.4", "description": "Alert you when Claude Code needs input — an audible terminal bell, an OSC 9 terminal notification, and an OS-native toast (macOS/Linux) on permission and idle prompts.", "author": { "name": "Melodic Software", diff --git a/plugins/desktop-notification/CHANGELOG.md b/plugins/desktop-notification/CHANGELOG.md index 4fffff23a..f383eaee4 100644 --- a/plugins/desktop-notification/CHANGELOG.md +++ b/plugins/desktop-notification/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `desktop-notification` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.6.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.6.3] ### Fixed diff --git a/plugins/desktop-notification/README.md b/plugins/desktop-notification/README.md index 82105cdb7..e9a02437d 100644 --- a/plugins/desktop-notification/README.md +++ b/plugins/desktop-notification/README.md @@ -111,10 +111,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure desktop-notification`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install desktop-notification@melodic-software --config desktop_notification_enabled= + claude plugin install desktop-notification@ --config desktop_notification_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -123,7 +124,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "desktop-notification@melodic-software": { + "desktop-notification@": { "options": { "desktop_notification_enabled": } diff --git a/plugins/discipline/README.md b/plugins/discipline/README.md index f3bca3e95..0538f9016 100644 --- a/plugins/discipline/README.md +++ b/plugins/discipline/README.md @@ -410,10 +410,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure discipline`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install discipline@melodic-software --config batch_exclude= + claude plugin install discipline@ --config batch_exclude= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -422,7 +423,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "discipline@melodic-software": { + "discipline@": { "options": { "batch_exclude": } diff --git a/plugins/disk-hygiene/README.md b/plugins/disk-hygiene/README.md index c781a179a..6bf829327 100644 --- a/plugins/disk-hygiene/README.md +++ b/plugins/disk-hygiene/README.md @@ -268,10 +268,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure disk-hygiene`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install disk-hygiene@melodic-software --config disk_hygiene_enabled= + claude plugin install disk-hygiene@ --config disk_hygiene_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -280,7 +281,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "disk-hygiene@melodic-software": { + "disk-hygiene@": { "options": { "disk_hygiene_enabled": } diff --git a/plugins/dometrain/README.md b/plugins/dometrain/README.md index 03eee3efd..54ad2513c 100644 --- a/plugins/dometrain/README.md +++ b/plugins/dometrain/README.md @@ -164,10 +164,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure dometrain`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install dometrain@melodic-software --config dometrain_api_key= + claude plugin install dometrain@ --config dometrain_api_key= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -176,7 +177,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "dometrain@melodic-software": { + "dometrain@": { "options": { "dometrain_api_key": } diff --git a/plugins/education/README.md b/plugins/education/README.md index 51e944847..5919d593e 100644 --- a/plugins/education/README.md +++ b/plugins/education/README.md @@ -111,10 +111,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure education`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install education@melodic-software --config quiz_policy= + claude plugin install education@ --config quiz_policy= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -123,7 +124,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "education@melodic-software": { + "education@": { "options": { "quiz_policy": } diff --git a/plugins/eol-normalizer/.claude-plugin/plugin.json b/plugins/eol-normalizer/.claude-plugin/plugin.json index c6d5e890f..a61793460 100644 --- a/plugins/eol-normalizer/.claude-plugin/plugin.json +++ b/plugins/eol-normalizer/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "eol-normalizer", - "version": "0.6.3", + "version": "0.6.4", "description": "Normalize a written file's working-tree line endings to its .gitattributes eol value on edit — symmetric CRLF/LF driven by git check-attr, advisory and never blocking.", "author": { "name": "Melodic Software", diff --git a/plugins/eol-normalizer/CHANGELOG.md b/plugins/eol-normalizer/CHANGELOG.md index 1087db6e3..33eda1a75 100644 --- a/plugins/eol-normalizer/CHANGELOG.md +++ b/plugins/eol-normalizer/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `eol-normalizer` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.6.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.6.3] ### Fixed diff --git a/plugins/eol-normalizer/README.md b/plugins/eol-normalizer/README.md index eadf55894..47511a59d 100644 --- a/plugins/eol-normalizer/README.md +++ b/plugins/eol-normalizer/README.md @@ -96,10 +96,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure eol-normalizer`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install eol-normalizer@melodic-software --config eol_normalizer_enabled= + claude plugin install eol-normalizer@ --config eol_normalizer_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -108,7 +109,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "eol-normalizer@melodic-software": { + "eol-normalizer@": { "options": { "eol_normalizer_enabled": } diff --git a/plugins/github/README.md b/plugins/github/README.md index 66a83d1a7..0d8032c71 100644 --- a/plugins/github/README.md +++ b/plugins/github/README.md @@ -95,10 +95,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure github`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install github@melodic-software --config offer_browser_automation= + claude plugin install github@ --config offer_browser_automation= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -107,7 +108,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "github@melodic-software": { + "github@": { "options": { "offer_browser_automation": } diff --git a/plugins/go-format/.claude-plugin/plugin.json b/plugins/go-format/.claude-plugin/plugin.json index 0aa7e35bc..3e1e592b6 100644 --- a/plugins/go-format/.claude-plugin/plugin.json +++ b/plugins/go-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "go-format", - "version": "0.3.3", + "version": "0.3.4", "description": "Auto-fix Go formatting and import management on edit via goimports — runs unconditionally (no consumer-config gate), skipping generated files.", "author": { "name": "Melodic Software", diff --git a/plugins/go-format/CHANGELOG.md b/plugins/go-format/CHANGELOG.md index 3fa4a2585..a68d65be8 100644 --- a/plugins/go-format/CHANGELOG.md +++ b/plugins/go-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `go-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.3.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.3.3] ### Fixed diff --git a/plugins/go-format/README.md b/plugins/go-format/README.md index bcf7de560..e5d076154 100644 --- a/plugins/go-format/README.md +++ b/plugins/go-format/README.md @@ -114,10 +114,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure go-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install go-format@melodic-software --config go_format_enabled= + claude plugin install go-format@ --config go_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -126,7 +127,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "go-format@melodic-software": { + "go-format@": { "options": { "go_format_enabled": } diff --git a/plugins/guardrails/.claude-plugin/plugin.json b/plugins/guardrails/.claude-plugin/plugin.json index d0e755b89..400877dc2 100644 --- a/plugins/guardrails/.claude-plugin/plugin.json +++ b/plugins/guardrails/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "guardrails", - "version": "0.24.2", + "version": "0.24.3", "description": "Twelve safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, irreversible git operations (force-push, reset --hard, worktree-wide checkout/restore discards), Bash file-write workarounds that circumvent Write/Edit hooks, multi-line `git commit -m` messages (an actual-newline `-m` mangles across shells; single-line `-m` passes), commit subjects and gh pr create titles that violate the repo's tracked team convention (when one is declared in .claude/source-control.md), (advisory) hallucinated CLI flags, (advisory) /plugin:skill references that do not resolve, (advisory) markdown citing a repo path the repo's own history shows was removed, (advisory, opt-in) un-throttled Workflow fan-out that risks burst 529s, and (advisory, opt-in) direct gh pr create calls bypassing this marketplace's own pull-request skill — each independently toggleable.", "author": { "name": "Melodic Software", diff --git a/plugins/guardrails/CHANGELOG.md b/plugins/guardrails/CHANGELOG.md index 0a55010b6..d3c200eb7 100644 --- a/plugins/guardrails/CHANGELOG.md +++ b/plugins/guardrails/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `guardrails` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.24.3] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.24.2] ### Changed diff --git a/plugins/guardrails/README.md b/plugins/guardrails/README.md index 982959c73..f7f5a7f4b 100644 --- a/plugins/guardrails/README.md +++ b/plugins/guardrails/README.md @@ -287,10 +287,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure guardrails`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install guardrails@melodic-software --config secret_pattern_detection_enabled= + claude plugin install guardrails@ --config secret_pattern_detection_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -299,7 +300,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "guardrails@melodic-software": { + "guardrails@": { "options": { "secret_pattern_detection_enabled": } diff --git a/plugins/knowledge/README.md b/plugins/knowledge/README.md index eb29b3df8..4da5448a7 100644 --- a/plugins/knowledge/README.md +++ b/plugins/knowledge/README.md @@ -132,10 +132,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure knowledge`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install knowledge@melodic-software --config library_dir= + claude plugin install knowledge@ --config library_dir= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -144,7 +145,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "knowledge@melodic-software": { + "knowledge@": { "options": { "library_dir": } diff --git a/plugins/machine-health/README.md b/plugins/machine-health/README.md index 3689eb99d..35666d7a3 100644 --- a/plugins/machine-health/README.md +++ b/plugins/machine-health/README.md @@ -100,10 +100,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure machine-health`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install machine-health@melodic-software --config report_dir= + claude plugin install machine-health@ --config report_dir= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -112,7 +113,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "machine-health@melodic-software": { + "machine-health@": { "options": { "report_dir": } diff --git a/plugins/markdown-format/.claude-plugin/plugin.json b/plugins/markdown-format/.claude-plugin/plugin.json index 4ba070a90..fe360431e 100644 --- a/plugins/markdown-format/.claude-plugin/plugin.json +++ b/plugins/markdown-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "markdown-format", - "version": "0.11.5", + "version": "0.11.6", "description": "Auto-format and lint Markdown on edit via markdownlint-cli2 — only in repos that carry their own markdownlint config.", "author": { "name": "Melodic Software", diff --git a/plugins/markdown-format/CHANGELOG.md b/plugins/markdown-format/CHANGELOG.md index c50a63de8..c5776e9b7 100644 --- a/plugins/markdown-format/CHANGELOG.md +++ b/plugins/markdown-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `markdown-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.11.6] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.11.5] ### Changed diff --git a/plugins/markdown-format/README.md b/plugins/markdown-format/README.md index 6561cc0db..fd71cf61d 100644 --- a/plugins/markdown-format/README.md +++ b/plugins/markdown-format/README.md @@ -190,10 +190,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure markdown-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install markdown-format@melodic-software --config markdown_format_enabled= + claude plugin install markdown-format@ --config markdown_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -202,7 +203,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "markdown-format@melodic-software": { + "markdown-format@": { "options": { "markdown_format_enabled": } diff --git a/plugins/miro/README.md b/plugins/miro/README.md index 2a9648d8a..e0341c214 100644 --- a/plugins/miro/README.md +++ b/plugins/miro/README.md @@ -101,10 +101,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure miro`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install miro@melodic-software --config miro_api_token= + claude plugin install miro@ --config miro_api_token= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -113,7 +114,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "miro@melodic-software": { + "miro@": { "options": { "miro_api_token": } diff --git a/plugins/planning/README.md b/plugins/planning/README.md index 0d320ad1c..2024e765f 100644 --- a/plugins/planning/README.md +++ b/plugins/planning/README.md @@ -84,10 +84,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure planning`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install planning@melodic-software --config use_ask_user_question= + claude plugin install planning@ --config use_ask_user_question= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -96,7 +97,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "planning@melodic-software": { + "planning@": { "options": { "use_ask_user_question": } diff --git a/plugins/powershell-format/.claude-plugin/plugin.json b/plugins/powershell-format/.claude-plugin/plugin.json index 81c687c2d..b1ddc9be7 100644 --- a/plugins/powershell-format/.claude-plugin/plugin.json +++ b/plugins/powershell-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "powershell-format", - "version": "0.7.3", + "version": "0.7.4", "description": "Auto-format and lint PowerShell on edit via PSScriptAnalyzer, only when a PSScriptAnalyzerSettings.psd1 governs the repo — using the consuming repo's own analyzer settings.", "author": { "name": "Melodic Software", diff --git a/plugins/powershell-format/CHANGELOG.md b/plugins/powershell-format/CHANGELOG.md index a1d6455ca..0d507e95e 100644 --- a/plugins/powershell-format/CHANGELOG.md +++ b/plugins/powershell-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `powershell-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.7.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.7.3] ### Fixed diff --git a/plugins/powershell-format/README.md b/plugins/powershell-format/README.md index 0e584be58..a41fb493c 100644 --- a/plugins/powershell-format/README.md +++ b/plugins/powershell-format/README.md @@ -133,10 +133,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure powershell-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install powershell-format@melodic-software --config powershell_format_enabled= + claude plugin install powershell-format@ --config powershell_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -145,7 +146,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "powershell-format@melodic-software": { + "powershell-format@": { "options": { "powershell_format_enabled": } diff --git a/plugins/rate-limit-guard/README.md b/plugins/rate-limit-guard/README.md index 5e67adc7f..26ba6b8aa 100644 --- a/plugins/rate-limit-guard/README.md +++ b/plugins/rate-limit-guard/README.md @@ -123,10 +123,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure rate-limit-guard`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install rate-limit-guard@melodic-software --config rate_limit_guard_enabled= + claude plugin install rate-limit-guard@ --config rate_limit_guard_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -135,7 +136,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "rate-limit-guard@melodic-software": { + "rate-limit-guard@": { "options": { "rate_limit_guard_enabled": } diff --git a/plugins/repo-hygiene/README.md b/plugins/repo-hygiene/README.md index 252a6be2c..0fb77fc39 100644 --- a/plugins/repo-hygiene/README.md +++ b/plugins/repo-hygiene/README.md @@ -108,10 +108,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure repo-hygiene`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install repo-hygiene@melodic-software --config clean_destructive_guard_enabled= + claude plugin install repo-hygiene@ --config clean_destructive_guard_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -120,7 +121,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "repo-hygiene@melodic-software": { + "repo-hygiene@": { "options": { "clean_destructive_guard_enabled": } diff --git a/plugins/ruff-format/.claude-plugin/plugin.json b/plugins/ruff-format/.claude-plugin/plugin.json index 3fbfca7bc..bbde563f9 100644 --- a/plugins/ruff-format/.claude-plugin/plugin.json +++ b/plugins/ruff-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "ruff-format", - "version": "0.6.3", + "version": "0.6.4", "description": "Auto-format and lint Python on edit via Ruff, only when a Ruff config governs the repo — using the consuming repo's own Ruff config.", "author": { "name": "Melodic Software", diff --git a/plugins/ruff-format/CHANGELOG.md b/plugins/ruff-format/CHANGELOG.md index 65458e427..483785009 100644 --- a/plugins/ruff-format/CHANGELOG.md +++ b/plugins/ruff-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `ruff-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.6.4] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.6.3] ### Fixed diff --git a/plugins/ruff-format/README.md b/plugins/ruff-format/README.md index 34c3f565e..6414de740 100644 --- a/plugins/ruff-format/README.md +++ b/plugins/ruff-format/README.md @@ -114,10 +114,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure ruff-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install ruff-format@melodic-software --config ruff_format_enabled= + claude plugin install ruff-format@ --config ruff_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -126,7 +127,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "ruff-format@melodic-software": { + "ruff-format@": { "options": { "ruff_format_enabled": } diff --git a/plugins/session-flow/README.md b/plugins/session-flow/README.md index 56dff1fde..ac9d39e7a 100644 --- a/plugins/session-flow/README.md +++ b/plugins/session-flow/README.md @@ -341,10 +341,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure session-flow`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install session-flow@melodic-software --config observer_enabled= + claude plugin install session-flow@ --config observer_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -353,7 +354,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "session-flow@melodic-software": { + "session-flow@": { "options": { "observer_enabled": } diff --git a/plugins/skill-quality/README.md b/plugins/skill-quality/README.md index 98e66b6a7..8e16d4f4a 100644 --- a/plugins/skill-quality/README.md +++ b/plugins/skill-quality/README.md @@ -123,10 +123,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure skill-quality`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install skill-quality@melodic-software --config skills_root= + claude plugin install skill-quality@ --config skills_root= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -135,7 +136,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "skill-quality@melodic-software": { + "skill-quality@": { "options": { "skills_root": } diff --git a/plugins/source-control/README.md b/plugins/source-control/README.md index 402158367..c54d206e5 100644 --- a/plugins/source-control/README.md +++ b/plugins/source-control/README.md @@ -321,10 +321,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure source-control`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install source-control@melodic-software --config lane_instance= + claude plugin install source-control@ --config lane_instance= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -333,7 +334,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "source-control@melodic-software": { + "source-control@": { "options": { "lane_instance": } diff --git a/plugins/typos-format/.claude-plugin/plugin.json b/plugins/typos-format/.claude-plugin/plugin.json index 03ced2e7d..a839f3066 100644 --- a/plugins/typos-format/.claude-plugin/plugin.json +++ b/plugins/typos-format/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "typos-format", - "version": "0.6.4", + "version": "0.6.5", "description": "Spell-check on edit via typos-cli, unconditionally — report-only by default, honoring the consuming repo's own typos configuration when one is present.", "author": { "name": "Melodic Software", diff --git a/plugins/typos-format/CHANGELOG.md b/plugins/typos-format/CHANGELOG.md index ab537667d..c82ad34ff 100644 --- a/plugins/typos-format/CHANGELOG.md +++ b/plugins/typos-format/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `typos-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.6.5] + +### Changed + +- **Carries the shared hook library's new `hook::is_enabled` predicate.** `hook::check_enabled` + exits the process when a plugin is gated off, which is correct for a hook but wrong for a + caller that must keep running afterward. The resolution is now also available as a predicate + that returns instead of exiting. No behaviour of this plugin changes; the version moves so + consumers receive the updated library. + ## [0.6.4] ### Changed diff --git a/plugins/typos-format/README.md b/plugins/typos-format/README.md index 8e2cd1f3f..b9205c991 100644 --- a/plugins/typos-format/README.md +++ b/plugins/typos-format/README.md @@ -134,10 +134,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure typos-format`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install typos-format@melodic-software --config typos_format_enabled= + claude plugin install typos-format@ --config typos_format_enabled= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -146,7 +147,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "typos-format@melodic-software": { + "typos-format@": { "options": { "typos_format_enabled": } diff --git a/plugins/visualization/README.md b/plugins/visualization/README.md index a727ca158..310c23bfd 100644 --- a/plugins/visualization/README.md +++ b/plugins/visualization/README.md @@ -104,10 +104,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure visualization`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install visualization@melodic-software --config medium= + claude plugin install visualization@ --config medium= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -116,7 +117,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "visualization@melodic-software": { + "visualization@": { "options": { "medium": } diff --git a/plugins/work-items/README.md b/plugins/work-items/README.md index 1c3ddb466..ec73c01dd 100644 --- a/plugins/work-items/README.md +++ b/plugins/work-items/README.md @@ -166,10 +166,11 @@ Three supported routes, in the order most people want them: 1. **Interactively** — Claude Code prompts for declared options when you enable the plugin. To change them later: `/plugin configure work-items`. -2. **Headless, at install time** — repeat `--config` for each option: +2. **Headless, at install time** — repeat `--config` for each option. Replace + `` with the marketplace you installed this plugin from: ```shell - claude plugin install work-items@melodic-software --config lane_instance= + claude plugin install work-items@ --config lane_instance= ``` 3. **By hand, in settings** — add the value under `pluginConfigs` in your **user** @@ -178,7 +179,7 @@ Three supported routes, in the order most people want them: ```json { "pluginConfigs": { - "work-items@melodic-software": { + "work-items@": { "options": { "lane_instance": } diff --git a/scripts/sync-plugin-options-docs.py b/scripts/sync-plugin-options-docs.py old mode 100644 new mode 100755 index 672aa615a..b5a51ea19 --- a/scripts/sync-plugin-options-docs.py +++ b/scripts/sync-plugin-options-docs.py @@ -29,6 +29,12 @@ REPO = pathlib.Path(__file__).resolve().parent.parent PLUGINS = REPO / "plugins" +# A placeholder, deliberately, not this repository's marketplace name. Plugin-facing docs are +# marketplace-agnostic: a plugin can be installed from a fork, a mirror, or a private catalog +# under a different name, and `plugins/github/github.test.sh` enforces that its docs never +# hardcode one. The reader substitutes whatever they installed from. +MARKETPLACE = "" + def env_var(key: str) -> str: """Claude Code exports each declared option as CLAUDE_PLUGIN_OPTION_, uppercased.""" @@ -72,7 +78,8 @@ def render(plugin: str, marketplace: str, options: dict) -> str: "", "1. **Interactively** — Claude Code prompts for declared options when you enable the", f" plugin. To change them later: `/plugin configure {plugin}`.", - "2. **Headless, at install time** — repeat `--config` for each option:", + f"2. **Headless, at install time** — repeat `--config` for each option. Replace", + f" `{marketplace}` with the marketplace you installed this plugin from:", "", " ```shell", f" claude plugin install {plugin}@{marketplace} --config {first}=", @@ -147,7 +154,7 @@ def main() -> int: stale.append(d.name) continue current = readme.read_text(encoding="utf-8") - updated = splice(current, render(d.name, "melodic-software", options)) + updated = splice(current, render(d.name, MARKETPLACE, options)) if updated != current: if check: stale.append(d.name) From 1979381aeacf77f3145cc5013ee76bd7c1266e34 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 23:15:27 -0400 Subject: [PATCH 6/9] fix(scripts): spelling in the options-docs generator The repo's typos gate flags UNPARSEABLE; UNPARSABLE is the accepted spelling. Co-Authored-By: Claude Opus 5 --- scripts/sync-plugin-options-docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sync-plugin-options-docs.py b/scripts/sync-plugin-options-docs.py index b5a51ea19..88a98c00d 100755 --- a/scripts/sync-plugin-options-docs.py +++ b/scripts/sync-plugin-options-docs.py @@ -144,7 +144,7 @@ def main() -> int: try: options = json.loads(manifest.read_text(encoding="utf-8")).get("userConfig") or {} except json.JSONDecodeError as exc: - print(f" MANIFEST UNPARSEABLE: {d.name}: {exc}", file=sys.stderr) + print(f" MANIFEST UNPARSABLE: {d.name}: {exc}", file=sys.stderr) return 2 if not options: skipped += 1 From 1a0419f05968ab664fd6017ffabc46b6889669ba Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 10 Aug 2026 23:35:01 -0400 Subject: [PATCH 7/9] fix(rate-limit-guard,scripts): address the review findings Three defects the automated reviewers caught, all real. The tee's kill switch was decorative. CLAUDE_PLUGIN_OPTION_ is exported to hook processes only, and statusline-tee.sh is invoked by absolute path from the user's statusLine setting -- so the variable was always unset, the :-true fallback always won, and the gate never fired. It passed review only because the tests injected the variable by hand. This is the same class of defect this PR fixes in source-control: a gate reading a channel that never delivers. The tee now reads the value from pluginConfigs in user settings, the sanctioned route for a non-hook consumer, matching the marketplace by prefix so forks work, and failing open on every error path. Also NOT `// empty` in the jq: the alternative operator treats false as falsy, which would discard the exact value the gate exists to detect. Caught by test. The generator dropped multiple/required/min/max, so a repeated option rendered as a plain scalar -- visibly contradicting source-control's own hand-written prose in the same file, which documents 10 of them as "string (multiple)". The generator also left a stale block when a plugin removed its last option, and --check reported it as up to date because it returned before reading the README -- the one path where the gate silently failed at its own job. Co-Authored-By: Claude Opus 5 --- plugins/actionlint/README.md | 2 +- plugins/claude-ops/README.md | 2 +- plugins/dometrain/README.md | 2 +- plugins/guardrails/README.md | 2 +- plugins/knowledge/README.md | 2 +- plugins/markdown-format/README.md | 2 +- plugins/miro/README.md | 2 +- .../.claude-plugin/plugin.json | 2 +- plugins/rate-limit-guard/CHANGELOG.md | 15 ++++++++ .../scripts/statusline-tee.sh | 36 +++++++++++++++---- plugins/session-flow/README.md | 2 +- plugins/source-control/README.md | 28 +++++++-------- plugins/work-items/README.md | 12 +++---- scripts/sync-plugin-options-docs.py | 29 +++++++++++++++ 14 files changed, 103 insertions(+), 35 deletions(-) diff --git a/plugins/actionlint/README.md b/plugins/actionlint/README.md index 046917e0e..28a88a7af 100644 --- a/plugins/actionlint/README.md +++ b/plugins/actionlint/README.md @@ -90,7 +90,7 @@ reads it from. | Option | Type | Default | Environment variable | Description | | --- | --- | --- | --- | --- | | `actionlint_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_ACTIONLINT_ENABLED` | Lint GitHub Actions workflow files on edit via actionlint | -| `stdin_read_timeout` | number | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open | +| `stdin_read_timeout` | number
*min 1* | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open | ### How to set these diff --git a/plugins/claude-ops/README.md b/plugins/claude-ops/README.md index 7106bc2b0..e3650a4f7 100644 --- a/plugins/claude-ops/README.md +++ b/plugins/claude-ops/README.md @@ -260,7 +260,7 @@ reads it from. | `skill_usage_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_SKILL_USAGE_AUDIT_ENABLED` | Emit telemetry on skill usage; shared by both skill-usage audit hooks (the Skill-tool and slash-command expansion paths) and also gates the shared skill-usage.jsonl store | | `tool_failure_audit_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_TOOL_FAILURE_AUDIT_ENABLED` | Emit telemetry on Write/Edit/Bash tool failures | | `instructions_loaded_audit_log_session_start` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_INSTRUCTIONS_LOADED_AUDIT_LOG_SESSION_START` | Opt back into logging session_start instruction loads (dropped by default as deterministic and high-volume) | -| `stdin_read_timeout` | number | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open | +| `stdin_read_timeout` | number
*min 1* | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open | ### How to set these diff --git a/plugins/dometrain/README.md b/plugins/dometrain/README.md index 54ad2513c..4a840649e 100644 --- a/plugins/dometrain/README.md +++ b/plugins/dometrain/README.md @@ -156,7 +156,7 @@ reads it from. | Option | Type | Default | Environment variable | Description | | --- | --- | --- | --- | --- | -| `dometrain_api_key` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_DOMETRAIN_API_KEY` | **Sensitive** — stored in the OS keychain or protected credentials file. Dometrain account API key from https://dometrain.com/dashboard/account/ (MCP API keys section). Required — the remote MCP server rejects requests without it. Stored by Claude Code in secure credential storage, never settings.json. | +| `dometrain_api_key` | string
*required* | *(none)* | `CLAUDE_PLUGIN_OPTION_DOMETRAIN_API_KEY` | **Sensitive** — stored in the OS keychain or protected credentials file. Dometrain account API key from https://dometrain.com/dashboard/account/ (MCP API keys section). Required — the remote MCP server rejects requests without it. Stored by Claude Code in secure credential storage, never settings.json. | ### How to set these diff --git a/plugins/guardrails/README.md b/plugins/guardrails/README.md index f7f5a7f4b..9af0a9e7f 100644 --- a/plugins/guardrails/README.md +++ b/plugins/guardrails/README.md @@ -279,7 +279,7 @@ reads it from. | `block_dangerous_git_allow` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BLOCK_DANGEROUS_GIT_ALLOW` | Comma-separated forms block-dangerous-git permits: push-force, push-lease-unsafe, reset-hard, clean-force, checkout-dot, restore-dot, checkout-force; empty blocks all | | `block_noncanonical_commit_allow` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BLOCK_NONCANONICAL_COMMIT_ALLOW` | Comma-separated form tokens to allow (currently: message-flag, which permits `-m` even when the message contains a newline) | | `block_no_verify_hook_manager_prefixes` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_HOOK_MANAGER_PREFIXES` | Comma-separated hook-manager env-var name prefixes block-no-verify treats as a bypass when set to 0/false (e.g. lefthook,husky); empty uses the built-in default set (lefthook, husky, pre_commit, simple_git_hooks) | -| `stdin_read_timeout` | number | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long a silent pipe is tolerated before a blocking guard fails closed | +| `stdin_read_timeout` | number
*min 1* | `2` | `CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT` | Idle bound on reading the hook payload from stdin — how long a silent pipe is tolerated before a blocking guard fails closed | ### How to set these diff --git a/plugins/knowledge/README.md b/plugins/knowledge/README.md index 4da5448a7..7666649aa 100644 --- a/plugins/knowledge/README.md +++ b/plugins/knowledge/README.md @@ -124,7 +124,7 @@ reads it from. | `yt_dlp_js_runtimes` | string | `"node"` | `CLAUDE_PLUGIN_OPTION_YT_DLP_JS_RUNTIMES` | JavaScript runtime yt-dlp uses for YouTube signature deciphering. Default 'node'. Set to 'off' to omit the --js-runtimes flag entirely. | | `yt_dlp_cookies_file` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_YT_DLP_COOKIES_FILE` | Path to a Netscape-format cookies.txt for authenticated YouTube acquisition. Empty by default (unauthenticated, with automatic browser-cookie fallback on a bot check). Never commit cookie files. | | `yt_dlp_cookies_from_browser` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_YT_DLP_COOKIES_FROM_BROWSER` | Browser to pull YouTube cookies from (e.g. chrome, firefox, edge), forcing one instead of the automatic platform-ordered fallback. Empty by default. A cookies file, when set, wins over this. | -| `max_concurrent_acquires` | number | `1` | `CLAUDE_PLUGIN_OPTION_MAX_CONCURRENT_ACQUIRES` | Cap on concurrent yt-dlp acquisition runs during a batch. Default 1; raising it increases HTTP 429 throttling risk. | +| `max_concurrent_acquires` | number
*min 1, max 3* | `1` | `CLAUDE_PLUGIN_OPTION_MAX_CONCURRENT_ACQUIRES` | Cap on concurrent yt-dlp acquisition runs during a batch. Default 1; raising it increases HTTP 429 throttling risk. | ### How to set these diff --git a/plugins/markdown-format/README.md b/plugins/markdown-format/README.md index fd71cf61d..56399aa55 100644 --- a/plugins/markdown-format/README.md +++ b/plugins/markdown-format/README.md @@ -182,7 +182,7 @@ reads it from. | --- | --- | --- | --- | --- | | `markdown_format_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED` | Auto-format and lint Markdown on Write/Edit of .md/.mdc files (runs only when the repo carries a markdownlint config) | | `markdown_format_lint_gitignored` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_LINT_GITIGNORED` | By default the hook leaves gitignored files alone — a scratch tier the repo excludes is neither rewritten nor reported on. Set true to bypass THIS HOOK's git check; markdownlint-cli2's own ignores/gitignore config still applies downstream, so a path your markdownlint config also excludes stays untouched. | -| `markdown_format_max_findings` | number | `20` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_MAX_FINDINGS` | How many individual markdownlint violations are listed per run. The total count and the leading rule codes are always reported regardless. 0 = unlimited. | +| `markdown_format_max_findings` | number
*min 0* | `20` | `CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_MAX_FINDINGS` | How many individual markdownlint violations are listed per run. The total count and the leading rule codes are always reported regardless. 0 = unlimited. | ### How to set these diff --git a/plugins/miro/README.md b/plugins/miro/README.md index e0341c214..2ebbbfd75 100644 --- a/plugins/miro/README.md +++ b/plugins/miro/README.md @@ -93,7 +93,7 @@ reads it from. | Option | Type | Default | Environment variable | Description | | --- | --- | --- | --- | --- | -| `miro_api_token` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_MIRO_API_TOKEN` | **Sensitive** — stored in the OS keychain or protected credentials file. Miro REST API token from https://miro.com/app/settings/user-profile/apps. Required — the bundled MCP server exits at startup without it. Stored by Claude Code in secure credential storage, never settings.json. | +| `miro_api_token` | string
*required* | *(none)* | `CLAUDE_PLUGIN_OPTION_MIRO_API_TOKEN` | **Sensitive** — stored in the OS keychain or protected credentials file. Miro REST API token from https://miro.com/app/settings/user-profile/apps. Required — the bundled MCP server exits at startup without it. Stored by Claude Code in secure credential storage, never settings.json. | ### How to set these diff --git a/plugins/rate-limit-guard/.claude-plugin/plugin.json b/plugins/rate-limit-guard/.claude-plugin/plugin.json index 0170c3310..0fea54256 100644 --- a/plugins/rate-limit-guard/.claude-plugin/plugin.json +++ b/plugins/rate-limit-guard/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "rate-limit-guard", - "version": "0.5.5", + "version": "0.5.6", "description": "Shared rate-limit guard for loop lanes: a statusline wrapper tees the subscription rate-limit windows to a fixed machine-scope file, a StopFailure hook records rate-limit stops reactively, and a reader contract fixes how consuming sessions pause and resume.", "author": { "name": "Melodic Software", diff --git a/plugins/rate-limit-guard/CHANGELOG.md b/plugins/rate-limit-guard/CHANGELOG.md index 87358d1e6..92fbddcba 100644 --- a/plugins/rate-limit-guard/CHANGELOG.md +++ b/plugins/rate-limit-guard/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to the `rate-limit-guard` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.6] + +### Fixed + +- **The statusline tee's kill switch read a channel that never reaches it.** The previous + release gated the tee on `CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED`, but Claude Code + exports `CLAUDE_PLUGIN_OPTION_` to **hook processes** only, and this script is invoked by + absolute path from the user's `statusLine` setting. The variable was therefore always unset, + the `:-true` fallback always won, and the gate was decorative -- it only appeared to work + because the tests injected the variable by hand. The tee now reads + `pluginConfigs.@.options.rate_limit_guard_enabled` from the user's + settings directly, the sanctioned route for a non-hook consumer. The `pluginConfigs` key is + matched by prefix so a fork or private catalog works, and every failure path -- no settings + file, no jq, malformed JSON -- still runs the tee. + ## [0.5.5] ### Fixed diff --git a/plugins/rate-limit-guard/scripts/statusline-tee.sh b/plugins/rate-limit-guard/scripts/statusline-tee.sh index 5190b7fe5..6c5ca062b 100755 --- a/plugins/rate-limit-guard/scripts/statusline-tee.sh +++ b/plugins/rate-limit-guard/scripts/statusline-tee.sh @@ -263,12 +263,36 @@ tee_snapshot() { # The library is the plugin's own synced copy in the sibling hooks/ directory. # If it cannot be read the tee still runs, consistent with this script's rule # that no tee outcome ever alters the wrapped statusline. -_rlg_hook_utils="$(cd "$(dirname "${BASH_SOURCE[0]}")/../hooks" 2>/dev/null && pwd)/hook-utils.sh" -if [[ -r "$_rlg_hook_utils" ]]; then - # shellcheck source=../hooks/hook-utils.sh - source "$_rlg_hook_utils" -fi -if ! declare -F hook::is_enabled >/dev/null 2>&1 || hook::is_enabled "RATE_LIMIT_GUARD"; then +# CHANNEL: this process is NOT a hook process. Claude Code exports +# CLAUDE_PLUGIN_OPTION_ to hook processes only (plugins-reference, "User +# configuration"; and this repo's docs/conventions/hook-config-delivery/README.md scopes +# the env channel the same way). Reading that variable alone would always find it unset, +# always fall back to enabled, and produce a gate that is decorative -- so the value is +# read from the configured source directly, which is the sanctioned route for a non-hook +# consumer. The env var is still honored first in case it is ever delivered here. +# +# Marketplace-agnostic: pluginConfigs is keyed `@`, and this plugin +# may be installed from a fork or private catalog, so the key is matched by prefix. +_rlg_tee_enabled() { + case "${CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED:-}" in + true) return 0 ;; + "") : ;; + *) return 1 ;; + esac + local cfg="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}/settings.json" + [[ -r "$cfg" ]] || return 0 + command -v jq >/dev/null 2>&1 || return 0 + local value + # NOT `// empty`: jq's alternative operator treats `false` as falsy, so the one value + # this gate exists to detect would be discarded. `tostring` yields "false"/"true"/"null". + value="$(jq -r '(.pluginConfigs // {}) | to_entries[] + | select(.key | startswith("rate-limit-guard@")) + | .value.options.rate_limit_guard_enabled | tostring' "$cfg" 2>/dev/null \ + | grep -v '^null$' | head -n 1)" + [[ "$value" == "false" ]] && return 1 + return 0 +} +if _rlg_tee_enabled; then tee_snapshot fi diff --git a/plugins/session-flow/README.md b/plugins/session-flow/README.md index ac9d39e7a..235e73c42 100644 --- a/plugins/session-flow/README.md +++ b/plugins/session-flow/README.md @@ -332,7 +332,7 @@ reads it from. | `observer_analysis_model` | string | `"claude-haiku-4-5"` | `CLAUDE_PLUGIN_OPTION_OBSERVER_ANALYSIS_MODEL` | Model id for the headless post-end analysis run (the dominant cost lever). Defaults to the cheapest active tier; pin a different id to trade cost for depth. | | `observer_analysis_bare` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_OBSERVER_ANALYSIS_BARE` | Drop auto-discovery (a further cost lever) on the analysis run. Off by default because --bare fails on OAuth-login installs (the run reports 'Not logged in'); enable only where auth is an env-var API key that survives it. See reference/observer.md. | | `observer_idle_seconds` | number | `900` | `CLAUDE_PLUGIN_OPTION_OBSERVER_IDLE_SECONDS` | How long the transcript must stop growing before the observer treats the session as ended. Keep it above the longest expected single turn (large fan-outs, long builds) or a mid-turn pause will be misread as end and fire analysis on a partial transcript. | -| `observer_poll_seconds` | number | `5` | `CLAUDE_PLUGIN_OPTION_OBSERVER_POLL_SECONDS` | How often the observer re-reads the transcript to distill new observations and re-check the mtime-idle threshold. Lower costs more wakeups for a faster end-detection; raise it on a busy machine. The idle threshold, not this, decides when the session is over. Bounded below at 1: 0 spins the detached observer continuously, and a negative value raises at its sleep and kills it silently. | +| `observer_poll_seconds` | number
*min 1* | `5` | `CLAUDE_PLUGIN_OPTION_OBSERVER_POLL_SECONDS` | How often the observer re-reads the transcript to distill new observations and re-check the mtime-idle threshold. Lower costs more wakeups for a faster end-detection; raise it on a busy machine. The idle threshold, not this, decides when the session is over. Bounded below at 1: 0 spins the detached observer continuously, and a negative value raises at its sleep and kills it silently. | | `observer_max_seconds` | number | `86400` | `CLAUDE_PLUGIN_OPTION_OBSERVER_MAX_SECONDS` | Absolute cap on observer lifetime. Reaching it exits WITHOUT running analysis (it is a safety valve, not an end signal); mtime-idle is the intended terminator. Default 24h. | ### How to set these diff --git a/plugins/source-control/README.md b/plugins/source-control/README.md index c54d206e5..a15b932cc 100644 --- a/plugins/source-control/README.md +++ b/plugins/source-control/README.md @@ -284,36 +284,36 @@ reads it from. | `pr_body_linkage_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_PR_BODY_LINKAGE_GATE_ENABLED` | Block a `gh pr create`/`gh pr edit` whose statically-readable PR body would fail the repository's required pr-issue-linkage check (missing a closing keyword, or a missing/empty `## Related` section). Enforced only in a repository that carries .github/workflows/pr-issue-linkage.yml; a body the hook cannot read statically always passes. | | `pr_linkage_mcp_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_PR_LINKAGE_MCP_GATE_ENABLED` | Block a GitHub MCP create_pull_request/update_pull_request whose PR body would fail the repository's required pr-issue-linkage check — the MCP-surface sibling of pr-body-linkage-gate, covering cloud/remote sessions that open PRs without the gh CLI. Same policy scope: enforced only in a repository that carries .github/workflows/pr-issue-linkage.yml, and only for the repository the origin remote names. | | `worktree_create_gate_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED` | Redirect a WorktreeCreate away from Claude Code's default location, which may be inside the repository, to the configured worktree_root. The hook already reads this option and names it in its own skip message, but the option was never declared here — so Claude Code never exported CLAUDE_PLUGIN_OPTION_WORKTREE_CREATE_GATE_ENABLED, the hook's `:-true` fallback always won, and the gate could not be turned off. | -| `babysit_watched_owners` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_WATCHED_OWNERS` | GitHub owners (users/orgs) babysit-prs may act under. Absent: the current repo's owner is inferred per run. | -| `babysit_self_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_SELF_LOGINS` | Extra GitHub posting identities (e.g. a project bot account) added to your `gh api user` login — the self set babysit-prs treats as its own: self-comment suppression, same-login classification, readiness-gate classification rows, the merge-gate self-exemption, and the resolve-thread bot-only test (a self-authored reply to a bot thread no longer counts as a disqualifying human participant). Not a discovery filter — which authors' PRs the queue discovers is `--author`'s job, independent of this set. Absent: your gh login alone. | +| `babysit_watched_owners` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_WATCHED_OWNERS` | GitHub owners (users/orgs) babysit-prs may act under. Absent: the current repo's owner is inferred per run. | +| `babysit_self_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_SELF_LOGINS` | Extra GitHub posting identities (e.g. a project bot account) added to your `gh api user` login — the self set babysit-prs treats as its own: self-comment suppression, same-login classification, readiness-gate classification rows, the merge-gate self-exemption, and the resolve-thread bot-only test (a self-authored reply to a bot thread no longer counts as a disqualifying human participant). Not a discovery filter — which authors' PRs the queue discovers is `--author`'s job, independent of this set. Absent: your gh login alone. | | `babysit_intended_write_identity` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_INTENDED_WRITE_IDENTITY` | The single GitHub login babysit-prs's own writes are intended to land under — typically the bot posting identity. When a write the orchestrator recorded performing lands under a different `babysit_self_logins` identity (e.g. a bot-token mint failed and the write silently fell back to your personal login), the cycle status surfaces an attribution-drift material finding instead of proceeding silently. Set it to one of your self logins; a value that is not actually a posting identity would flag every write. Absent: the check is dormant. | | `babysit_default_tier` | string | `"safe"` | `CLAUDE_PLUGIN_OPTION_BABYSIT_DEFAULT_TIER` | Tier an explicit bare /source-control:babysit-prs invocation runs: safe, worker, or autopilot. Never applies to auto-routed invocations. | | `babysit_merge_method` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_MERGE_METHOD` | Merge method for gate-proven merges: merge, squash, or rebase. Absent: repo convention, then squash. | | `babysit_autopilot_merge_tier` | boolean | `false` | `CLAUDE_PLUGIN_OPTION_BABYSIT_AUTOPILOT_MERGE_TIER` | Enable the #476 autopilot merge tier: a distinct bot account submits a genuine approving review, then the gate merges only when every criterion holds (issue-linked, lane-authored, no do-not-merge label, distinct-bot approval on the live head, no human blocking comment). Ships DISABLED; a deliberate operator opt-in. Requires babysit_lane_logins, babysit_approver_bot_logins, and babysit_merge_block_labels to be set. Absent/false: the tier does not exist and PRs go to the human merge-ready list. | -| `babysit_lane_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_LANE_LOGINS` | Author logins recognized as pipeline lanes for the autopilot merge tier's lane-authored criterion. Absent: the tier (when enabled) refuses fail-closed. | -| `babysit_approver_bot_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_APPROVER_BOT_LOGINS` | Bot logins whose approving review satisfies the autopilot merge tier's author != approver criterion. Absent: the tier (when enabled) refuses fail-closed. | -| `babysit_merge_block_labels` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_MERGE_BLOCK_LABELS` | Labels that veto an autopilot-merge-tier merge, e.g. do-not-merge. Absent: the tier (when enabled) refuses fail-closed. | +| `babysit_lane_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_LANE_LOGINS` | Author logins recognized as pipeline lanes for the autopilot merge tier's lane-authored criterion. Absent: the tier (when enabled) refuses fail-closed. | +| `babysit_approver_bot_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_APPROVER_BOT_LOGINS` | Bot logins whose approving review satisfies the autopilot merge tier's author != approver criterion. Absent: the tier (when enabled) refuses fail-closed. | +| `babysit_merge_block_labels` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_MERGE_BLOCK_LABELS` | Labels that veto an autopilot-merge-tier merge, e.g. do-not-merge. Absent: the tier (when enabled) refuses fail-closed. | | `babysit_review_trigger_phrase` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_TRIGGER_PHRASE` | Comment phrase that requests an AI re-review (posted and recognized). Absent: the review-trigger module stays dormant. | -| `babysit_review_bot_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_BOT_LOGINS` | Logins of the AI review bots the trigger phrase addresses, and whose review of the live head the merge gate waits for. Absent: the review-trigger module stays dormant and the merge gate's review-settle hold stays dormant. | +| `babysit_review_bot_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_BOT_LOGINS` | Logins of the AI review bots the trigger phrase addresses, and whose review of the live head the merge gate waits for. Absent: the review-trigger module stays dormant and the merge gate's review-settle hold stays dormant. | | `babysit_review_settle_minutes` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_SETTLE_MINUTES` | How long after a head appears a review bot's re-review may still be in flight. The merge gate holds a head that bot has not reviewed yet until the window elapses, then stops waiting. Requires babysit_review_bot_logins; absent, the hold stays dormant. Set it above the reviewer's observed latency. | | `babysit_review_gate_context` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_REVIEW_GATE_CONTEXT` | Check/status context name of the AI-review gate. Absent: gate treated as absent (degrade). | | `babysit_ci_gateway_context` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_CI_GATEWAY_CONTEXT` | Check/status context name of a CI gateway check. Absent: gateway classification unused. | -| `babysit_extra_bot_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_EXTRA_BOT_LOGINS` | Additional logins to treat as bots when structural detection cannot identify them. Absent: structural detection only. | -| `babysit_extra_dependency_manager_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_EXTRA_DEPENDENCY_MANAGER_LOGINS` | Additional dependency-manager bot logins beyond the built-in dependabot/renovate set whose PRs the merge gate holds absent --allow-dependency, the same as the built-ins. Absent: built-in dependency-manager set only. | -| `babysit_approval_downgrade_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_APPROVAL_DOWNGRADE_LOGINS` | AI reviewer logins whose approval is surfaced as a `material` finding instead of `ignored` in the one case the structural approval-downgrade reaches: a review body carrying blocking-looking prose that still parses as an approval verdict (no CRITICAL/IMPORTANT or required-fix marker). Every bot's such approval is downgraded to non-blocking regardless; naming a login opts its own into the more-conservative `material` bucket rather than being ignored. Does not affect a review already in the APPROVED state or a plain clean approval with no blocking-looking prose — both are ignored regardless. Absent: such approvals are ignored for every bot. | -| `babysit_skip_downgrade_logins` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_SKIP_DOWNGRADE_LOGINS` | AI reviewer logins whose skip/no-op review is not treated as an approval. Absent: the downgrade heuristic stays dormant. | +| `babysit_extra_bot_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_EXTRA_BOT_LOGINS` | Additional logins to treat as bots when structural detection cannot identify them. Absent: structural detection only. | +| `babysit_extra_dependency_manager_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_EXTRA_DEPENDENCY_MANAGER_LOGINS` | Additional dependency-manager bot logins beyond the built-in dependabot/renovate set whose PRs the merge gate holds absent --allow-dependency, the same as the built-ins. Absent: built-in dependency-manager set only. | +| `babysit_approval_downgrade_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_APPROVAL_DOWNGRADE_LOGINS` | AI reviewer logins whose approval is surfaced as a `material` finding instead of `ignored` in the one case the structural approval-downgrade reaches: a review body carrying blocking-looking prose that still parses as an approval verdict (no CRITICAL/IMPORTANT or required-fix marker). Every bot's such approval is downgraded to non-blocking regardless; naming a login opts its own into the more-conservative `material` bucket rather than being ignored. Does not affect a review already in the APPROVED state or a plain clean approval with no blocking-looking prose — both are ignored regardless. Absent: such approvals are ignored for every bot. | +| `babysit_skip_downgrade_logins` | string (multiple) | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_SKIP_DOWNGRADE_LOGINS` | AI reviewer logins whose skip/no-op review is not treated as an approval. Absent: the downgrade heuristic stays dormant. | | `babysit_max_quiet_recheck_seconds` | number | `14400` | `CLAUDE_PLUGIN_OPTION_BABYSIT_MAX_QUIET_RECHECK_SECONDS` | Longest a quiet PR may go without a worker recheck. | | `babysit_stuck_check_age_seconds` | number | `1800` | `CLAUDE_PLUGIN_OPTION_BABYSIT_STUCK_CHECK_AGE_SECONDS` | Minimum age before a pending non-required check under UNSTABLE is reported stuck (stuck_queued / never_settling material finding). Orphaned status contexts with no backing run are detected structurally and ignore this threshold. | | `babysit_advisory_fix_round_cap` | number | `100` | `CLAUDE_PLUGIN_OPTION_BABYSIT_ADVISORY_FIX_ROUND_CAP` | Per-PR cap on advisory-only fix rounds (never caps blocking defects). | | `babysit_worker_concurrency_cap` | number | `10` | `CLAUDE_PLUGIN_OPTION_BABYSIT_WORKER_CONCURRENCY_CAP` | Maximum per-PR workers dispatched concurrently in one cycle. | | `babysit_worktree_root` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_BABYSIT_WORKTREE_ROOT` | Root directory for babysit-managed ephemeral worktrees. Absent: the worktrees/ subdirectory of the plugin data dir. | | `worktree_root` | directory | *(none)* | `CLAUDE_PLUGIN_OPTION_WORKTREE_ROOT` | External root under which /worktree create places worktrees, as /-- — a path OUTSIDE every repository (on Windows, the same drive as the repo). Absent: the worktrees/ subdirectory of the plugin data dir, which the skill supplies explicitly rather than reading from the environment (not per-plugin in a Bash-tool subprocess). Deliberately outside the repository tree AND outside repository-discovery roots such as a ghq root, which a checkout-relative default would land inside. Never the in-repo .claude/worktrees/ default: from a worktree nested inside a checkout, a read matching a path-scoped rule's glob also loads the parent checkout's copy of that rule. | -| `worktree_stale_days` | number | `14` | `CLAUDE_PLUGIN_OPTION_WORKTREE_STALE_DAYS` | Days since last commit before /worktree status classifies a worktree as stale | -| `fetch_logs_max_bytes` | number | `52428800` | `CLAUDE_PLUGIN_OPTION_FETCH_LOGS_MAX_BYTES` | Abort a CI-log ZIP fetch larger than this | +| `worktree_stale_days` | number
*min 1* | `14` | `CLAUDE_PLUGIN_OPTION_WORKTREE_STALE_DAYS` | Days since last commit before /worktree status classifies a worktree as stale | +| `fetch_logs_max_bytes` | number
*min 1* | `52428800` | `CLAUDE_PLUGIN_OPTION_FETCH_LOGS_MAX_BYTES` | Abort a CI-log ZIP fetch larger than this | | `branch_issue_pattern` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_BRANCH_ISSUE_PATTERN` | POSIX ERE for extracting the numeric GitHub issue number from the current branch name; the LAST capture group holds it and must resolve to digits (Closes #N honors only a numeric issue). Set this for a non-default branch scheme that places the number differently, e.g. '^\[^/\]+/(\[0-9\]+)-' for 'alice/1234-slug' or '-(\[0-9\]+)$' for 'feat/add-widget-1234'. Absent: the built-in '/-' (and routine-issue-) convention. | | `setup_inference_window` | string | `"1 year"` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_WINDOW` | git log --since window /source-control:setup samples for commit-subject convention inference (any git-approxidate, e.g. '1 year', '6 months'). Absent: 1 year. | -| `setup_inference_recency_days` | number | `90` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_RECENCY_DAYS` | Boundary for the recency split in /source-control:setup's convention-inference report — subjects newer than this many days are the 'recent' bucket, weighted as the live convention when its share diverges from the older bucket. Absent: 90. | -| `setup_inference_min_commits` | number | `50` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_MIN_COMMITS` | Below this many classifiable subjects in the window, /source-control:setup widens inference to full history; still below it, the inference is reported low-confidence rather than authoritative. Absent: 50. | +| `setup_inference_recency_days` | number
*min 1* | `90` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_RECENCY_DAYS` | Boundary for the recency split in /source-control:setup's convention-inference report — subjects newer than this many days are the 'recent' bucket, weighted as the live convention when its share diverges from the older bucket. Absent: 90. | +| `setup_inference_min_commits` | number
*min 1* | `50` | `CLAUDE_PLUGIN_OPTION_SETUP_INFERENCE_MIN_COMMITS` | Below this many classifiable subjects in the window, /source-control:setup widens inference to full history; still below it, the inference is reported low-confidence rather than authoritative. Absent: 50. | ### How to set these diff --git a/plugins/work-items/README.md b/plugins/work-items/README.md index ec73c01dd..f91f30057 100644 --- a/plugins/work-items/README.md +++ b/plugins/work-items/README.md @@ -153,12 +153,12 @@ reads it from. | Option | Type | Default | Environment variable | Description | | --- | --- | --- | --- | --- | | `lane_instance` | string | *(none)* | `CLAUDE_PLUGIN_OPTION_LANE_INSTANCE` | Writer identity for this machine's loop-lane telemetry, per the loop-lane convention's lane-instance identity rule. It becomes the suffix of the lane's telemetry sentinel marker (`work-items:work-loop@`), so each concurrently running lane instance owns its own comment and none can overwrite another's durable state — including first_drain_complete, whose loss would end one machine's earn-trust ratification gate because a different machine finished a drain. Must match ^\[a-z0-9\]\[a-z0-9-\]{0,31}$, be stable across restarts, and be distinct across concurrent instances; two lanes on one machine each need an explicit value. Absent: the sanitized lowercased hostname. The value appears verbatim in tracker comments — set an opaque id if a machine name should not be published in a public tracker. | -| `work_dispatch_concurrency_cap` | number | *(none)* | `CLAUDE_PLUGIN_OPTION_WORK_DISPATCH_CONCURRENCY_CAP` | Maximum concurrent dispatch waves /work-items:work's autonomous execute step allows per invocation (it runs exactly one item per invocation). Give a whole number of waves; a fractional value is floored to whole waves since a wave is discrete. When set, /work-items:work threads it into /implementation:implement-dispatch as that skill's --wave-cap ceiling. Leave unset to let implement-dispatch apply its own internal 3-5 wave default — this key declares no default, so an unset value stays distinguishable from a configured one (which a declared default would collapse into a hard cap). | -| `work_loop_item_cap_start` | number | `2` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_START` | Where the work-loop lane's adaptive per-cycle item cap starts. The cap ramps up by one after three consecutive clean items (never while a rate-limit warning is latched) and drops by one on any dirty item; enforcement is the loop body's own arithmetic. | -| `work_loop_item_cap_ceiling` | number | `3` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_CEILING` | Upper bound the work-loop lane's adaptive item cap can ramp to for non-frontier-tier items. Frontier-tier items are bounded separately by work_loop_frontier_item_cap_ceiling. | -| `work_loop_item_cap_floor` | number | `1` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_FLOOR` | Lower bound the work-loop lane's adaptive item cap can drop to on dirty items. | -| `work_loop_frontier_item_cap_ceiling` | number | `2` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_FRONTIER_ITEM_CAP_CEILING` | Quota guard for frontier-capability-tier items in the work-loop lane: such items run at concurrency 1 and their adaptive cap is bounded by this ceiling instead of the general one. Keep it at or below work_loop_item_cap_ceiling. The frontier tier is read from the item body, which any item author can write, so a frontier ceiling above the general one would let a body claim buy higher throughput; the lane detects that inversion and ignores this ceiling, bounding the item by the general one instead. The manifest cannot enforce the ordering — userConfig min/max are static bounds with no cross-key validation. | -| `work_loop_no_progress_threshold` | number | `3` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_NO_PROGRESS_THRESHOLD` | Consecutive no-progress cycles (actionable work in view, no item advanced and no PR opened) before the work-loop lane raises its stall escalation. The lane escalates and keeps looping; it never stops on a stall. Idle cycles with nothing actionable neither count nor reset. | +| `work_dispatch_concurrency_cap` | number
*min 1* | *(none)* | `CLAUDE_PLUGIN_OPTION_WORK_DISPATCH_CONCURRENCY_CAP` | Maximum concurrent dispatch waves /work-items:work's autonomous execute step allows per invocation (it runs exactly one item per invocation). Give a whole number of waves; a fractional value is floored to whole waves since a wave is discrete. When set, /work-items:work threads it into /implementation:implement-dispatch as that skill's --wave-cap ceiling. Leave unset to let implement-dispatch apply its own internal 3-5 wave default — this key declares no default, so an unset value stays distinguishable from a configured one (which a declared default would collapse into a hard cap). | +| `work_loop_item_cap_start` | number
*min 1* | `2` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_START` | Where the work-loop lane's adaptive per-cycle item cap starts. The cap ramps up by one after three consecutive clean items (never while a rate-limit warning is latched) and drops by one on any dirty item; enforcement is the loop body's own arithmetic. | +| `work_loop_item_cap_ceiling` | number
*min 1* | `3` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_CEILING` | Upper bound the work-loop lane's adaptive item cap can ramp to for non-frontier-tier items. Frontier-tier items are bounded separately by work_loop_frontier_item_cap_ceiling. | +| `work_loop_item_cap_floor` | number
*min 1* | `1` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_ITEM_CAP_FLOOR` | Lower bound the work-loop lane's adaptive item cap can drop to on dirty items. | +| `work_loop_frontier_item_cap_ceiling` | number
*min 1* | `2` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_FRONTIER_ITEM_CAP_CEILING` | Quota guard for frontier-capability-tier items in the work-loop lane: such items run at concurrency 1 and their adaptive cap is bounded by this ceiling instead of the general one. Keep it at or below work_loop_item_cap_ceiling. The frontier tier is read from the item body, which any item author can write, so a frontier ceiling above the general one would let a body claim buy higher throughput; the lane detects that inversion and ignores this ceiling, bounding the item by the general one instead. The manifest cannot enforce the ordering — userConfig min/max are static bounds with no cross-key validation. | +| `work_loop_no_progress_threshold` | number
*min 1* | `3` | `CLAUDE_PLUGIN_OPTION_WORK_LOOP_NO_PROGRESS_THRESHOLD` | Consecutive no-progress cycles (actionable work in view, no item advanced and no PR opened) before the work-loop lane raises its stall escalation. The lane escalates and keeps looping; it never stops on a stall. Idle cycles with nothing actionable neither count nor reset. | ### How to set these diff --git a/scripts/sync-plugin-options-docs.py b/scripts/sync-plugin-options-docs.py index 88a98c00d..f4720a6c5 100755 --- a/scripts/sync-plugin-options-docs.py +++ b/scripts/sync-plugin-options-docs.py @@ -56,6 +56,18 @@ def render(plugin: str, marketplace: str, options: dict) -> str: ] for key, spec in options.items(): typ = spec.get("type", "string") + # `multiple: true` means the option takes an array of that type, and the + # constraint keys bound it. Rendering only `type` made a repeated option + # indistinguishable from a scalar one -- source-control declares 10 such options + # whose hand-written prose already says "string (multiple)", so the generated + # table contradicted the prose beside it. + if spec.get("multiple"): + typ = f"{typ} (multiple)" + bounds = [f"{k} {spec[k]}" for k in ("min", "max") if k in spec] + if spec.get("required"): + bounds.insert(0, "required") + if bounds: + typ = f"{typ}
*{', '.join(str(b) for b in bounds)}*" default = spec.get("default", "") # MD049: this repo's markdownlint requires asterisk emphasis, not underscore. default = "*(none)*" if default == "" else f"`{json.dumps(default)}`" @@ -147,6 +159,23 @@ def main() -> int: print(f" MANIFEST UNPARSABLE: {d.name}: {exc}", file=sys.stderr) return 2 if not options: + # A plugin that removed its LAST option must lose its generated block too. + # Skipping here left a stale block documenting options that no longer exist, + # and --check never read the README on this branch, so the gate reported it + # as up to date -- the one path where the gate silently fails at its own job. + if readme.exists(): + current = readme.read_text(encoding="utf-8") + if BEGIN in current and END in current: + head = current[: current.index(BEGIN)] + tail = current[current.index(END) + len(END) :] + stripped = (head.rstrip("\n") + "\n" + tail.lstrip("\n")).rstrip("\n") + "\n" + if check: + stale.append(d.name) + else: + readme.write_text(stripped, encoding="utf-8", newline="\n") + wrote += 1 + print(f" removed stale block: plugins/{d.name}/README.md (0 options)") + continue skipped += 1 continue if not readme.exists(): From a7d44b2f1e5db16c8d6e467fe2739639b0c80d1b Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 11 Aug 2026 00:50:47 -0400 Subject: [PATCH 8/9] fix(rate-limit-guard): honor managed settings in the statusline tee gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.5.6 gate read only the user settings file, so a managed policy setting rate_limit_guard_enabled: false was ignored — a policy bypass, since managed settings are the highest-precedence scope and cannot be overridden by any user or project scope (hook-config-delivery fact 5). _rlg_tee_enabled now resolves managed → user settings → environment, highest first, mirroring the channel-F exemplars: the fixed per-platform root-owned managed paths selected by uname -s plus the managed-settings.d/ drop-ins, every resolved path re-checked as absolute. The env channel drops below user settings because a repo env block owns an unconfigured key (fact 4). Fails open on a missing file, missing jq, malformed JSON, or an unrecognized platform; prefix marketplace match retained; no `// empty` on the value. statusline-tee.test.sh gains real coverage of the gate — unconfigured, user false/true, fork marketplace, another plugin's identically-named option, malformed JSON, missing jq, and managed false-over-true plus the managed true-over-false mirror — each asserting the wrapped statusline's stdout is unchanged, plus one unstubbed end-to-end case. The script gained a main() behind the repo's existing BASH_SOURCE sourcing guard so the unwritable managed path list can be stubbed; a direct run is unchanged. Co-Authored-By: Claude Opus 5 --- .../.claude-plugin/plugin.json | 2 +- plugins/rate-limit-guard/CHANGELOG.md | 63 +++++ .../scripts/statusline-tee.sh | 235 ++++++++++++++---- .../scripts/statusline-tee.test.sh | 146 +++++++++++ 4 files changed, 390 insertions(+), 56 deletions(-) diff --git a/plugins/rate-limit-guard/.claude-plugin/plugin.json b/plugins/rate-limit-guard/.claude-plugin/plugin.json index 0fea54256..979b5e368 100644 --- a/plugins/rate-limit-guard/.claude-plugin/plugin.json +++ b/plugins/rate-limit-guard/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "rate-limit-guard", - "version": "0.5.6", + "version": "0.5.7", "description": "Shared rate-limit guard for loop lanes: a statusline wrapper tees the subscription rate-limit windows to a fixed machine-scope file, a StopFailure hook records rate-limit stops reactively, and a reader contract fixes how consuming sessions pause and resume.", "author": { "name": "Melodic Software", diff --git a/plugins/rate-limit-guard/CHANGELOG.md b/plugins/rate-limit-guard/CHANGELOG.md index 92fbddcba..6db4efa28 100644 --- a/plugins/rate-limit-guard/CHANGELOG.md +++ b/plugins/rate-limit-guard/CHANGELOG.md @@ -3,6 +3,69 @@ All notable changes to the `rate-limit-guard` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.7] + +### Fixed + +- **The statusline tee's kill switch read only one of the three scopes the contract names, so a + managed policy was silently ignored.** `0.5.6` moved the gate onto a direct settings read but + implemented the user settings file alone. This repository's own + [hook-config-delivery](../../docs/conventions/hook-config-delivery/README.md) convention, fact 5, + states that `pluginConfigs` is read back from **user settings, the `--settings` flag, and managed + settings** — so an organization that set `rate_limit_guard_enabled: false` in + `managed-settings.json` had the tee keep writing anyway. Managed settings are the + highest-precedence scope and cannot be overridden by any user or project scope, which is exactly + what makes that a policy bypass rather than a cosmetic omission. + + The gate now reads managed settings too, mirroring the channel-F exemplars the convention points + at — `plugins/disk-hygiene/lib/killswitch_config.py` and the sibling bash reader + `plugins/autonomy/hooks/lane-stop-gate-lib.sh`: the fixed per-platform root-owned paths + (`/Library/Application Support/ClaudeCode/`, `/etc/claude-code/`, `C:/Program Files/ClaudeCode/`) + selected by `uname -s`, plus the `managed-settings.d/` drop-ins in sorted order with later files + overriding earlier ones. The Windows path is the literal absolute path the docs give, never + `%ProgramFiles%`-derived, and every resolved path is re-checked as absolute — an + environment-derived or relative base would let a repository redirect the one scope that outranks + every other. + + **Precedence is now managed → user settings → environment**, highest first. Managed wins because + a gate a user or a repository can out-vote is not a policy control. The environment channel also + moved *below* user settings, which is a second behaviour change and deliberate: it is retained + only in case `CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED` is ever delivered to a `statusLine` + process, and for an unconfigured key a repository `.claude/settings.json` `env` block populates it + freely with no provenance (same convention, fact 4), so it must not out-vote a value a real + settings scope configured. Every previously held property survives: the tee fails **open** on a + missing file, missing `jq`, malformed JSON, or an unrecognized platform; the `pluginConfigs` key + is still matched by prefix so a fork or private catalog works; and the jq filter still avoids + `// empty` on the value — the alternative operator treats `false` as falsy and would discard the + exact value this gate exists to detect — using `tostring` plus an explicit `length == 0` emptiness + test instead. + + **Residuals (accepted, unchanged by this release).** The *user* settings file is still located + from `${CLAUDE_CONFIG_DIR:-$HOME/.claude}` rather than channel F's install-cache anchor, so a + repository `env` block that redirects `CLAUDE_CONFIG_DIR` can still hide a user-scope `false`; + and a value supplied only through a session `--settings` file is invisible to any on-disk read + (channel F's own documented residual). Neither reaches the managed verdict, which is + environment-independent by construction, so the scope an organization actually enforces with is + now sound. + +- **Nothing tested the gate at all.** `scripts/statusline-tee.test.sh` had no coverage of + `_rlg_tee_enabled` under either implementation; the `0.5.5` version passed review only because + the tests injected the environment variable by hand, and `0.5.6` carried the same gap forward. + The suite now drives the gate through real settings files on a scoped `HOME`: unconfigured (no + file, and a file with no `pluginConfigs`), user `false` and user `true`, a `false` under a + different marketplace suffix (the prefix match), another plugin's identically-named option and a + prefix-colliding plugin name, malformed JSON and a missing `jq` (both fail open), and managed + `false` over user `true` *and* managed `true` over user `false` — the mirror case is what + distinguishes real precedence from an or-of-falses. Every case also asserts that the wrapped + statusline's stdout is unchanged, because a gate that blanked the status line would be worse than + the bug it closes; one unstubbed end-to-end case exercises the script exactly as `settings.json` + invokes it. + + Managed settings live at fixed root-owned paths a test cannot write, so those cases source the + wrapper and stub the path list. To make that possible the script gained a `main` function behind + the `[[ "${BASH_SOURCE[0]}" == "${0}" ]]` sourcing guard already used elsewhere in this + repository, and its stdin read moved inside it; a direct invocation behaves exactly as before. + ## [0.5.6] ### Fixed diff --git a/plugins/rate-limit-guard/scripts/statusline-tee.sh b/plugins/rate-limit-guard/scripts/statusline-tee.sh index 6c5ca062b..686b8a398 100755 --- a/plugins/rate-limit-guard/scripts/statusline-tee.sh +++ b/plugins/rate-limit-guard/scripts/statusline-tee.sh @@ -69,11 +69,18 @@ INPUT="" # payload at the timeout (measured on Git Bash); Bash below 4.1 (macOS ships # 3.2) lacks -N and falls back to the delimiter form, fast enough on native # POSIX pipes. 1MiB bound: statusline payloads are a few KB. -if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 1))); then - IFS= read -r -N 1048576 -t 5 INPUT || true -else - IFS= read -r -d '' -t 5 INPUT || true -fi +# +# Deferred into a function rather than run at load: this file carries a sourcing +# guard at the bottom so its enablement gate can be driven by the test suite, and +# a top-level read would consume the sourcing shell's stdin before `main` runs. +read_tee_input() { + if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 1))); then + IFS= read -r -N 1048576 -t 5 INPUT || true + else + IFS= read -r -d '' -t 5 INPUT || true + fi + return 0 +} # Path of the temp file currently in flight, for the reclaim traps below. A # global rather than the function's local: the trap body is evaluated when the @@ -260,67 +267,185 @@ tee_snapshot() { # enablement says. Without this gate it is the one code path in the plugin # that keeps running when the plugin is off. # -# The library is the plugin's own synced copy in the sibling hooks/ directory. -# If it cannot be read the tee still runs, consistent with this script's rule -# that no tee outcome ever alters the wrapped statusline. +# If no scope contributes a verdict the tee still runs, consistent with this +# script's rule that no tee outcome ever alters the wrapped statusline. +# # CHANNEL: this process is NOT a hook process. Claude Code exports # CLAUDE_PLUGIN_OPTION_ to hook processes only (plugins-reference, "User # configuration"; and this repo's docs/conventions/hook-config-delivery/README.md scopes # the env channel the same way). Reading that variable alone would always find it unset, # always fall back to enabled, and produce a gate that is decorative -- so the value is -# read from the configured source directly, which is the sanctioned route for a non-hook -# consumer. The env var is still honored first in case it is ever delivered here. +# read from the configured sources directly (channel F), which is the sanctioned route +# for a non-hook consumer. The env var is still honored, but last (see the precedence +# note on _rlg_tee_enabled), in case it is ever delivered here. + +# Managed settings — the highest-precedence scope Claude Code honors for +# pluginConfigs (settings docs, "Settings precedence") and the one an organization +# uses to enforce a policy. Fixed, root-owned per-platform paths, the primary file +# first and then the `managed-settings.d/` drop-ins in sorted order; later files +# override earlier ones in _rlg_managed_option, mirroring Claude Code's merge. +# +# Platform comes from `uname -s`, never $OSTYPE — the same spelling as this repo's +# sibling bash channel-F reader, plugins/autonomy/hooks/lane-stop-gate-lib.sh, +# itself the bash equivalent of the python exemplar's sys.platform table +# (plugins/disk-hygiene/lib/killswitch_config.py::managed_settings_path). The +# Windows path is the literal absolute path the docs give, never +# %ProgramFiles%-derived: an environment-derived base would let a repo `env` block +# redirect the highest-precedence scope at the one process that trusts it most. +# +# Server-managed settings are deliberately excluded, as in the sibling reader: +# their only on-disk artifact is the user-writable cache +# ~/.claude/remote-settings.json, which fails this list's trust test. # -# Marketplace-agnostic: pluginConfigs is keyed `@`, and this plugin -# may be installed from a fork or private catalog, so the key is matched by prefix. +# Prints nothing on an unrecognized platform — managed then configures nothing and +# the user scope decides. +_rlg_managed_settings_files() { + local primary + case "$(uname -s 2>/dev/null)" in + Darwin) primary="/Library/Application Support/ClaudeCode/managed-settings.json" ;; + MINGW* | MSYS* | CYGWIN*) primary="C:/Program Files/ClaudeCode/managed-settings.json" ;; + Linux) primary="/etc/claude-code/managed-settings.json" ;; + *) return 0 ;; + esac + # Defense in depth: a managed path MUST be absolute (POSIX /… or a Windows + # drive). A relative value would resolve against this process's cwd — the + # user's checkout — turning the highest-precedence scope into a plantable file. + case "$primary" in + /* | [A-Za-z]:[/\\]*) ;; + *) return 0 ;; + esac + [[ -f "$primary" ]] && printf '%s\n' "$primary" + local dropin="${primary%/*}/managed-settings.d" f + if [[ -d "$dropin" ]]; then + for f in "$dropin"/*.json; do + [[ -f "$f" ]] && printf '%s\n' "$f" + done + fi + return 0 +} + +# Print this plugin's configured rate_limit_guard_enabled from ONE settings file +# as "true"/"false"; return 1 when that file contributes no verdict — absent, +# unreadable, unparsable, no matching entry, or a JSON null. Every no-verdict path +# is a fail-OPEN path at the caller. +# +# NOT `// empty` on the value: jq's alternative operator treats `false` as falsy, +# so the one value this gate exists to detect would be discarded. `tostring` yields +# "true"/"false", and emptiness is decided by an explicit `length == 0` on the +# collected array, so no alternative operator ever touches a boolean. +# +# Marketplace-agnostic: pluginConfigs is keyed `@`, and this +# plugin may be installed from a fork or private catalog, so the key is matched by +# PREFIX. Last match wins, as in the sibling reader. +# +# The file is opened by bash (`<"$file"`), not by jq: a native jq on Windows +# cannot open an MSYS-style path, while a shell redirection always can. +_rlg_settings_option() { + local file="$1" out + [[ -r "$file" ]] || return 1 + command -v jq >/dev/null 2>&1 || return 1 + out="$(jq -r '[ (.pluginConfigs // {}) | to_entries[] + | select(.key | startswith("rate-limit-guard@")) + | .value.options.rate_limit_guard_enabled + | select(. != null) | tostring ] + | if length == 0 then empty else last end' <"$file" 2>/dev/null)" || return 1 + [[ -n "$out" ]] || return 1 + printf '%s' "$out" +} + +# The managed-scope verdict, or return 1 when managed configures none. +_rlg_managed_option() { + local f v verdict="" have=1 + while IFS= read -r f; do + [[ -n "$f" ]] || continue + if v="$(_rlg_settings_option "$f")"; then + verdict="$v" + have=0 + fi + done < <(_rlg_managed_settings_files) + ((have == 0)) || return 1 + printf '%s' "$verdict" +} + +# PRECEDENCE, highest first: managed settings, then user settings, then the +# environment. Absent a verdict from all three the tee runs — the plugin default, +# and the fail-OPEN direction every degraded read lands on. +# +# Managed is authoritative because it is the scope an organization uses to enforce +# a policy and the one no user or project scope can override; a gate that let a +# user (or a repo) out-vote it would be exactly the policy bypass this function is +# supposed to close. The env channel sits BELOW user settings for the same reason +# it is kept at all: it is honored only in case it is ever delivered here, and for +# an UNCONFIGURED key a repo `.claude/settings.json` `env` block freely populates +# CLAUDE_PLUGIN_OPTION_* with no provenance +# (docs/conventions/hook-config-delivery/README.md fact 4), so it must never +# out-vote a value a real settings scope actually configured. +# +# RESIDUALS (accepted; see this plugin's CHANGELOG for the trust analysis): the +# USER settings file is still located from ${CLAUDE_CONFIG_DIR:-$HOME/.claude} +# rather than channel F's install anchor, and a value supplied only through a +# session `--settings` file is invisible to any on-disk read (channel F's own +# documented residual). Neither weakens the MANAGED verdict, which is +# environment-independent by construction. _rlg_tee_enabled() { + local v + if v="$(_rlg_managed_option)"; then + [[ "$v" == "false" ]] && return 1 + return 0 + fi + if v="$(_rlg_settings_option "${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}/settings.json")"; then + [[ "$v" == "false" ]] && return 1 + return 0 + fi case "${CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED:-}" in - true) return 0 ;; - "") : ;; - *) return 1 ;; + "" | true) return 0 ;; + *) return 1 ;; esac - local cfg="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}/settings.json" - [[ -r "$cfg" ]] || return 0 - command -v jq >/dev/null 2>&1 || return 0 - local value - # NOT `// empty`: jq's alternative operator treats `false` as falsy, so the one value - # this gate exists to detect would be discarded. `tostring` yields "false"/"true"/"null". - value="$(jq -r '(.pluginConfigs // {}) | to_entries[] - | select(.key | startswith("rate-limit-guard@")) - | .value.options.rate_limit_guard_enabled | tostring' "$cfg" 2>/dev/null \ - | grep -v '^null$' | head -n 1)" - [[ "$value" == "false" ]] && return 1 - return 0 } -if _rlg_tee_enabled; then - tee_snapshot -fi -if (($#)); then - # Wrapped mode: transparent passthrough. The wrapped command sees the same - # stdin bytes and owns stdout; its exit code is the wrapper's. pipefail is - # dropped for exactly this pipeline: a wrapped command that never reads - # stdin closes the pipe under printf, and pipefail would surface printf's - # SIGPIPE (141) instead of the wrapped command's own exit code. printf's - # stderr is silenced for the same case (bash prints a broken-pipe notice). - set +o pipefail - printf '%s' "$INPUT" 2>/dev/null | "$@" - rc=$? - set -o pipefail - if ! command -v jq >/dev/null 2>&1; then - printf 'rate-limit-guard: jq not found — rate-limit tee disabled (https://jqlang.org/download/)\n' +main() { + read_tee_input + + if _rlg_tee_enabled; then + tee_snapshot fi - exit "$rc" -fi -# Standalone mode: minimal statusline when none was configured. -if command -v jq >/dev/null 2>&1; then - printf '%s' "$INPUT" | jq -r ' - def pct(w): ((w // {}) | if .used_percentage != null then "\(.used_percentage)%" else "-" end); - "[\(.model.display_name // "Claude")] ctx \(.context_window.used_percentage // "-")% | 5h " - + pct(.rate_limits.five_hour) + " | 7d " + pct(.rate_limits.seven_day) - ' 2>/dev/null || printf 'rate-limit-guard: waiting for session data\n' -else - printf 'rate-limit-guard: jq not found — install jq (https://jqlang.org/download/)\n' + if (($#)); then + # Wrapped mode: transparent passthrough. The wrapped command sees the same + # stdin bytes and owns stdout; its exit code is the wrapper's. pipefail is + # dropped for exactly this pipeline: a wrapped command that never reads + # stdin closes the pipe under printf, and pipefail would surface printf's + # SIGPIPE (141) instead of the wrapped command's own exit code. printf's + # stderr is silenced for the same case (bash prints a broken-pipe notice). + local rc + set +o pipefail + printf '%s' "$INPUT" 2>/dev/null | "$@" + rc=$? + set -o pipefail + if ! command -v jq >/dev/null 2>&1; then + printf 'rate-limit-guard: jq not found — rate-limit tee disabled (https://jqlang.org/download/)\n' + fi + exit "$rc" + fi + + # Standalone mode: minimal statusline when none was configured. + if command -v jq >/dev/null 2>&1; then + printf '%s' "$INPUT" | jq -r ' + def pct(w): ((w // {}) | if .used_percentage != null then "\(.used_percentage)%" else "-" end); + "[\(.model.display_name // "Claude")] ctx \(.context_window.used_percentage // "-")% | 5h " + + pct(.rate_limits.five_hour) + " | 7d " + pct(.rate_limits.seven_day) + ' 2>/dev/null || printf 'rate-limit-guard: waiting for session data\n' + else + printf 'rate-limit-guard: jq not found — install jq (https://jqlang.org/download/)\n' + fi + exit 0 +} + +# Sourcing guard (the idiom already used by this repo's updater scripts): `main` +# runs only on a direct invocation, so a direct run is byte-for-byte what it was, +# while the test suite can SOURCE this file to stub _rlg_managed_settings_files — +# whose paths are fixed and root-owned by design, therefore unwritable from a +# test — and then drive the real end-to-end path through `main`. +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" fi -exit 0 diff --git a/plugins/rate-limit-guard/scripts/statusline-tee.test.sh b/plugins/rate-limit-guard/scripts/statusline-tee.test.sh index 4a5b1e64e..b2d59a08d 100755 --- a/plugins/rate-limit-guard/scripts/statusline-tee.test.sh +++ b/plugins/rate-limit-guard/scripts/statusline-tee.test.sh @@ -18,6 +18,11 @@ set -uo pipefail +# Hermeticity for the enablement-gate cases below: both of these are real inputs +# to _rlg_tee_enabled, so a developer's own exported value would otherwise decide +# what this suite proves. Every case supplies its settings through a scoped HOME. +unset CLAUDE_CONFIG_DIR CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TEE="$SCRIPT_DIR/statusline-tee.sh" @@ -389,6 +394,147 @@ else fail "stale lock directory survived" fi +# --- The enablement gate (_rlg_tee_enabled) ---------------------------------- +# Third limb of the contract: the tee's WRITE is gated by +# `rate_limit_guard_enabled`, resolved from the settings scopes Claude Code +# actually reads pluginConfigs back from (docs/conventions/hook-config-delivery, +# fact 5) rather than from an environment variable this non-hook process never +# receives. Two properties are inseparable and every case asserts BOTH: the gate +# decides whether tee_snapshot runs, and it NEVER touches the wrapped +# statusline's stdout — a gate that blanked the status line would be worse than +# the bug it closes. +# +# Managed settings live at fixed, root-owned system paths, unwritable from a test +# by design, so those cases source the wrapper (its `main` sourcing guard keeps +# that inert) to stub the path list and then drive the real end-to-end path +# through `main`. The user-scope cases are also proven black-box, unstubbed, by +# the plain `run()` case at the end. + +write_settings() { + mkdir -p "$(dirname "$1")" + printf '%s\n' "$2" >"$1" +} + +# Sourced-with-stub runner: $1 HOME, $2 managed settings file (may not exist), +# $3 stdin payload, rest = the wrapped statusline command. +gate_run() { + local home="$1" managed="$2" input="$3" + shift 3 + printf '%s' "$input" | HOME="$home" RLG_TEST_MANAGED="$managed" bash -c ' + source "$1" "$UNAME_SHIM/uname" +chmod +x "$UNAME_SHIM/uname" +if [[ -z "$(PATH="$UNAME_SHIM:$PATH" bash -c 'source "$1" Date: Tue, 11 Aug 2026 00:57:09 -0400 Subject: [PATCH 9/9] docs(rate-limit-guard): describe both surfaces the kill switch governs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifest's option description and the README's Configuration section still called rate_limit_guard_enabled the kill switch for the StopFailure hook alone, and the README told operators that disabling the statusline tee was their own settings edit — true before 0.5.5 gated the tee's write on the same option, wrong since. Both now name the hook and the tee's snapshot write, and the README states where each surface reads the value from and that the tee's precedence is managed → user settings → environment, so a managed value outranking a user one is explicable rather than surprising. README options block regenerated by scripts/sync-plugin-options-docs.py. Co-Authored-By: Claude Opus 5 --- .../.claude-plugin/plugin.json | 2 +- plugins/rate-limit-guard/CHANGELOG.md | 9 +++++++++ plugins/rate-limit-guard/README.md | 19 ++++++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/plugins/rate-limit-guard/.claude-plugin/plugin.json b/plugins/rate-limit-guard/.claude-plugin/plugin.json index 979b5e368..1420f48c7 100644 --- a/plugins/rate-limit-guard/.claude-plugin/plugin.json +++ b/plugins/rate-limit-guard/.claude-plugin/plugin.json @@ -20,7 +20,7 @@ "rate_limit_guard_enabled": { "type": "boolean", "title": "rate-limit-guard hook kill switch", - "description": "Master switch for the StopFailure detection hook", + "description": "Master switch for the StopFailure detection hook and the statusline tee's snapshot write; read from managed settings first, then user settings", "default": true } } diff --git a/plugins/rate-limit-guard/CHANGELOG.md b/plugins/rate-limit-guard/CHANGELOG.md index 6db4efa28..03c5796e0 100644 --- a/plugins/rate-limit-guard/CHANGELOG.md +++ b/plugins/rate-limit-guard/CHANGELOG.md @@ -66,6 +66,15 @@ All notable changes to the `rate-limit-guard` plugin are documented here. Format the `[[ "${BASH_SOURCE[0]}" == "${0}" ]]` sourcing guard already used elsewhere in this repository, and its stdin read moved inside it; a direct invocation behaves exactly as before. +- **The plugin's own documentation still described the pre-`0.5.5` single-surface switch.** Both + the manifest's option `description` (which is what `/plugin configure` shows) and the README's + `## Configuration` section called `rate_limit_guard_enabled` the kill switch for the StopFailure + hook alone, and the README additionally told operators that "disabling the statusline tee is the + operator's edit" — true before `0.5.5` gated the tee's write on the same option, wrong since. + Both now say the switch governs the hook **and** the tee's snapshot write, and the README states + where each surface reads it from and that the tee's precedence is managed → user → environment, + so an operator can tell why a managed value outranks the one they set themselves. + ## [0.5.6] ### Fixed diff --git a/plugins/rate-limit-guard/README.md b/plugins/rate-limit-guard/README.md index 26ba6b8aa..94d39344c 100644 --- a/plugins/rate-limit-guard/README.md +++ b/plugins/rate-limit-guard/README.md @@ -87,17 +87,26 @@ One `userConfig` option: | Option | What it controls | |---|---| -| `rate_limit_guard_enabled` | Kill switch for the StopFailure detection hook (default `true`). | +| `rate_limit_guard_enabled` | Kill switch for the StopFailure detection hook **and** the statusline tee's snapshot write (default `true`). | Set it with `/plugin configure rate-limit-guard`, or headless on a fresh install via `claude plugin install rate-limit-guard@ --config rate_limit_guard_enabled=false`. +**Where the switch is read from.** The StopFailure hook receives it the ordinary way, as +`CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED`. The statusline tee cannot: it is invoked by +absolute path from the operator's own `statusLine` setting, and Claude Code exports those variables +to *hook* processes only, so the tee reads `pluginConfigs` from the settings files directly. Its +precedence is **managed settings → user settings → environment**, highest first — a managed +`rate_limit_guard_enabled: false` is an organization's policy and wins over any user value. Every +degraded read (no settings file, no `jq`, malformed JSON) leaves the tee enabled, and no outcome of +that gate ever changes what your statusline prints. + The tee path and the 90% pause threshold are deliberately **not** configurable: they are contract constants that cross-plugin consumers inline from the [reader contract](reference/reader-contract.md); a per-user override would silently split the -writer from its readers. Disabling the statusline tee is the operator's edit (remove or unwrap the -statusline command); disabling the hook is the kill switch; disabling everything is -`enabledPlugins` / uninstall. +writer from its readers. The kill switch stops the tee's *write* while leaving the wrapper +transparent; removing the wrapper itself is the operator's edit to their `statusLine`; disabling +everything is `enabledPlugins` / uninstall. ## Consumers @@ -115,7 +124,7 @@ reads it from. | Option | Type | Default | Environment variable | Description | | --- | --- | --- | --- | --- | -| `rate_limit_guard_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED` | Master switch for the StopFailure detection hook | +| `rate_limit_guard_enabled` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_RATE_LIMIT_GUARD_ENABLED` | Master switch for the StopFailure detection hook and the statusline tee's snapshot write; read from managed settings first, then user settings | ### How to set these