Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@
"author": {
"name": "JacobPEvans"
}
},
{
"name": "skill-guards",
"source": "./skill-guards",
"description": "Ensures fresh execution of skills on every invocation via UserPromptSubmit hook",
"version": "2.3.2",
"author": {
"name": "JacobPEvans"
}
}
]
}
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This is a **Claude Code plugins repository** containing production-ready hooks f
| **process-cleanup** | PostToolUse | — | Cleanup orphaned MCP server processes on session exit |
| **project-standards** | Skill | `/agentsmd-authoring`, `/workspace-standards`, `/skills-registry` | AgentsMD authoring standards, workspace management, and skills/tools registry lookup |
| **session-analytics** | Skill | `/token-breakdown` | Session token analytics via Splunk OTEL telemetry |
| **skill-guards** | UserPromptSubmit | — | Ensures fresh execution of skills on every invocation |

## Multi-Model Delegation

Expand Down
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"ENDJSON",
"RLENGTH",
"unrecognised",
"tokenisation"
"tokenisation",
"reinvocation"
]
}
3 changes: 3 additions & 0 deletions git-workflows/skills/refresh-repo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ description: Check PR merge readiness, sync local repo, and cleanup stale worktr
Check open PR merge-readiness status, sync the local repository, and cleanup stale worktrees.
**Note**: Does not automatically merge PRs - only reports readiness status for each PR.

> **State warning**: Branch state, remote tracking, and PR status change between
> invocations. Re-run all git/gh commands from Step 1.

## Steps

### 1. Identify Open PRs
Expand Down
3 changes: 3 additions & 0 deletions git-workflows/skills/wrap-up/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ description: "End-of-session cleanup after PR merge: refresh repo, run quick ret

# Post-Merge Wrap-Up

> **State warning**: Branch state, remote tracking, and PR status change between
> invocations. Re-run all git/gh commands from Step 1.

Run Steps 1 and 2 **in parallel** (they are independent). Step 3 starts as soon as
Step 1 completes (depends on its remote prune). Step 4 runs after all prior steps finish.
Provide a summary of actions taken.
Expand Down
4 changes: 4 additions & 0 deletions github-workflows/skills/finalize-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ metadata:
**FULLY AUTOMATIC** - Fully automates PR finalization: monitor, fix, prepare for merge. Assumes PR already exists.
No manual intervention required. For manual review-focused workflows, use `/review-pr`.

> **State warning**: Automated reviewers (CodeQL, Copilot, AI reviews) post
> asynchronously. CI may have re-run. Merge conflicts may have appeared.
> Re-fetch live PR state from Step 1.

## Critical Rules

1. **Wait for user approval to merge** - Report ready status, then pause for user merge command
Expand Down
3 changes: 3 additions & 0 deletions github-workflows/skills/resolve-pr-threads/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Orchestrates resolution of all unresolved PR review comments by grouping
related threads, processing each group sequentially inline to implement
fixes or provide explanations, then resolving threads via GitHub's GraphQL API.

> **State warning**: Thread IDs and resolution status change as reviews arrive.
> Re-fetch all open threads — cached thread lists from earlier are unreliable.

## Usage

