Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .claude/hooks/hook-telemetry-sink.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,22 @@
# undetected pipe failures without aborting the process.

set -uo pipefail
# Hook directory by parameter expansion, never `dirname`. GNU Bash forks a
# subshell for every command substitution even when the body is a builtin
# (Command Substitution, Bash Reference Manual). On Windows Git Bash that
# fork is a process. `${BASH_SOURCE[0]%/*}` equals dirname for every shape
# BASH_SOURCE takes; the fallback covers a bare filename, where the strip is a
# no-op and dirname answers `.`.
HOOK_DIR="${BASH_SOURCE[0]%/*}"
[[ "$HOOK_DIR" == "${BASH_SOURCE[0]}" ]] && HOOK_DIR=.

# Repo-local copy of the claude-ops reference sink: hook-utils.sh is not
# colocated here, so source the repository's SSOT copy instead (same pattern as
# the sibling pr-linkage-mcp-gate.sh reaching shared sources by relative path).
# shellcheck source=../../lib/hook-utils.sh
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/hook-utils.sh"
source "$HOOK_DIR/../../lib/hook-utils.sh"
# shellcheck source=../../plugins/claude-ops/hooks/session-log-lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/../../plugins/claude-ops/hooks/session-log-lib.sh"

source "$HOOK_DIR/../../plugins/claude-ops/hooks/session-log-lib.sh"
INPUT=$(cat)
[[ -n "$INPUT" ]] || exit 0
# silent-skip-ok: fire-and-forget sink — the producer discards stdout+stderr,
Expand Down
5 changes: 3 additions & 2 deletions .claude/hooks/hook-telemetry-sink.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ upstream="$repo_root/plugins/claude-ops/hooks/hook-telemetry-sink.sh"

# Strip the resolution block from both: the shellcheck source directive, the
# source line itself, and the repo-copy-only comment lines that explain it.
strip_re='^# shellcheck source=|^source "\$\(dirname|^# Repo-local copy of the claude-ops reference sink|^# colocated here, so source the repository|^# the sibling pr-linkage-mcp-gate\.sh'
# shellcheck disable=SC2016 # the regex matches the literal source line, not an expansion
strip_re='^# shellcheck source=|^source "\$HOOK_DIR/|^source "\$\(dirname|^# Repo-local copy of the claude-ops reference sink|^# colocated here, so source the repository|^# the sibling pr-linkage-mcp-gate\.sh'

norm_upstream="$(mktemp)"
norm_local="$(mktemp)"
Expand All @@ -48,7 +49,7 @@ fi
# source line points somewhere broken. Pin the exact expected target, then
# prove the wiring executes end-to-end: a valid envelope must append a record.
# shellcheck disable=SC2016 # deliberate: the literal source line is the fixture
expected_source='source "$(dirname "${BASH_SOURCE[0]}")/../../lib/hook-utils.sh"'
expected_source='source "$HOOK_DIR/../../lib/hook-utils.sh"'
if ! grep -qF "$expected_source" "$local_copy"; then
echo "FAIL: the repo copy's source line does not target ../../lib/hook-utils.sh"
exit 1
Expand Down
2 changes: 1 addition & 1 deletion plugins/actionlint/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "actionlint",
"version": "0.8.37",
"version": "0.8.38",
"description": "Lint GitHub Actions workflow files on edit via actionlint, surfacing findings as advisory context.",
"author": {
"name": "Melodic Software",
Expand Down
13 changes: 13 additions & 0 deletions plugins/actionlint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
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.38]

### Changed

