Compare commits

..

1 Commits

Author SHA1 Message Date
Claude
4ffa4f07ff Add plan-auto-show plugin to display plan after updates
When in plan mode, users couldn't see the plan without typing /plan,
but they also couldn't type /plan while being asked follow-up questions.

This plugin adds a SessionStart hook that instructs Claude to
automatically display the plan content after updating the plan file,
showing it before any follow-up questions.

Fixes the UX issue where users need to answer questions about a plan
they can't see.
2025-12-15 17:56:08 +00:00
9 changed files with 85 additions and 86 deletions

View File

@@ -3,7 +3,6 @@
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

View File

@@ -3,7 +3,6 @@
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
@@ -15,7 +14,6 @@
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
@@ -27,7 +25,6 @@
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
@@ -39,7 +36,6 @@
],
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

View File

@@ -3,7 +3,6 @@
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

View File

@@ -0,0 +1,45 @@
# Plan Auto-Show Plugin
Automatically displays the plan content when it's updated in plan mode, eliminating the need to type `/plan` to preview changes.
## Problem
When in plan mode, Claude updates the plan file and may ask follow-up questions. Users can't see the plan without typing `/plan`, but they also can't type `/plan` while being asked questions - they need to respond to the questions first.
## Solution
This plugin instructs Claude to automatically display the plan content in its response whenever it updates the plan file. The plan is shown before any follow-up questions, giving users the context they need to answer.
## Installation
1. Enable the plugin in your Claude Code settings
2. The SessionStart hook will automatically add instructions for plan auto-display
## How It Works
The plugin adds a SessionStart hook that provides additional context to Claude:
- After updating the plan file, Claude will display the full plan content
- The plan is shown in a markdown code block
- The plan appears before any follow-up questions
## Example
Before this plugin:
```
Claude: I've updated the plan file. Would you prefer approach A or B?
User: (Can't see the plan without typing /plan, but needs to answer the question)
```
After this plugin:
```
Claude: I've updated the plan file:
## Current Plan
1. Implement feature X
2. Add tests
3. Deploy
Would you prefer approach A or B?
User: (Can see the plan and answer the question)
```

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Auto-show plan content after updates in plan mode
# This hook adds instructions to automatically display the plan when updated
cat << 'EOF'
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "## Plan Mode Auto-Display\n\nWhen you are in plan mode and you update the plan file:\n\n1. After writing or editing the plan file, ALWAYS display the full plan content in a markdown code block in your response\n2. Use the format:\n ```markdown\n ## Current Plan\n [full plan content here]\n ```\n3. This ensures the user can see the plan without needing to run `/plan`, which is especially important when you're asking follow-up questions\n4. Show the plan BEFORE asking any clarifying questions, so the user has context for their answers\n\nThis improves the user experience by making the plan visible immediately after updates, without requiring separate commands."
}
}
EOF
exit 0

View File

@@ -0,0 +1,15 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
}
]
}
]
}
}

View File

