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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,19 @@ jobs:
- name: Run cross-plugin-source-drift tests
run: bash scripts/check-cross-plugin-source-drift.test.sh

silent-skip-gate:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Check hook entry scripts for silent prerequisite skips
run: scripts/check-silent-skips.sh
Comment thread
kyle-sexton marked this conversation as resolved.
- name: Run silent-skip-gate tests
run: bash scripts/check-silent-skips.test.sh

plugin-gate:
runs-on: ubuntu-24.04
timeout-minutes: 15
Expand Down Expand Up @@ -350,6 +363,7 @@ jobs:
- hook-utils-sync
- standards-contract-sync
- cross-plugin-source-drift
- silent-skip-gate
- plugin-gate
- miro-plugin
- runner-policy
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-ops/.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": "claude-ops",
"version": "0.13.0",
"version": "0.13.1",
"description": "Claude Code operations toolkit. Five 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), 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",
Expand Down
9 changes: 9 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
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.13.1]

### Changed

- **`hook-telemetry-sink` quiet jq skip documented at the site** with a
`silent-skip-ok` annotation (the marketplace's new silent-skip CI gate). No
behavior change: the sink is fire-and-forget — its producer discards
stdout+stderr, so prerequisite visibility is owned by the producer side.

## [0.13.0]

### Changed
Expand Down
2 changes: 2 additions & 0 deletions plugins/claude-ops/hooks/hook-telemetry-sink.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ source "$(dirname "${BASH_SOURCE[0]}")/hook-utils.sh"

INPUT=$(cat)
[[ -n "$INPUT" ]] || exit 0
# silent-skip-ok: fire-and-forget sink — the producer discards stdout+stderr,
# so no notice channel exists; the producer side owns prerequisite visibility.
command -v jq >/dev/null 2>&1 || exit 0

# Single jq parse + required-key guard. Emit one mapped field per line (nothing
Expand Down
2 changes: 1 addition & 1 deletion plugins/guardrails/.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": "guardrails",
"version": "0.7.0",
"version": "0.7.1",
"description": "Eight 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, (advisory) hallucinated CLI flags, (advisory) un-throttled Workflow fan-out that risks burst 529s, and (advisory) direct git commit/gh pr create calls bypassing this marketplace's own commit/pull-request skills — each independently toggleable.",
"author": {
"name": "Melodic Software",
Expand Down
9 changes: 9 additions & 0 deletions plugins/guardrails/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
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.7.1]

### Fixed

- **`flag-commit-pr-skill-bypass` jq-absent skip is now visible** (prerequisite-visibility
doctrine). The hook previously no-op'd silently when `jq` was missing; it now writes the
same one-line stderr notice its sibling guardrails hooks emit ("advisory disabled —
install jq to enable") before exiting 0.

## [0.7.0]

### Added
Expand Down
4 changes: 3 additions & 1 deletion plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ hook::check_enabled "FLAG_COMMIT_PR_SKILL_BYPASS"
start=${EPOCHREALTIME:-}

# jq is required both to parse the tool payload and to read enabledPlugins.
# Fail OPEN (silent, advisory-only guard) when it is absent.
# Fail OPEN when it is absent, but make the degraded state visible rather than
# silently disabling the advisory (this hook never blocks either way).
if ! command -v jq >/dev/null 2>&1; then
echo "guardrails/flag-commit-pr-skill-bypass: jq not found on PATH — advisory disabled (install jq to enable)." >&2
exit 0
fi

Expand Down
2 changes: 1 addition & 1 deletion plugins/powershell-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": "powershell-format",
"version": "0.4.0",
"version": "0.4.1",
"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",
Expand Down
9 changes: 9 additions & 0 deletions plugins/powershell-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
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.4.1]

### Changed

- **Quiet pwsh-absent skip documented at the site** with a `silent-skip-ok`
annotation (the marketplace's new silent-skip CI gate). No behavior change:
absent `pwsh` remains a by-design not-applicable quiet skip, still recorded
via opt-in telemetry.

## [0.4.0]

### Added
Expand Down
2 changes: 2 additions & 0 deletions plugins/powershell-format/hooks/powershell-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ done
# Resolve pwsh from PATH — never downloaded. Absent -> clean skip (a pwsh-less
# contributor box, or a Linux cloud session without PowerShell). CI's PowerShell
# job is the authoritative PSScriptAnalyzer gate, so nothing is lost locally.
# silent-skip-ok: not-applicable classification — absent pwsh is a by-design
# quiet skip on hosts without PowerShell; telemetry still records the skip.
command -v pwsh >/dev/null 2>&1 || emit_skipped

# PowerShell on Windows does not understand MSYS mount paths (/d/...). Convert
Expand Down
122 changes: 122 additions & 0 deletions scripts/check-silent-skips.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# Gate: a hook that skips because a CLI prerequisite is absent must make the
# skip visible. The prerequisite-visibility doctrine classifies absence as
# required (stop with remediation), optional (visible warn + skip), or
# not-applicable (quiet) — a quiet skip is only ever a deliberate,
# documented classification, never a default.
#
# scripts/check-silent-skips.sh fail if any hook entry script
# silently skips on a missing CLI
#
# Two shapes are flagged in plugins/*/hooks/*.sh (entry scripts only —
# hook-utils.sh lib copies have their own sync gate and review; *.test.sh
# files exercise these patterns as fixtures):
#
# 1. same-line guard: command -v X ... || exit 0 (also `return 0`, or
# a skip-named helper such as `|| emit_skipped`)
# 2. block guard: if ! command -v X ...; then ... fi where the block
# reaches `exit 0` / `return 0`
#
# A flagged site passes when the skip is visible — the guard line or block
# carries one of the sanctioned visibility calls (hook::emit_skip_notice,
# hook::emit_system_message, hook::notice_once, hook::require_jq, or a
# stderr write) — or when it is a documented quiet classification: an
# annotation comment `# silent-skip-ok: <reason>` on the guard line, in the
# comment block immediately above it, or inside the guard block. The
# annotation is the recorded decision; a bare quiet skip is a defect.
#
# This is a grep-level tripwire, not a semantic proof: it does not chase
# helper-function bodies and does not flag a positive-form
# `if command -v X; then ... else exit 0` (the current corpus uses the
# else-branch for its visible notice). Its job is to stop the historical
# regression — a new hook quietly no-op'ing when an optional CLI is absent.
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/.."

errors=0

for hook in plugins/*/hooks/*.sh; do
base="${hook##*/}"
case "$base" in
hook-utils.sh | *.test.sh) continue ;;
*) ;;
esac
[[ -f "$hook" ]] || continue