```text
Expand Down
4 changes: 4 additions & 0 deletions github-workflows/skills/ship/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ allowed-tools: Bash(git *), Bash(gh *), Bash(pre-commit *), Bash(npm run lint*),
**Single command to commit, push, create PR(s), and auto-finalize everything.**
Handles commit, push, PR creation, and `/finalize-pr` in one pipeline. Never merges.

> **State warning**: Automated reviewers (CodeQL, Copilot, AI reviews) post
> asynchronously. CI may have re-run. Merge conflicts may have appeared.
> Re-fetch live PR state from Step 1.

## Rate Limit Awareness

This skill orchestrates many downstream API calls via `/finalize-pr`,
Expand Down
3 changes: 3 additions & 0 deletions github-workflows/skills/squash-merge-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Validates PR readiness and executes squash merge. If the PR is not ready,
errors immediately and suggests `/finalize-pr` to fix issues. Never fixes
issues — only merges.

> **State warning**: Branch state, remote tracking, and PR status change between
> invocations. Re-run all git/gh commands from Step 1.

## Critical Rules

- **Never fix issues** — only merge. If PR isn't ready, error and suggest `/finalize-pr`
Expand Down
9 changes: 9 additions & 0 deletions skill-guards/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "skill-guards",
"version": "2.3.2",
"description": "Ensures fresh execution of skills on every invocation via UserPromptSubmit hook",
"author": {"name": "JacobPEvans"},
"homepage": "https://github.com/JacobPEvans/claude-code-plugins",
"keywords": ["skills", "execution", "guards", "hooks"],
"license": "Apache-2.0"
}
38 changes: 38 additions & 0 deletions skill-guards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# skill-guards

Ensures fresh execution of skills on every invocation via a UserPromptSubmit hook.

## Problem

When a skill like `/ship` is called a second time in a session, Claude may shortcut
by assuming previous results are still valid instead of re-running all commands against
live state.

## Solution

A UserPromptSubmit hook detects `/skill-name` patterns in user prompts and injects a
systemMessage reminding Claude to execute every step from scratch using current live state.

## Installation

```bash
claude plugins add jacobpevans-cc-plugins/skill-guards
```

## Usage

No manual invocation required. The hook activates automatically on every user prompt.

## Hook Behavior

- **Fires on**: Every user prompt submission
- **Detects**: `/lowercase-with-hyphens` skill invocation patterns
- **Excludes**: Common filesystem paths (`/usr`, `/tmp`, `/etc`, `/nix`, etc.)
- **Output**: `{"systemMessage": "FRESH EXECUTION: /skill — ..."}` or `{}`
- **Exit code**: Always 0 (never blocks prompts)

## Part of a Three-Layer System

1. **Global rule** (`skill-execution-integrity`) — establishes the mental model
2. **This hook** — tactical trigger at moment of invocation
3. **Skill preambles** — skill-specific state warnings in SKILL.md files
16 changes: 16 additions & 0 deletions skill-guards/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/skill-reinvocation-guard.sh",
"timeout": 3
}
]
}
]
}
}
21 changes: 21 additions & 0 deletions skill-guards/scripts/skill-reinvocation-guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash
# UserPromptSubmit hook — inject fresh-execution reminder on skill invocation.

input=$(cat)
prompt=$(echo "$input" | jq -r '.tool_input.prompt // .tool_input.content // .prompt // .content // empty' 2>/dev/null) || { echo '{}'; exit 0; }
Comment thread
JacobPEvans marked this conversation as resolved.
Outdated

[[ -z "$prompt" ]] && { echo '{}'; exit 0; }

if [[ "$prompt" =~ (^|[[:space:]])/([a-z][a-z0-9-]+) ]]; then
Comment thread
JacobPEvans marked this conversation as resolved.
Outdated
skill_name="${BASH_REMATCH[2]}"
case "$skill_name" in
usr|tmp|etc|var|bin|dev|opt|home|nix|proc|sys|run|lib|mnt|srv|boot) echo '{}'; exit 0 ;;
Comment thread
JacobPEvans marked this conversation as resolved.
Outdated
esac
jq -n --arg skill "$skill_name" '{
systemMessage: ("FRESH EXECUTION: /" + $skill + " — Step 1 now. New invocation, new live state. All prior outputs in this session are stale. Re-run every git, gh, and API command.")
}'
Comment thread
JacobPEvans marked this conversation as resolved.
Outdated
else
echo '{}'
fi

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env bats
# Test suite for skill-guards/scripts/skill-reinvocation-guard.sh
#
# Tests the UserPromptSubmit hook's skill detection and systemMessage injection:
# - No skill in prompt → empty JSON, exit 0
# - /ship → systemMessage with "FRESH EXECUTION" and skill name
# - /finalize-pr 42 → systemMessage with skill name
# - Regular text → empty JSON, exit 0
# - Filesystem path /usr/bin → empty JSON (false positive exclusion)
# - Empty prompt → empty JSON, exit 0
#
# Run with: bats tests/skill-guards/skill-reinvocation-guard/skill-reinvocation-guard.bats

setup() {
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)"
SCRIPT="$REPO_ROOT/skill-guards/scripts/skill-reinvocation-guard.sh"

if [[ ! -f "$SCRIPT" ]]; then
echo "ERROR: Script not found at $SCRIPT" >&2
return 1
Comment thread
JacobPEvans marked this conversation as resolved.
fi
}

# Run the hook with a given prompt string
run_hook_with_prompt() {
local prompt="$1"
run bash -c "echo '{\"tool_input\":{\"prompt\":\"$prompt\"}}' | /bin/bash '$SCRIPT'"
Comment thread
JacobPEvans marked this conversation as resolved.
Outdated
}

# ---------------------------------------------------------------------------
# TC1: No skill invocation → empty JSON, exit 0
# ---------------------------------------------------------------------------

@test "TC1: prompt without skill outputs empty JSON" {
run_hook_with_prompt "help me fix this bug"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}

# ---------------------------------------------------------------------------
# TC2: /ship → systemMessage with FRESH EXECUTION and skill name
# ---------------------------------------------------------------------------

@test "TC2: /ship triggers fresh execution message" {
run_hook_with_prompt "/ship"
[ "$status" -eq 0 ]
[[ "$output" =~ "systemMessage" ]]
[[ "$output" =~ "FRESH EXECUTION" ]]
[[ "$output" =~ "ship" ]]
}

# ---------------------------------------------------------------------------
# TC3: /finalize-pr 42 → systemMessage with skill name
# ---------------------------------------------------------------------------

@test "TC3: /finalize-pr with argument triggers fresh execution message" {
run_hook_with_prompt "/finalize-pr 42"
[ "$status" -eq 0 ]
[[ "$output" =~ "systemMessage" ]]
[[ "$output" =~ "FRESH EXECUTION" ]]
[[ "$output" =~ "finalize-pr" ]]
}

# ---------------------------------------------------------------------------
# TC4: /resolve-pr-threads all → systemMessage with skill name
# ---------------------------------------------------------------------------

@test "TC4: /resolve-pr-threads triggers fresh execution message" {
run_hook_with_prompt "/resolve-pr-threads all"
[ "$status" -eq 0 ]
[[ "$output" =~ "systemMessage" ]]
[[ "$output" =~ "resolve-pr-threads" ]]
}

# ---------------------------------------------------------------------------
# TC5: Regular text without slash → empty JSON
# ---------------------------------------------------------------------------

@test "TC5: text mentioning 'ship' without slash outputs empty JSON" {
run_hook_with_prompt "help me ship this feature"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}

# ---------------------------------------------------------------------------
# TC6: Filesystem path /usr/bin → empty JSON (false positive exclusion)
# ---------------------------------------------------------------------------

@test "TC6: filesystem path /usr excluded as false positive" {
run_hook_with_prompt "check /usr/bin/python"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}

# ---------------------------------------------------------------------------
# TC7: Filesystem path /tmp → empty JSON (false positive exclusion)
# ---------------------------------------------------------------------------

@test "TC7: filesystem path /tmp excluded as false positive" {
run_hook_with_prompt "read /tmp/output.log"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}

# ---------------------------------------------------------------------------
# TC8: Empty/missing prompt → empty JSON, exit 0
# ---------------------------------------------------------------------------

@test "TC8: empty input outputs empty JSON" {
run bash -c "echo '{}' | /bin/bash '$SCRIPT'"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}

# ---------------------------------------------------------------------------
# TC9: Malformed JSON input → empty JSON, exit 0 (fail-open)
# ---------------------------------------------------------------------------

@test "TC9: malformed JSON input outputs empty JSON" {
run bash -c "echo 'not json at all' | /bin/bash '$SCRIPT'"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}

# ---------------------------------------------------------------------------
# TC10: /nix path excluded as false positive
# ---------------------------------------------------------------------------

@test "TC10: filesystem path /nix excluded as false positive" {
run_hook_with_prompt "look at /nix/store/abc123"
[ "$status" -eq 0 ]
[[ "$output" == "{}" ]]
}
Loading