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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ user opts in with `/plugin enable`; an existing install is never flipped by cata
- [`claude-config`](plugins/claude-config) — Four audit skills for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), and audit-model-fit (instruction surfaces swept for model-hobbling constraints newer models no longer need).
- [`claude-memory`](plugins/claude-memory) — Keeps a repo's Claude Code memory layer healthy and under your control, against criteria derived from official Claude Code documentation. The audit skill checks the instruction/memory layer (CLAUDE.md, CLAUDE.local.md, .claude/rules/, auto-memory) with a deterministic script-backed spine plus judgment-tier checks. The stateless skill inspects, disables, and (confirm-gated) purges Claude-written auto memory across all settings scopes.
- [`claude-ops`](plugins/claude-ops) — 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 and a repo-pull + marketplace-refresh launch step), 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.
- [`skill-quality`](plugins/skill-quality) — Skill-authoring QA tooling: a static contract checker that runs eighteen deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.
- [`skill-quality`](plugins/skill-quality) — Skill-authoring QA tooling: a static contract checker that runs twenty deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity, injection shell-declaration) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.

### Autonomy

Expand Down
4 changes: 2 additions & 2 deletions plugins/skill-quality/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "skill-quality",
"version": "0.7.2",
"description": "Skill-authoring QA tooling: a static contract checker that runs eighteen deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.",
"version": "0.8.0",
"description": "Skill-authoring QA tooling: a static contract checker that runs twenty deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity, injection shell-declaration) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.",
"author": {
"name": "Melodic Software",
"email": "info@melodicsoftware.com"
Expand Down
19 changes: 19 additions & 0 deletions plugins/skill-quality/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to the `skill-quality` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.8.0]

### Added

- **Check 19 (dynamic-context injection shell declaration) — FAIL/WARN.** A `` !`command` `` /
` ```! ` injection defaults to `shell: bash`; on a host without Git Bash it falls through to
the PowerShell tool, so a bash-only pipeline silently breaks (a 2026-07-21 fleet census found
64 such skills across 26 plugins). When a skill carries injections and declares no `shell:`
frontmatter, the check FAILs on detectable bash-only syntax (`/dev/null`, `command -v`, a pipe
into a Unix text tool with no same-named PowerShell cmdlet) and WARNs on portable-looking
commands (portability is not statically provable). A `shell:` declaration is trusted as the
author's explicit choice — no per-shell syntax validation. The scan is scoped to injected
command text only, never prose or a plain ` ```bash ` example.
- **Check 20 (injection defensive fallback) — WARN.** Injection failure/timeout/stderr semantics
are undocumented, so an unguarded command can inline an error string into the prompt. The check
WARNs on any injected command lacking a `|| <fallback>` continuation, per the pinned
precompute convention. It matches the `||` continuation, not the literal `echo` (`|| printf` /
`|| true` are valid fallbacks).

## [0.7.2]

### Changed
Expand Down
106 changes: 106 additions & 0 deletions plugins/skill-quality/scripts/check-skill.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
# 17. Vendor-backed: metadata.synced not older than 180 days (WARN)
# 18. Precompute opportunity: a fenced shell block gathers read-only context
# the skill could inline at load time via `!` injection (WARN; heuristic)
# 19. shell: declared when `!` dynamic-context injections carry bash-only syntax
# (FAIL bash-only syntax + no shell:; WARN portable-looking but undeclared)
# 20. `!`-injected commands carry a `|| <fallback>` (WARN; undocumented injection
# failure semantics — degrade to a known string, not a surprise)
#
# Notes (static, git-diff-based design):
# - Checks 3/8/9 diff the working tree against CHECK_SKILL_BASE_REF (default
Expand Down Expand Up @@ -549,6 +553,108 @@ if ((PRECOMPUTE_CANDIDATE == 1)); then
warn 'precompute opportunity: a fenced shell block runs read-only context-gathering commands the skill could inline at load time via `!`command`` / ```! dynamic-context injection (preprocessed once at load, no per-invocation tool call). If the block runs on every invocation to gather context, convert it; if it is an illustrative example, ignore. Heuristic over fenced shell blocks — see https://code.claude.com/docs/en/skills#inject-dynamic-context'
fi