- **The hook locates its own directory and the edited file's directory with
parameter expansion, not `dirname`.** GNU Bash forks a subshell for every
command substitution even when the body is a builtin (Command Substitution,
Bash Reference Manual; https://mywiki.wooledge.org/CommandSubstitution).
On Windows Git Bash that fork is a process. `${BASH_SOURCE[0]%/*}` and
`${FILE%/*}` equal `dirname` for every shape those paths take; the empty-strip
fallback answers `/` at the filesystem root, matching GNU. What the hook
lints is unchanged.

## [0.8.37]

### Changed
Expand Down
16 changes: 13 additions & 3 deletions plugins/actionlint/hooks/actionlint-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ set -uo pipefail
# pay to parse hook-utils.sh to learn it is off. Same predicate as
# hook::is_enabled; scripts/check-killswitch-hoist.sh pins the two together.
[[ "${CLAUDE_PLUGIN_OPTION_ACTIONLINT_ENABLED:-true}" == "true" ]] || exit 0
# Hook directory by parameter expansion, never `dirname`. GNU Bash forks a
# subshell for every command substitution even when the body is a builtin
# (Command Substitution, Bash Reference Manual). On Windows Git Bash that
# fork is a process. `${BASH_SOURCE[0]%/*}` equals dirname for every shape
# BASH_SOURCE takes; the fallback covers a bare filename, where the strip is a
# no-op and dirname answers `.`.
HOOK_DIR="${BASH_SOURCE[0]%/*}"
[[ "$HOOK_DIR" == "${BASH_SOURCE[0]}" ]] && HOOK_DIR=.

# shellcheck source=hook-utils.sh
source "$(dirname "${BASH_SOURCE[0]}")/hook-utils.sh"

source "$HOOK_DIR/hook-utils.sh"
# Capture $EPOCHREALTIME immediately after the kill-switch so duration_ms covers
# the work below (pre-work exits do not emit telemetry). EPOCHREALTIME is Bash
# 5.0+; on older bash it is unset, so default to empty — referencing it bare
Expand Down Expand Up @@ -88,7 +95,10 @@ fi

# Resolve repo root early — used to compute the schema-required repo-relative
# path in data.file.
REPO_ROOT="$(hook::repo_root "$(dirname "$FILE")")"
FILE_DIR="${FILE%/*}"
[[ "$FILE_DIR" == "$FILE" ]] && FILE_DIR=.
[[ -n "$FILE_DIR" ]] || FILE_DIR=/
REPO_ROOT="$(hook::repo_root "$FILE_DIR")"
# Repo-relative path, serving two consumers: the schema-required data.file, and
# the argument actionlint runs on from the repo root. A path the prefix strip
# could not make relative degrades to its basename, which is right for telemetry
Expand Down
2 changes: 1 addition & 1 deletion plugins/autonomy/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "autonomy",
"version": "0.22.28",
"version": "0.22.29",
"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",
Expand Down
12 changes: 12 additions & 0 deletions plugins/autonomy/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
All notable changes to the `autonomy` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.22.29]

### Changed