out=$(awk '
function is_annotated(l) { return l ~ /#[[:space:]]*silent-skip-ok:/ }
function is_comment(l) { return l ~ /^[[:space:]]*#/ }
function is_visible(l) {
return l ~ /hook::emit_skip_notice/ || l ~ /hook::emit_system_message/ ||
l ~ /hook::notice_once/ || l ~ /hook::require_jq/ || l ~ />&2/
}
{
line = $0

# Shape 2: block guard. Statement-per-line shell (shfmt-formatted), so
# `if`/`fi` sit at line start; comment lines never open or close a block.
if (in_block) {
if (line ~ /^[[:space:]]*if[[:space:]]/) depth++
if (line ~ /^[[:space:]]*fi([[:space:]]*(#.*)?)?$/) depth--
if (is_visible(line)) block_visible = 1
if (is_annotated(line)) block_annotated = 1
if (line ~ /(^|[[:space:]])(exit|return)[[:space:]]+0([[:space:]]|$|;)/) block_skips = 1
if (depth == 0) {
if (block_skips && !block_visible && !block_annotated)
printf "%d: silent block skip: `if ! command -v ...` reaches exit/return 0 with no visible notice\n", block_start
in_block = 0
}
next
}

# An annotation anywhere in the contiguous comment block directly above
# a guard sanctions that guard; any code line ends the pending block.
annotated_above = pending_annot
if (is_comment(line)) {
if (is_annotated(line)) pending_annot = 1
} else {
pending_annot = 0
}

if (line ~ /^[[:space:]]*(el)?if[[:space:]]+!.*command -v/) {
in_block = 1; depth = 1; block_start = NR
block_visible = is_visible(line)
block_annotated = is_annotated(line) || annotated_above
block_skips = 0
next
}

# Shape 1: same-line guard.
if (line ~ /command -v/ &&
(line ~ /\|\|[[:space:]]*exit[[:space:]]+0/ ||
line ~ /\|\|[[:space:]]*return[[:space:]]+0/ ||
line ~ /\|\|[[:space:]]*[A-Za-z_][A-Za-z0-9_:]*[Ss]kip/)) {
if (!is_visible(line) && !is_annotated(line) && !annotated_above)
printf "%d: silent skip: `command -v ... ||` quiet skip with no visible notice\n", NR
}
}
END {
if (in_block && block_skips && !block_visible && !block_annotated)
printf "%d: silent block skip: unterminated `if ! command -v` block reaches exit/return 0 with no visible notice\n", block_start
}
' "$hook")

if [[ -n "$out" ]]; then
while IFS= read -r v; do
echo "SILENT SKIP: ${hook}:${v}" >&2
errors=$((errors + 1))
done <<<"$out"
fi
done

if ((errors > 0)); then
{
echo
echo "A missing-CLI skip must be visible (hook::emit_skip_notice /"
echo "hook::emit_system_message / a stderr write) or carry a documented"
echo "quiet classification: '# silent-skip-ok: <reason>' at the site."
} >&2
exit 1
fi
echo "No silent prerequisite skips found in hook entry scripts."
Loading
Loading