# --- Checks 19-20: dynamic-context injection portability + fallback ----------
# Both checks scan the INJECTED command text only — never prose or a plain
# ```bash example that merely shows the syntax. Collect it once: every inline
# !`cmd` occurrence, plus the body lines of each ```! fenced block. Lines inside
# a NON-injection fenced block are literal examples, so inline !` there is
# skipped too (a general-fence state gates the inline scan). The inline form is
# recognized only at line start or after whitespace (per the injection docs), so
# a mid-token `!`` such as an inline `#!` code span in prose is not an injection.
INJECTIONS=()
inj_in_fence=0 # inside any fenced code block
inj_fence_len=0
inj_is_injection=0 # the current fence opened as a ```! injection block
# shellcheck disable=SC2016 # single quotes deliberate: backticks and $ are literal regex, not shell expansion
inj_fence_re='^(```+)([^`]*)$'
while IFS= read -r il || [[ -n "$il" ]]; do
itrim="${il#"${il%%[![:space:]]*}"}"
if [[ "$itrim" =~ $inj_fence_re ]]; then
iticks="${BASH_REMATCH[1]}"
iinfo="${BASH_REMATCH[2]}"
ilen=${#iticks}
iinfo="${iinfo#"${iinfo%%[![:space:]]*}"}" # ltrim the info string
if ((inj_in_fence == 0)); then
inj_in_fence=1
inj_fence_len=$ilen
[[ "$iinfo" == '!'* ]] && inj_is_injection=1 || inj_is_injection=0
elif ((ilen >= inj_fence_len)) && [[ -z "$iinfo" ]]; then
inj_in_fence=0
inj_is_injection=0
fi
continue
fi
if ((inj_in_fence == 1)); then
if ((inj_is_injection == 1)) && [[ -n "$itrim" && "$itrim" != \#* ]]; then
INJECTIONS+=("$il")
fi
continue # inside a non-injection fence → literal example, skip inline scan
fi
# Outside any fence: collect every inline injection on the line. grep -oE
# emits each anchored `<boundary>!`cmd`` match on its own line; strip the
# boundary + opening !` and the closing backtick to leave the command. Fixed
# literals do the stripping (never the command text), so a command carrying
# glob metacharacters is captured verbatim.
# shellcheck disable=SC2016 # single quotes deliberate: the backticks are literal delimiters, not shell expansion
while IFS= read -r inj_match; do
inj_match="${inj_match#*'!`'}" # drop boundary + opening !`
inj_match="${inj_match%'`'}" # drop closing backtick
INJECTIONS+=("$inj_match")
done < <(grep -oE '(^|[[:space:]])!`[^`]+`' <<<"$il")
done <"$SKILL_MD"

if ((${#INJECTIONS[@]} > 0)); then
# --- Check 19: shell declaration for bash-only injection syntax ------------
# A `!` injection defaults to bash; on a host without Git Bash it falls
# through to the PowerShell tool, so a bash-only pipeline silently breaks.
# Declaring `shell:` is the author taking explicit responsibility for the
# shell (we trust it — no per-shell syntax validation, so `shell: pwsh` with
# bash-only commands is out of scope). With no declaration, bash-only syntax
# is a FAIL; portable-looking commands are an unprovable WARN.
#
# Bash-only token set is deliberately narrow (tight avoids a false FAIL that
# blocks; anything missed degrades to the WARN path, never a false negative):
# `/dev/null` (PowerShell is `$null`), `command -v` (a bash builtin;
# PowerShell is `Get-Command`), and a pipe into a Unix text tool with no
# same-named PowerShell cmdlet. `sort`/`tee` are excluded — PowerShell aliases
# them, so a pipe there is not a clean break.
# shellcheck disable=SC2016 # single quotes deliberate: \| and $ are literal ERE, not shell expansion
bash_only_re='/dev/null|(^|[[:space:]])command[[:space:]]+-v([[:space:]]|$)|\|[[:space:]]*(head|tail|grep|sed|awk|cut|tr|wc|xargs|rev|nl|fold|paste|comm|join|column|uniq)([[:space:]]|$)'
if grep -qE '^shell:[[:space:]]*\S' <<<"$FRONTMATTER"; then
note "shell: declared — dynamic-context injection portability is the author's explicit choice"
else
bash_only_hit=""
for inj in "${INJECTIONS[@]}"; do
hit="$(grep -oE "$bash_only_re" <<<"$inj" | head -1 || true)"
if [[ -n "$hit" ]]; then
bash_only_hit="$hit"
break
fi
done
if [[ -n "$bash_only_hit" ]]; then
# shellcheck disable=SC2016 # single quotes deliberate: the backticked tokens in the message are literal
err "\`!\` dynamic-context injection uses bash-only syntax ('$bash_only_hit') with no \`shell:\` frontmatter — on a host without Git Bash the injection falls through to the PowerShell tool and breaks. Declare \`shell: bash\` (or write portable commands). See https://code.claude.com/docs/en/skills#inject-dynamic-context"
else
# shellcheck disable=SC2016 # single quotes deliberate: the backticked tokens in the message are literal
warn "\`!\` dynamic-context injection present with no \`shell:\` frontmatter — the commands look portable but static analysis can't prove it. Declare \`shell:\` explicitly, or confirm the commands run under the host's default shell"
fi
fi

# --- Check 20: injected commands carry a defensive fallback ----------------
# Injection failure/timeout/stderr semantics are undocumented, so an unguarded
# command can inline an error string (or nothing) into the prompt. The pinned
# convention is a `|| <fallback>` on every injected command; match the `||`
# continuation, not the literal `echo` (`|| printf`/`|| true` are valid too).
missing_fallback=0
for inj in "${INJECTIONS[@]}"; do
[[ "$inj" == *'||'* ]] || missing_fallback=$((missing_fallback + 1))
done
if ((missing_fallback > 0)); then
# shellcheck disable=SC2016 # single quotes deliberate: the backticked tokens in the message are literal
warn "$missing_fallback \`!\`-injected command(s) carry no \`|| <fallback>\` — injection failure/timeout/stderr semantics are undocumented, so an unguarded command can inline an error string into the prompt. Add a \`|| echo \"<fallback>\"\` (or shell-appropriate) continuation"
fi
fi

# --- Summary ---------------------------------------------------------------

printf '\n'
Expand Down
187 changes: 187 additions & 0 deletions plugins/skill-quality/scripts/check-skill.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,193 @@ else
fail "unpaired vendor/ diff should still fail (rc=$rc): $out"
fi

# 21. A `!` injection with a `shell:` declaration is silent — the author has
# taken explicit responsibility for the shell (check 19).
make_skill inj-shell-ok '---
name: inj-shell-ok
description: "Injects context. Use when: '"'"'injecting with a shell decl'"'"'."
shell: bash
---

## Context
- Tree: !`git status --short 2>/dev/null || echo "(clean)"`

## Gotchas

None known.
'
out="$(run inj-shell-ok 2>&1)"
rc=$?
if [[ $rc -eq 0 ]] && ! grep -q 'falls through to the PowerShell tool' <<<"$out" && ! grep -q 'look portable but static analysis' <<<"$out"; then
pass "injection with a shell: declaration is silent (check 19)"
else
fail "injection + shell: decl should not warn/fail check 19 (rc=$rc): $out"
fi

# 22. A `!` injection with bash-only syntax (/dev/null) and NO `shell:` fails
# (check 19 — the census regression this gate exists for).
make_skill inj-bashonly-fail '---
name: inj-bashonly-fail
description: "Injects context. Use when: '"'"'injecting bash-only syntax'"'"'."
---

## Context
- Tree: !`git status --short 2>/dev/null || echo "(clean)"`

## Gotchas

None known.
'
out="$(run inj-bashonly-fail 2>&1)"
rc=$?
if [[ $rc -eq 1 ]] && grep -q 'falls through to the PowerShell tool' <<<"$out"; then
pass "bash-only injection without shell: fails (check 19)"
else
fail "bash-only injection + no shell: should fail check 19 (rc=$rc): $out"
fi

# 23. A `!` injection whose commands look portable, with no `shell:`, WARNs
# (portability is not statically provable) but passes (check 19).
make_skill inj-portable-warn '---
name: inj-portable-warn
description: "Injects context. Use when: '"'"'injecting portable commands'"'"'."
---

## Context
- Branch: !`git branch --show-current`

## Gotchas

None known.
'
out="$(run inj-portable-warn 2>&1)"
rc=$?
if [[ $rc -eq 0 ]] && grep -q 'look portable but static analysis' <<<"$out"; then
pass "portable-looking injection without shell: warns without failing (check 19)"
else
fail "portable injection + no shell: should warn not fail (rc=$rc): $out"
fi

# 24. A ```! FENCED injection with bash-only syntax (| head) and no `shell:`
# fails — exercises the fenced-block extraction feeding check 19.
make_skill inj-fenced-fail '---
name: inj-fenced-fail
description: "Injects a block. Use when: '"'"'injecting a fenced block'"'"'."
---

## Snapshot

```!
git log --oneline | head -5
```

## Gotchas

None known.
'
out="$(run inj-fenced-fail 2>&1)"
rc=$?
if [[ $rc -eq 1 ]] && grep -q 'falls through to the PowerShell tool' <<<"$out"; then
pass "fenced injection block with bash-only syntax fails (check 19 fence extraction)"
else
fail "bash-only fenced injection should fail check 19 (rc=$rc): $out"
fi

# 25. The over-reach guard: a bash-only token in a PLAIN ```bash block (not an
# injection) must NOT flag check 19/20 — the scan is scoped to injected text.
make_skill inj-plainblock-safe '---
name: inj-plainblock-safe
description: "Shows an example. Use when: '"'"'showing a plain shell example'"'"'."
---

## Example

```bash
git status --short 2>/dev/null | head -5
```

## Gotchas

None known.
'
out="$(run inj-plainblock-safe 2>&1)"
rc=$?
if [[ $rc -eq 0 ]] && ! grep -q 'falls through to the PowerShell tool' <<<"$out" && ! grep -q 'look portable but static analysis' <<<"$out"; then
pass "bash-only tokens in a plain bash code block do not flag check 19 (scoped to injections)"
else
fail "plain bash code block should not trip the injection checks (rc=$rc): $out"
fi

# 26. An injected command with no `|| <fallback>` warns (check 20), isolated
# from check 19 by a `shell:` declaration.
make_skill inj-nofallback-warn '---
name: inj-nofallback-warn
description: "Injects context. Use when: '"'"'injecting without a fallback'"'"'."
shell: bash
---

## Context
- Tree: !`git status --short`

## Gotchas

None known.
'
out="$(run inj-nofallback-warn 2>&1)"
rc=$?
if [[ $rc -eq 0 ]] && grep -q 'carry no' <<<"$out"; then
pass "injected command without a || fallback warns (check 20)"
else
fail "missing || fallback should warn (rc=$rc): $out"
fi

# 27. A `|| printf` fallback satisfies check 20 — the gate matches the `||`
# continuation, not the literal `echo`.
make_skill inj-printf-fallback '---
name: inj-printf-fallback
description: "Injects context. Use when: '"'"'injecting with a printf fallback'"'"'."
shell: bash
---

## Context
- Tree: !`git status --short || printf "(clean)"`

## Gotchas

None known.
'
out="$(run inj-printf-fallback 2>&1)"
rc=$?
if [[ $rc -eq 0 ]] && ! grep -q 'carry no' <<<"$out"; then
pass "a || printf fallback satisfies check 20 (matches || not echo)"
else
fail "|| printf should count as a fallback (rc=$rc): $out"
fi

# 28. Anchor guard: a mid-token `!`` in prose (an inline `#!` code span) is NOT
# an injection, so a no-shell skill carrying one is not scanned (checks 19/20
# stay silent). The inline form is recognized only at start / after space.
make_skill inj-prose-hashbang '---
name: inj-prose-hashbang
description: "Mentions a shebang. Use when: '"'"'mentioning a shebang in prose'"'"'."
---

## Notes

For a file whose first line is a shebang (`#!`), confirm the recorded mode.

## Gotchas

None known.
'
out="$(run inj-prose-hashbang 2>&1)"
rc=$?
if [[ $rc -eq 0 ]] && ! grep -q 'falls through to the PowerShell tool' <<<"$out" && ! grep -q 'look portable but static analysis' <<<"$out" && ! grep -q 'carry no' <<<"$out"; then
pass "a mid-token !\` in prose (inline #! code span) is not treated as an injection"
else
fail "prose #! code span should not trip the injection checks (rc=$rc): $out"
fi

if [[ $fails -ne 0 ]]; then
printf '%d assertion(s) failed\n' "$fails" >&2
exit 1
Expand Down
Loading
Loading