- **Production hooks locate the hook directory with parameter expansion, not `dirname`.**
GNU Bash forks a subshell for every command substitution even when the body is a
builtin (Command Substitution, Bash Reference Manual;
https://mywiki.wooledge.org/CommandSubstitution). On Windows Git Bash that fork
is a process. `${BASH_SOURCE[0]%/*}` equals `dirname` for every shape BASH_SOURCE
takes; the fallback covers a bare filename, where the strip is a no-op and
dirname answers `.`. What the hook checks is unchanged.

## [0.22.28]

### Changed
Expand Down
19 changes: 15 additions & 4 deletions plugins/autonomy/hooks/lane-stop-gate-arm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,17 @@
# stays fail-open at stop time.

set -uo pipefail
# Hook directory by parameter expansion, never `dirname`. GNU Bash forks a
# subshell for every command substitution even when the body is a builtin
# (Command Substitution, Bash Reference Manual). On Windows Git Bash that
# fork is a process. `${BASH_SOURCE[0]%/*}` equals dirname for every shape
# BASH_SOURCE takes; the fallback covers a bare filename, where the strip is a
# no-op and dirname answers `.`.
HOOK_DIR="${BASH_SOURCE[0]%/*}"
[[ "$HOOK_DIR" == "${BASH_SOURCE[0]}" ]] && HOOK_DIR=.

# shellcheck source=lane-stop-gate-lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lane-stop-gate-lib.sh"

source "$HOOK_DIR/lane-stop-gate-lib.sh"
err() { printf 'ERROR: lane-stop-gate-arm: %s\n' "$*" >&2; }

usage() { awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"; }
Expand Down Expand Up @@ -96,8 +103,12 @@ command -v jq >/dev/null 2>&1 || {
exit 4
}

if ! gate_resolve_anchor "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)"; then
err "no plugins/cache install anchor for $(dirname -- "${BASH_SOURCE[0]}") — a --plugin-dir checkout install has no trusted record store, so the gate cannot be armed (managed settings remain the only enable path there)"
case "$HOOK_DIR" in
/* | ?:[/\\]*) _arm_root="$HOOK_DIR/.." ;;
*) _arm_root="$(cd "$HOOK_DIR/.." 2>/dev/null && pwd)" ;;
esac
if ! gate_resolve_anchor "$_arm_root"; then
err "no plugins/cache install anchor for $HOOK_DIR — a --plugin-dir checkout install has no trusted record store, so the gate cannot be armed (managed settings remain the only enable path there)"
exit 4
fi

Expand Down
20 changes: 15 additions & 5 deletions plugins/autonomy/hooks/lane-stop-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,25 @@
# lane_stop_gate_arm_id launcher-written arm-record id (never authority)

set -uo pipefail
# Hook directory by parameter expansion, never `dirname`. GNU Bash forks a
# subshell for every command substitution even when the body is a builtin
# (Command Substitution, Bash Reference Manual). On Windows Git Bash that
# fork is a process. `${BASH_SOURCE[0]%/*}` equals dirname for every shape
# BASH_SOURCE takes; the fallback covers a bare filename, where the strip is a
# no-op and dirname answers `.`.
HOOK_DIR="${BASH_SOURCE[0]%/*}"
[[ "$HOOK_DIR" == "${BASH_SOURCE[0]}" ]] && HOOK_DIR=.

# shellcheck source=hook-utils.sh
source "$(dirname "${BASH_SOURCE[0]}")/hook-utils.sh"
source "$HOOK_DIR/hook-utils.sh"
# shellcheck source=lane-notify.sh
source "$(dirname "${BASH_SOURCE[0]}")/lane-notify.sh"
source "$HOOK_DIR/lane-notify.sh"
# shellcheck source=lane-stop-gate-lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lane-stop-gate-lib.sh"

gate_resolve_install "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" || true
source "$HOOK_DIR/lane-stop-gate-lib.sh"
case "$HOOK_DIR" in
/* | ?:[/\\]*) gate_resolve_install "$HOOK_DIR/.." || true ;;
*) gate_resolve_install "$(cd "$HOOK_DIR/.." 2>/dev/null && pwd)" || true ;;
esac

# High-res start stamp for the telemetry envelope. EPOCHREALTIME is Bash 5.0+;
# on an older host it is empty and hook::emit_telemetry skips fail-open.
Expand Down
2 changes: 1 addition & 1 deletion plugins/bash-format/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "bash-format",
"version": "0.7.38",
"version": "0.7.39",
"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",
Expand Down
16 changes: 16 additions & 0 deletions plugins/bash-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@
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.39]

### Changed

- **`hooks/bash-format.sh` locates the hook directory, the edited file's directory,
and each parent in the EditorConfig walk with parameter expansion, not
`dirname`.** GNU Bash forks a subshell for every command substitution even when
the body is a builtin (Command Substitution, Bash Reference Manual;
https://mywiki.wooledge.org/CommandSubstitution). On Windows Git Bash that fork
is a process. Spawn census through a stable PATH shim
(`plugins/performance/scripts/spawn-census.sh`), `HOOK_TELEMETRY_SINK` unset,
matching `.sh` Write of an in-repo file: **9 → 4**. `dirname` 5 → 0; the
remaining four are `1 basename`, `1 git`, `1 jq`, `1 rm`. The milliseconds are
not the claim; the durable figure is the five PATH-visible `dirname` execs that
disappeared. What the hook formats is unchanged.

## [0.7.38]

### Changed
Expand Down
30 changes: 23 additions & 7 deletions plugins/bash-format/hooks/bash-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ set -uo pipefail
# pay to parse hook-utils.sh to learn it is off. Same predicate as
# hook::is_enabled; scripts/check-killswitch-hoist.sh pins the two together.
[[ "${CLAUDE_PLUGIN_OPTION_BASH_FORMAT_ENABLED:-true}" == "true" ]] || exit 0
# Hook directory by parameter expansion, never `dirname`. GNU Bash forks a
# subshell for every command substitution even when the body is a builtin
# (Command Substitution, Bash Reference Manual). On Windows Git Bash that
# fork is a process. `${BASH_SOURCE[0]%/*}` equals dirname for every shape
# BASH_SOURCE takes; the fallback covers a bare filename, where the strip is a
# no-op and dirname answers `.`.
HOOK_DIR="${BASH_SOURCE[0]%/*}"
[[ "$HOOK_DIR" == "${BASH_SOURCE[0]}" ]] && HOOK_DIR=.

# shellcheck source=hook-utils.sh
source "$(dirname "${BASH_SOURCE[0]}")/hook-utils.sh"
source "$HOOK_DIR/hook-utils.sh"
# shellcheck source=rewrite-guard.sh
source "$(dirname "${BASH_SOURCE[0]}")/rewrite-guard.sh"

source "$HOOK_DIR/rewrite-guard.sh"
# Capture $EPOCHREALTIME immediately after kill-switch so duration_ms covers the
# work below (pre-work exits do not emit telemetry). EPOCHREALTIME is Bash 5.0+;
# on older bash it is unset, so default to empty — referencing it bare under
Expand Down Expand Up @@ -69,7 +76,12 @@ esac

# Resolve repo root early — used to bound the .editorconfig opt-in walk and to
# compute the schema-required repo-relative path in data.file.
REPO_ROOT="$(hook::repo_root "$(dirname "$FILE")")"
# Parameter expansion, not a `$(dirname …)` subshell: same answers as dirname
# (no slash -> `.`, a root-level `/x` -> `/` rather than empty).
FILE_DIR="${FILE%/*}"
[[ "$FILE_DIR" == "$FILE" ]] && FILE_DIR=.
[[ -n "$FILE_DIR" ]] || FILE_DIR=/
REPO_ROOT="$(hook::repo_root "$FILE_DIR")"

# TOOL and FILE_REL feed the telemetry data object and nothing else, so both are
# resolved only when a sink is wired: the unwired default path spawns zero
Expand Down Expand Up @@ -127,8 +139,11 @@ section_applies_to_shell() {
# from the file to the repo root, stopping at a `root = true` config per
# EditorConfig search semantics. This gate is the whole formatting opt-in.
shell_editorconfig_opt_in() {
local dir root cfg line is_root parent
dir="$(cd "$(dirname "$FILE")" 2>/dev/null && pwd)" || return 1
local dir root cfg line is_root parent file_dir
file_dir="${FILE%/*}"
[[ "$file_dir" == "$FILE" ]] && file_dir=.
[[ -n "$file_dir" ]] || file_dir=/
dir="$(cd "$file_dir" 2>/dev/null && pwd)" || return 1
root="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || root=""
while :; do
cfg="$dir/.editorconfig"
Expand All @@ -145,7 +160,8 @@ shell_editorconfig_opt_in() {
[[ $is_root -eq 1 ]] && return 1 # root config, no shell section → stop
fi
[[ -n "$root" && "$dir" == "$root" ]] && return 1
parent="$(dirname "$dir")"
parent="${dir%/*}"
[[ -n "$parent" ]] || parent=/
[[ "$parent" == "$dir" ]] && return 1 # reached filesystem root
dir="$parent"
done
Expand Down
2 changes: 1 addition & 1 deletion plugins/biome-format/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "biome-format",
"version": "0.6.36",
"version": "0.6.37",
"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",
Expand Down
13 changes: 13 additions & 0 deletions plugins/biome-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
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.37]

### Changed

- **The hook locates its own directory, the edited file's directory, and each
parent in a config walk with parameter expansion, not `dirname`.** GNU Bash
forks a subshell for every command substitution even when the body is a builtin
(Command Substitution, Bash Reference Manual;
https://mywiki.wooledge.org/CommandSubstitution). On Windows Git Bash that
fork is a process. `${BASH_SOURCE[0]%/*}` and `${FILE%/*}` equal `dirname`
for every shape those paths take; the empty-strip fallback answers `/` at the
filesystem root, matching GNU. What the hook formats or lints is unchanged.

## [0.6.36]

### Changed
Expand Down
26 changes: 19 additions & 7 deletions plugins/biome-format/hooks/biome-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,19 @@ set -uo pipefail
# pay to parse hook-utils.sh to learn it is off. Same predicate as
# hook::is_enabled; scripts/check-killswitch-hoist.sh pins the two together.
[[ "${CLAUDE_PLUGIN_OPTION_BIOME_FORMAT_ENABLED:-true}" == "true" ]] || exit 0
# Hook directory by parameter expansion, never `dirname`. GNU Bash forks a
# subshell for every command substitution even when the body is a builtin
# (Command Substitution, Bash Reference Manual). On Windows Git Bash that
# fork is a process. `${BASH_SOURCE[0]%/*}` equals dirname for every shape
# BASH_SOURCE takes; the fallback covers a bare filename, where the strip is a
# no-op and dirname answers `.`.
HOOK_DIR="${BASH_SOURCE[0]%/*}"
[[ "$HOOK_DIR" == "${BASH_SOURCE[0]}" ]] && HOOK_DIR=.

# shellcheck source=hook-utils.sh
source "$(dirname "${BASH_SOURCE[0]}")/hook-utils.sh"
source "$HOOK_DIR/hook-utils.sh"
# shellcheck source=rewrite-guard.sh
source "$(dirname "${BASH_SOURCE[0]}")/rewrite-guard.sh"

source "$HOOK_DIR/rewrite-guard.sh"
# Capture $EPOCHREALTIME immediately after kill-switch so duration_ms covers the
# work below (pre-work exits do not emit telemetry). EPOCHREALTIME is Bash 5.0+;
# on older bash it is unset, so default to empty — referencing it bare under
Expand Down Expand Up @@ -72,7 +79,10 @@ esac

# Resolve repo root early — used to bound the biome-config opt-in walk and to
# compute the schema-required repo-relative path in data.file.
REPO_ROOT="$(hook::repo_root "$(dirname "$FILE")")"
FILE_DIR="${FILE%/*}"
[[ "$FILE_DIR" == "$FILE" ]] && FILE_DIR=.
[[ -n "$FILE_DIR" ]] || FILE_DIR=/
REPO_ROOT="$(hook::repo_root "$FILE_DIR")"

# TOOL and FILE_REL feed the telemetry data object and nothing else (Biome is
# invoked with a CONFIG_DIR-relative path computed below), so both are resolved
Expand Down Expand Up @@ -115,7 +125,7 @@ emit_skipped() {

# Resolve the file's directory in `pwd` form once. Both walks below start here,
# and it anchors the CONFIG_DIR-relative path passed to Biome.
FILE_DIR_POSIX="$(cd "$(dirname "$FILE")" 2>/dev/null && pwd)" || FILE_DIR_POSIX=""
FILE_DIR_POSIX="$(cd "$FILE_DIR" 2>/dev/null && pwd)" || FILE_DIR_POSIX=""
root="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || root=""

# Consumer opt-in: a Biome configuration that governs the edited file. Walk up
Expand All @@ -140,7 +150,8 @@ while [[ -n "$dir" ]]; do
[[ -f "$dir/$name" ]] && CONFIG_DIR="$dir" && break
done
[[ -n "$root" && "$dir" == "$root" ]] && break
parent="$(dirname "$dir")"
parent="${dir%/*}"
[[ -n "$parent" ]] || parent=/
[[ "$parent" == "$dir" ]] && break # reached filesystem root
dir="$parent"
done
Expand All @@ -159,7 +170,8 @@ while [[ -n "$dir" ]]; do
break
fi
[[ -n "$root" && "$dir" == "$root" ]] && break
parent="$(dirname "$dir")"
parent="${dir%/*}"
[[ -n "$parent" ]] || parent=/
[[ "$parent" == "$dir" ]] && break
dir="$parent"
done
Expand Down
Loading
Loading