@@ -40,27 +40,7 @@ echo ""
echo "Checking root structure..."
VALID_EVENTS=("PreToolUse" "PostToolUse" "UserPromptSubmit" "Stop" "SubagentStop" "SessionStart" "SessionEnd" "PreCompact" "Notification")
# Detect format: plugin format has { description?, hooks: {...} } wrapper
# Settings format has events directly at root level
is_plugin_format=false
if jq -e '.hooks' "$HOOKS_FILE" >/dev/null 2>&1; then
is_plugin_format=true
HOOKS_PATH=".hooks"
echo "Detected plugin format (with 'hooks' wrapper)"
# Validate allowed root keys for plugin format
for key in $(jq -r 'keys[]' "$HOOKS_FILE"); do
if [ "$key" != "hooks" ] && [ "$key" != "description" ]; then
echo "⚠️ Unknown root key in plugin format: $key (expected: 'hooks', 'description')"
fi
done
else
HOOKS_PATH="."
echo "Detected settings format (events at root)"
fi
# Validate event types
for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
found=false
for valid_event in "${VALID_EVENTS[@]}"; do
if [ "$event" = "$valid_event" ]; then
@@ -82,12 +62,12 @@ echo "Validating individual hooks..."
error_count=0
warning_count=0
for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
hook_count=$(jq -r "$HOOKS_PATH.\"$event\" | length" "$HOOKS_FILE")
for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
hook_count=$(jq -r ".\"$event\" | length" "$HOOKS_FILE")
for ((i=0; i<hook_count; i++)); do
# Check matcher exists
matcher=$(jq -r "$HOOKS_PATH.\"$event\"[$i].matcher // empty" "$HOOKS_FILE")
matcher=$(jq -r ".\"$event\"[$i].matcher // empty" "$HOOKS_FILE")
if [ -z "$matcher" ]; then
echo "$event[$i]: Missing 'matcher' field"
((error_count++))
@@ -95,7 +75,7 @@ for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
fi
# Check hooks array exists
hooks=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks // empty" "$HOOKS_FILE")
hooks=$(jq -r ".\"$event\"[$i].hooks // empty" "$HOOKS_FILE")
if [ -z "$hooks" ] || [ "$hooks" = "null" ]; then
echo "$event[$i]: Missing 'hooks' array"
((error_count++))
@@ -103,10 +83,10 @@ for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
fi
# Validate each hook in the array
hook_array_count=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks | length" "$HOOKS_FILE")
hook_array_count=$(jq -r ".\"$event\"[$i].hooks | length" "$HOOKS_FILE")
for ((j=0; j<hook_array_count; j++)); do
hook_type=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE")
hook_type=$(jq -r ".\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE")
if [ -z "$hook_type" ]; then
echo "$event[$i].hooks[$j]: Missing 'type' field"
@@ -122,7 +102,7 @@ for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
# Check type-specific fields
if [ "$hook_type" = "command" ]; then
command=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE")
command=$(jq -r ".\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE")
if [ -z "$command" ]; then
echo "$event[$i].hooks[$j]: Command hooks must have 'command' field"
((error_count++))
@@ -134,7 +114,7 @@ for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
fi
fi
elif [ "$hook_type" = "prompt" ]; then
prompt=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE")
prompt=$(jq -r ".\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE")
if [ -z "$prompt" ]; then
echo "$event[$i].hooks[$j]: Prompt hooks must have 'prompt' field"
((error_count++))
@@ -148,7 +128,7 @@ for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do
fi
# Check timeout
timeout=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE")
timeout=$(jq -r ".\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE")
if [ -n "$timeout" ] && [ "$timeout" != "null" ]; then
if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then
echo "$event[$i].hooks[$j]: Timeout must be a number"

View File

@@ -3,7 +3,6 @@
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

View File

@@ -1,49 +0,0 @@
#!/bin/bash
# Validate all hooks.json files in the repository
# This script can be run in CI to ensure all plugins have valid hook configurations
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
VALIDATOR="$REPO_ROOT/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh"
echo "🔍 Validating all hooks.json files in the repository..."
echo ""
# Find all hooks.json files
mapfile -t HOOKS_FILES < <(find "$REPO_ROOT/plugins" -name "hooks.json" -type f 2>/dev/null)
if [ ${#HOOKS_FILES[@]} -eq 0 ]; then
echo "No hooks.json files found"
exit 0
fi
echo "Found ${#HOOKS_FILES[@]} hooks.json file(s)"
echo ""
errors=0
for hooks_file in "${HOOKS_FILES[@]}"; do
relative_path="${hooks_file#$REPO_ROOT/}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📄 $relative_path"
echo ""
if bash "$VALIDATOR" "$hooks_file"; then
echo ""
else
echo ""
((errors++))
fi
done
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [ $errors -eq 0 ]; then
echo "✅ All ${#HOOKS_FILES[@]} hooks.json file(s) are valid!"
exit 0
else
echo "$errors hooks.json file(s) have validation errors"
exit 1
fi