Initial commit
This commit is contained in:
14
.claude-plugin/plugin.json
Normal file
14
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "rgw",
|
||||
"description": "Requirement Gathering Workflow - A structured workflow for gathering requirements, generating tasks, and executing them systematically",
|
||||
"version": "1.0.11",
|
||||
"author": {
|
||||
"name": "Byborg"
|
||||
},
|
||||
"commands": [
|
||||
"./commands"
|
||||
],
|
||||
"hooks": [
|
||||
"./hooks"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# rgw
|
||||
|
||||
Requirement Gathering Workflow - A structured workflow for gathering requirements, generating tasks, and executing them systematically
|
||||
12
commands/execute.md
Normal file
12
commands/execute.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Executes the previously created tasklist.
|
||||
|
||||
## Variables
|
||||
- `WorkflowTaskExecution`: `${CLAUDE_PLUGIN_ROOT}/context/workflow/task-execution.md`
|
||||
|
||||
## Workflow
|
||||
- follow the task execution workflow as described in <WorkflowTaskExecution>
|
||||
- tell the user in a clearly visible way, that you understand and are following this workflow, and list all the arguments received
|
||||
- IF an argument was received, read $1 and execute strictly following the execution workflow
|
||||
- IF no arguments were given, list all tasks (`task-XXX.yaml` files, relative to project root), and their statuses.
|
||||
- if there are no tasks found, stop here and suggest the User to plan first.
|
||||
- execute each task sequentially strictly following the execution workflow
|
||||
54
commands/plan.md
Normal file
54
commands/plan.md
Normal file
@@ -0,0 +1,54 @@
|
||||
Based on user defined requirements gather questions to clarify.
|
||||
|
||||
## Variables
|
||||
|
||||
### Requirement Gathering Variables
|
||||
- `WorkflowRequirementGathering`: `${CLAUDE_PLUGIN_ROOT}/context/workflow/requirement-gathering.md`
|
||||
- `CommunicationStandards`: `${CLAUDE_PLUGIN_ROOT}/context/standards/communication-standards.md`
|
||||
- `RequirementsSyntax`: `${CLAUDE_PLUGIN_ROOT}/context/syntaxes/requirements-syntax.md`
|
||||
|
||||
### Task Generation Variables
|
||||
- `WorkflowTaskGeneration`: `${CLAUDE_PLUGIN_ROOT}/context/workflow/task-generation.md`
|
||||
- `TaskSyntax`: `${CLAUDE_PLUGIN_ROOT}/context/syntaxes/task-syntax.md`
|
||||
- `CodingStandards`: `${CLAUDE_PLUGIN_ROOT}/context/standards/coding-standards.md`
|
||||
|
||||
### Replan Variables
|
||||
- `WorkflowReplan`: `${CLAUDE_PLUGIN_ROOT}/context/workflow/replan.md`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase 1: Prerequisites Check
|
||||
- Tell the user in a clearly visible way, that you understand and are following this workflow.
|
||||
- Check if `requirements.yaml` exists in the project root using a simple bash command, do not read it's content yet.
|
||||
- If `requirements.yaml` EXISTS:
|
||||
- Use the AskUserQuestion tool to present these options:
|
||||
- **Option 1: Fresh Plan** - Create a new plan from scratch (will delete existing requirements.yaml and all task-*.yaml files)
|
||||
- **Option 2: Replan** - Update existing requirements by reviewing and adding new aspects (will regenerate all task files based on updated requirements)
|
||||
- Based on user's choice:
|
||||
- If **Fresh Plan**: Delete `requirements.yaml` and all `task-*.yaml` files, then proceed with normal planning
|
||||
- If **Replan**:
|
||||
- Follow the REPLAN workflow as defined in <WorkflowReplan>
|
||||
- If `requirements.yaml` DOES NOT EXIST:
|
||||
- Proceed with normal planning workflow
|
||||
|
||||
### Phase 2: Planning Process
|
||||
- Wait for the User to ask a question or give you an instruction!
|
||||
- If this is a FRESH PLAN or NO EXISTING requirements:
|
||||
- Start by gathering requirements, as defined in <WorkflowRequirementGathering>
|
||||
- Once all requirements are gathered, present a summary to the user
|
||||
- **IMPORTANT**: Request explicit approval from user on the requirements gathered
|
||||
- If user approves, update `requirements.yaml` (in project root) to represent the latest requirement-gathering state
|
||||
- **STOP HERE** - Do NOT proceed to Phase 3 automatically
|
||||
- If this is a REPLAN:
|
||||
- Follow the replan workflow as defined in <WorkflowReplan>
|
||||
- Iterate through existing requirements and gather additional/modified requirements
|
||||
- Once replanning is complete, present the updated requirements to the user
|
||||
- **IMPORTANT**: Request explicit approval from user on the updated requirements
|
||||
- If user approves, delete ALL existing `task-*.yaml` files and update `requirements.yaml` with the enhanced requirements
|
||||
- **STOP HERE** - Do NOT proceed to Phase 3 automatically
|
||||
|
||||
### Phase 3: Task Generation (User Approval Required)
|
||||
- **IMPORTANT**: This phase requires explicit user permission. Ask: "Requirements gathering is complete. Would you like me to proceed with generating the task list?"
|
||||
- Only proceed if the user explicitly approves task generation
|
||||
- Create a list of tasks, as defined in <WorkflowTaskGeneration>
|
||||
- After the task list is generated, your work is done. DO NOT start working on the tasks!
|
||||
36
hooks/hooks.json
Normal file
36
hooks/hooks.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|mcp__serena__create_text_file|mcp__serena__replace_regex|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|mcp__serena__create_text_file|mcp__serena__replace_regex|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
266
hooks/post-tool-use.sh
Executable file
266
hooks/post-tool-use.sh
Executable file
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Load Logger Library
|
||||
# ============================================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib/logger.sh"
|
||||
|
||||
# ============================================================================
|
||||
# PART -1: Check if required commands are installed
|
||||
# ============================================================================
|
||||
|
||||
if ! command -v yq &> /dev/null; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"decision": "block",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "yq is not installed. Install it to enable hook functionality.\n macOS: brew install yq\n Linux: https://github.com/mikefarah/yq#install"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "post-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v node &> /dev/null; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"decision": "block",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "Node.js is not installed. Install it to enable hook functionality.\n macOS: brew install node\n Linux: https://nodejs.org/en/download/package-manager"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "post-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npx &> /dev/null; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"decision": "block",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "npx is not installed. Install it to enable hook functionality.\n npm install -g npx"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "post-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine which JSON command to use (prefer installed json, fallback to npx)
|
||||
if command -v json &> /dev/null; then
|
||||
JSON_CMD="json"
|
||||
else
|
||||
JSON_CMD="npx -y json"
|
||||
fi
|
||||
|
||||
# Read JSON input
|
||||
json=$(cat)
|
||||
|
||||
# Extract file path and tool name
|
||||
# For Write/Edit tools, the file path is in tool_response.filePath
|
||||
# For Serena MCP tools, we need to get it from the tool_input.relative_path
|
||||
file_path=$(echo "$json" | $JSON_CMD tool_response.filePath 2>/dev/null || echo "")
|
||||
if [[ -z "$file_path" ]]; then
|
||||
file_path=$(echo "$json" | $JSON_CMD tool_input.relative_path 2>/dev/null || echo "")
|
||||
fi
|
||||
tool_name=$(echo "$json" | $JSON_CMD tool_name 2>/dev/null || echo "")
|
||||
|
||||
# ============================================================================
|
||||
# PART 0: Track Changed Files in In-Progress Task
|
||||
# ============================================================================
|
||||
|
||||
if [[ -n "$file_path" ]]; then
|
||||
# Skip tracking task yaml files and requirements.yaml
|
||||
filename=$(basename "$file_path")
|
||||
if [[ "$filename" =~ ^task-.*\.ya?ml$ ]] || [[ "$filename" == "requirements.yaml" ]]; then
|
||||
# Don't track these files
|
||||
:
|
||||
else
|
||||
# Find the task file with "status: in progress"
|
||||
in_progress_task=""
|
||||
for task_file in task-*.yaml task-*.yml; do
|
||||
if [[ -f "$task_file" ]]; then
|
||||
# Check if task has "in progress" status using yq
|
||||
task_status=$(yq -r '.status // ""' "$task_file" 2>/dev/null || echo "")
|
||||
# Remove quotes if present
|
||||
if [[ "$task_status" =~ ^[\"\'](.*)[\"\']$ ]]; then
|
||||
task_status="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
if [[ "$task_status" == "in progress" ]]; then
|
||||
in_progress_task="$task_file"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# If we found an in-progress task, add the file to changed_files if not already there
|
||||
if [[ -n "$in_progress_task" ]]; then
|
||||
# Check if file is already in changed_files array
|
||||
file_exists=$(yq -r ".changed_files // [] | map(select(. == \"$file_path\")) | length" "$in_progress_task" 2>/dev/null || echo "0")
|
||||
|
||||
if [[ "$file_exists" == "0" ]]; then
|
||||
# Add file to changed_files array
|
||||
yq -i ".changed_files += [\"$file_path\"]" "$in_progress_task"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# PART 1: Verify Requirements
|
||||
# ============================================================================
|
||||
|
||||
if [[ -n "$file_path" ]] && [[ "$file_path" == *"requirements.yaml" ]]; then
|
||||
if [[ -f "$file_path" ]]; then
|
||||
if yq -e '.complete == true' "$file_path" >/dev/null 2>&1; then
|
||||
# Step 1: Get full Claude output
|
||||
claude_output=$(claude -p "read and execute ${CLAUDE_PLUGIN_ROOT}/context/verify-requirements.md")
|
||||
|
||||
# Step 2: Extract YAML from output, removing any markdown code fences
|
||||
yaml=$(echo "$claude_output" | awk '/^passed:/{flag=1} flag' | sed '/^```/d')
|
||||
|
||||
passed=$(echo "$yaml" | yq -r '.passed')
|
||||
remarks=$(echo "$yaml" | yq -r '.remarks[]?')
|
||||
|
||||
if [[ "$passed" == "false" ]]; then
|
||||
# Use jq to properly construct JSON with escaped content
|
||||
output=$(jq -n \
|
||||
--arg remarks "$remarks" \
|
||||
--arg claude_output "$claude_output" \
|
||||
'{
|
||||
decision: "block",
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PostToolUse",
|
||||
additionalContext: ("Requirements verification failed and needs fixing with the following reasons:\n" + $remarks + "\n\nFull verification output:\n" + $claude_output)
|
||||
}
|
||||
}')
|
||||
log_hook_output "post-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
else
|
||||
# Log successful verification
|
||||
success_output=$(cat <<EOF
|
||||
{
|
||||
"decision": "allow",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "Requirements verification passed successfully"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "post-tool-use" "$success_output"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# PART 2: Verify Task Creation
|
||||
# ============================================================================
|
||||
|
||||
if [[ -n "$file_path" ]] && [[ "$file_path" =~ task-[0-9]+\.yaml$ ]]; then
|
||||
# Only verify when the task file is created (Write or mcp__serena__create_text_file tool used)
|
||||
if [[ "$tool_name" == "Write" || "$tool_name" == "mcp__serena__create_text_file" ]] && [[ -f "$file_path" ]]; then
|
||||
# Step 1: Get full Claude output
|
||||
claude_output=$(claude -p "read task described in $file_path and execute ${CLAUDE_PLUGIN_ROOT}/context/verify-task.md")
|
||||
|
||||
# Step 2: Extract YAML from output, removing any markdown code fences
|
||||
yaml=$(echo "$claude_output" | awk '/^passed:/{flag=1} flag' | sed '/^```/d')
|
||||
|
||||
passed=$(echo "$yaml" | yq -r '.passed')
|
||||
remarks=$(echo "$yaml" | yq -r '.remarks[]?')
|
||||
if [[ "$passed" == "false" ]]; then
|
||||
# Use jq to properly construct JSON with escaped content
|
||||
output=$(jq -n \
|
||||
--arg remarks "$remarks" \
|
||||
--arg claude_output "$claude_output" \
|
||||
'{
|
||||
decision: "block",
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PostToolUse",
|
||||
additionalContext: ("Task verification failed and needs fixing with the following reasons:\n" + $remarks + "\n\nFull verification output:\n" + $claude_output)
|
||||
}
|
||||
}')
|
||||
log_hook_output "post-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
else
|
||||
# Log successful verification
|
||||
task_name=$(basename "$file_path")
|
||||
success_output=$(cat <<EOF
|
||||
{
|
||||
"decision": "allow",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "Task verification passed successfully for $task_name"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "post-tool-use" "$success_output"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# PART 3: Verify Content
|
||||
# ============================================================================
|
||||
|
||||
if [[ -n "$file_path" ]]; then
|
||||
extension="${file_path##*.}"
|
||||
|
||||
if [[ "$extension" == "yaml" || "$extension" == "yml" ]]; then
|
||||
commands=(
|
||||
"yq eval ."
|
||||
)
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for cmd in "${commands[@]}"; do
|
||||
# Use timeout if available, otherwise run directly
|
||||
if command -v timeout &> /dev/null; then
|
||||
if ! timeout 30 $cmd "$file_path" 1>&2; then
|
||||
# Use jq to properly construct JSON with escaped content
|
||||
output=$(jq -n \
|
||||
--arg cmd "$cmd" \
|
||||
--arg file_path "$file_path" \
|
||||
'{
|
||||
decision: "block",
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PostToolUse",
|
||||
additionalContext: ("Command failed: " + $cmd + " for " + $file_path)
|
||||
}
|
||||
}')
|
||||
log_hook_output "post-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# All checks passed - log successful execution with no blocking output
|
||||
log_hook_output "post-tool-use" ""
|
||||
exit 0
|
||||
339
hooks/pre-tool-use.sh
Executable file
339
hooks/pre-tool-use.sh
Executable file
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Load Logger Library
|
||||
# ============================================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib/logger.sh"
|
||||
|
||||
# ============================================================================
|
||||
# PART -1: Check if required commands are installed
|
||||
# ============================================================================
|
||||
|
||||
if ! command -v yq &> /dev/null; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "yq is not installed. Install it to enable hook functionality.\n macOS: brew install yq\n Linux: https://github.com/mikefarah/yq#install"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v node &> /dev/null; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Node.js is not installed. Install it to enable hook functionality.\n macOS: brew install node\n Linux: https://nodejs.org/en/download/package-manager"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npx &> /dev/null; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "npx is not installed. Install it to enable hook functionality.\n npm install -g npx"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine which JSON command to use (prefer installed json, fallback to npx)
|
||||
if command -v json &> /dev/null; then
|
||||
JSON_CMD="json"
|
||||
else
|
||||
JSON_CMD="npx -y json"
|
||||
fi
|
||||
|
||||
# Read JSON input
|
||||
json=$(cat)
|
||||
|
||||
# Extract file path (try both file_path and relative_path for Serena MCP tools)
|
||||
file_path=$(echo "$json" | $JSON_CMD tool_input.file_path 2>/dev/null || echo "")
|
||||
if [[ -z "$file_path" ]]; then
|
||||
file_path=$(echo "$json" | $JSON_CMD tool_input.relative_path 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# PART 2: Verify Task
|
||||
# ============================================================================
|
||||
|
||||
if [[ "$file_path" =~ task-[0-9]+\.yaml$ ]]; then
|
||||
if [[ -f "$file_path" ]]; then
|
||||
# Extract old and new status from the hook JSON
|
||||
old_string=$(echo "$json" | $JSON_CMD tool_input.old_string 2>/dev/null || echo "")
|
||||
new_string=$(echo "$json" | $JSON_CMD tool_input.new_string 2>/dev/null || echo "")
|
||||
|
||||
current_status=""
|
||||
new_status=""
|
||||
old_has_status=false
|
||||
new_has_status=false
|
||||
|
||||
# Parse status from old_string (format: "status: <value>" or "status: \"<value>\"" or "status: '<value>'")
|
||||
# Use grep to extract just the status line, handling multiline strings
|
||||
# Use grep -m 1 to only get the first match in case of multiline content
|
||||
if status_line=$(echo "$old_string" | grep -m 1 -E '^status:[[:space:]]*'); then
|
||||
# Match quoted or unquoted status values (including multi-word statuses)
|
||||
if [[ "$status_line" =~ ^status:[[:space:]]*\"([^\"]+)\" ]]; then
|
||||
current_status="${BASH_REMATCH[1]}"
|
||||
old_has_status=true
|
||||
elif [[ "$status_line" =~ ^status:[[:space:]]*\'([^\']+)\' ]]; then
|
||||
current_status="${BASH_REMATCH[1]}"
|
||||
old_has_status=true
|
||||
elif [[ "$status_line" =~ ^status:[[:space:]]*(.+)$ ]]; then
|
||||
# For unquoted values, capture everything after "status:" until end of line
|
||||
current_status=$(echo "${BASH_REMATCH[1]}" | sed 's/[[:space:]]*$//')
|
||||
old_has_status=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parse status from new_string (format: "status: <value>" or "status: \"<value>\"" or "status: '<value>'")
|
||||
# Use grep to extract just the status line, handling multiline strings
|
||||
# Use grep -m 1 to only get the first match in case of multiline content
|
||||
if status_line=$(echo "$new_string" | grep -m 1 -E '^status:[[:space:]]*'); then
|
||||
# Match quoted or unquoted status values (including multi-word statuses)
|
||||
if [[ "$status_line" =~ ^status:[[:space:]]*\"([^\"]+)\" ]]; then
|
||||
new_status="${BASH_REMATCH[1]}"
|
||||
new_has_status=true
|
||||
elif [[ "$status_line" =~ ^status:[[:space:]]*\'([^\']+)\' ]]; then
|
||||
new_status="${BASH_REMATCH[1]}"
|
||||
new_has_status=true
|
||||
elif [[ "$status_line" =~ ^status:[[:space:]]*(.+)$ ]]; then
|
||||
# For unquoted values, capture everything after "status:" until end of line
|
||||
new_status=$(echo "${BASH_REMATCH[1]}" | sed 's/[[:space:]]*$//')
|
||||
new_has_status=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for status field removal
|
||||
if [[ "$old_has_status" == true && "$new_has_status" == false ]]; then
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Removal of status field is not allowed"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Define valid transitions
|
||||
# Check if transition is valid
|
||||
if [[ -n "$new_status" && "$new_status" != "$current_status" ]]; then
|
||||
valid_transition=false
|
||||
valid_next=()
|
||||
|
||||
case "$current_status" in
|
||||
"")
|
||||
valid_next=("to do")
|
||||
;;
|
||||
"to do")
|
||||
valid_next=("in progress")
|
||||
;;
|
||||
"in progress")
|
||||
valid_next=("under review")
|
||||
;;
|
||||
"under review")
|
||||
valid_next=("done" "in progress")
|
||||
;;
|
||||
"done")
|
||||
valid_next=()
|
||||
;;
|
||||
*)
|
||||
valid_next=()
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check if new_status is in valid_next array
|
||||
for allowed in "${valid_next[@]}"; do
|
||||
[[ "$new_status" == "$allowed" ]] && valid_transition=true && break
|
||||
done
|
||||
|
||||
# Format expected_next for error message
|
||||
expected_next=$(IFS=' or '; echo "${valid_next[*]}")
|
||||
[[ -z "$expected_next" ]] && expected_next="<none>"
|
||||
|
||||
if [[ "$valid_transition" == false ]]; then
|
||||
output=$(cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Invalid status transition: '${current_status:-<empty>}' → '$new_status'\\nExpected: '${current_status:-<empty>}' → '${expected_next}'"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Function to extract status from file, handling quotes
|
||||
extract_file_status() {
|
||||
local file="$1"
|
||||
local raw_status=$(yq -r '.status // ""' "$file")
|
||||
# Remove surrounding quotes if present
|
||||
if [[ "$raw_status" =~ ^[\"\'](.*)[\"\']$ ]]; then
|
||||
echo "${BASH_REMATCH[1]}"
|
||||
else
|
||||
echo "$raw_status"
|
||||
fi
|
||||
}
|
||||
|
||||
# Specific status validations
|
||||
case "$new_status" in
|
||||
"in progress")
|
||||
# Check if any OTHER task is in "under review" status
|
||||
# (Allow setting the same file back to "in progress" from "under review")
|
||||
current_basename=$(basename "$file_path")
|
||||
for task_file in task-*.yaml; do
|
||||
if [[ "$task_file" != "$current_basename" && -f "$task_file" ]]; then
|
||||
other_status=$(extract_file_status "$task_file")
|
||||
if [[ "$other_status" == "under review" ]]; then
|
||||
output=$(cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Cannot set task to 'in progress' while $task_file is 'under review'"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# PART 3: Commit Changed Files When Task is Done
|
||||
# ============================================================================
|
||||
|
||||
if [[ -n "$file_path" ]]; then
|
||||
filename=$(basename "$file_path")
|
||||
|
||||
# Check if this is a task file that will be modified
|
||||
if [[ "$filename" =~ ^task-.*\.ya?ml$ ]] && [[ -f "$file_path" ]]; then
|
||||
# Extract new status from the hook JSON
|
||||
new_string=$(echo "$json" | $JSON_CMD tool_input.new_string 2>/dev/null || echo "")
|
||||
|
||||
# Check if the new status will be "done"
|
||||
new_status=""
|
||||
if status_line=$(echo "$new_string" | grep -m 1 -E '^status:[[:space:]]*'); then
|
||||
# Match quoted or unquoted status values (including multi-word statuses)
|
||||
if [[ "$status_line" =~ ^status:[[:space:]]*\"([^\"]+)\" ]]; then
|
||||
new_status="${BASH_REMATCH[1]}"
|
||||
elif [[ "$status_line" =~ ^status:[[:space:]]*\'([^\']+)\' ]]; then
|
||||
new_status="${BASH_REMATCH[1]}"
|
||||
elif [[ "$status_line" =~ ^status:[[:space:]]*(.+)$ ]]; then
|
||||
# For unquoted values, capture everything after "status:" until end of line
|
||||
new_status=$(echo "${BASH_REMATCH[1]}" | sed 's/[[:space:]]*$//')
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$new_status" == "done" ]]; then
|
||||
# Get the changed files and commit message
|
||||
changed_files_array=()
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && changed_files_array+=("$line")
|
||||
done < <(yq -r '.changed_files[]?' "$file_path" 2>/dev/null || true)
|
||||
|
||||
commit_message=$(yq -r '.commit_message // ""' "$file_path" 2>/dev/null || echo "")
|
||||
|
||||
# Remove quotes from commit message if present
|
||||
if [[ "$commit_message" =~ ^[\"\'](.*)[\"\']$ ]]; then
|
||||
commit_message="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# Only proceed if we have both changed files and a commit message
|
||||
if [[ ${#changed_files_array[@]} -gt 0 ]] && [[ -n "$commit_message" ]]; then
|
||||
# Stage each changed file
|
||||
for file in "${changed_files_array[@]}"; do
|
||||
if [[ -n "$file" ]] && [[ -f "$file" ]]; then
|
||||
if ! git add "$file" 2>&1 >&2; then
|
||||
output=$(cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Failed to stage file: $file"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Create the commit with the task's commit message
|
||||
if ! git diff --cached --quiet 2>/dev/null; then
|
||||
if ! git commit -m "$commit_message" > >(cat >&2) 2>&1; then
|
||||
output=$(cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Failed to commit changes for task $filename"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "pre-tool-use" "$output"
|
||||
check_and_echo_block_reason "$output"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
echo "Committed changes for task $filename"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# All checks passed - log successful execution with no blocking output
|
||||
log_hook_output "pre-tool-use" ""
|
||||
exit 0
|
||||
89
hooks/session-start.sh
Executable file
89
hooks/session-start.sh
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Load Logger Library
|
||||
# ============================================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib/logger.sh"
|
||||
|
||||
# ============================================================================
|
||||
# Session Start Hook - Check Required Dependencies
|
||||
# ============================================================================
|
||||
# This hook runs at the start of each Claude Code session to verify that
|
||||
# all required dependencies are installed for the rgw workflow hooks.
|
||||
# ============================================================================
|
||||
|
||||
missing_deps=()
|
||||
|
||||
# Check for yq (YAML processor)
|
||||
if ! command -v yq &> /dev/null; then
|
||||
missing_deps+=("yq")
|
||||
fi
|
||||
|
||||
# Check for node (required for json npm package)
|
||||
if ! command -v node &> /dev/null; then
|
||||
missing_deps+=("node")
|
||||
fi
|
||||
|
||||
# Check for npx (required to run json package)
|
||||
if ! command -v npx &> /dev/null; then
|
||||
missing_deps+=("npx")
|
||||
fi
|
||||
|
||||
# If there are missing dependencies, display installation instructions
|
||||
if [ ${#missing_deps[@]} -gt 0 ]; then
|
||||
message="⚠️ Missing required dependencies for rgw workflow hooks:\\n\\n"
|
||||
|
||||
for dep in "${missing_deps[@]}"; do
|
||||
case "$dep" in
|
||||
yq)
|
||||
message+=" 📦 yq (YAML processor)\\n"
|
||||
message+=" macOS: brew install yq\\n"
|
||||
message+=" Linux: https://github.com/mikefarah/yq#install\\n"
|
||||
message+="\\n"
|
||||
;;
|
||||
node)
|
||||
message+=" 📦 Node.js (JavaScript runtime)\\n"
|
||||
message+=" macOS: brew install node\\n"
|
||||
message+=" Linux: https://nodejs.org/en/download/package-manager\\n"
|
||||
message+="\\n"
|
||||
;;
|
||||
npx)
|
||||
message+=" 📦 npx (npm package runner)\\n"
|
||||
message+=" Usually installed with Node.js\\n"
|
||||
message+=" If missing: npm install -g npx\\n"
|
||||
message+="\\n"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
message+=" ℹ️ Install the missing dependencies to enable full hook functionality."
|
||||
|
||||
output=$(cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "$message"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "session-start" "$output"
|
||||
echo "$output"
|
||||
else
|
||||
output=$(cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "✅ rgw workflow hooks: All required dependencies are installed."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
log_hook_output "session-start" "$output"
|
||||
echo "$output"
|
||||
fi
|
||||
|
||||
# Explicitly flush output
|
||||
exit 0
|
||||
69
plugin.lock.json
Normal file
69
plugin.lock.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:ByborgAI/prompt-collection:rgw",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "a2ed9ffbdc7a3ed5b9dbca62cd6159bc524ce51a",
|
||||
"treeHash": "4fe3fc270827af47da839b413aa2f99c11549284d5c14a7423426d2802170409",
|
||||
"generatedAt": "2025-11-28T10:09:59.286424Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "rgw",
|
||||
"description": "Requirement Gathering Workflow - A structured workflow for gathering requirements, generating tasks, and executing them systematically",
|
||||
"version": "1.0.11"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "76493963f621dee14a4bd9a3cd0ec18b0e5d223eccd3a854f463f419522d1b8a"
|
||||
},
|
||||
{
|
||||
"path": "hooks/pre-tool-use.sh",
|
||||
"sha256": "d2165064d3d0a05f57c2b058821e7c0aa77fe43d4f1027e8378526ce60707f10"
|
||||
},
|
||||
{
|
||||
"path": "hooks/session-start.sh",
|
||||
"sha256": "aa610bc3ef7072ce88273f1264d65eef8d7afe07ebd7a077d63202620ee67234"
|
||||
},
|
||||
{
|
||||
"path": "hooks/hooks.json",
|
||||
"sha256": "7177d751969d3b2dac91536df9d514f997af3b532012ae4dfd877b4682b98e66"
|
||||
},
|
||||
{
|
||||
"path": "hooks/post-tool-use.sh",
|
||||
"sha256": "0a0157aabd6f0008b6a1cf8e9235904cc0ad27ca86402cbfdb9dc2126caeb0ab"
|
||||
},
|
||||
{
|
||||
"path": "hooks/lib/logger.sh",
|
||||
"sha256": "07684903eb96769ef76bc634ea5f5942ff5daa5ddb8dcdcef5fd8e8af5a985b9"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "e074fbe06712aac37830e4eb19ec8540cb0400bec938abde3c5f50b8e36c1831"
|
||||
},
|
||||
{
|
||||
"path": "commands/plan.md",
|
||||
"sha256": "9c2b169dd2445dd21ffc07706fbcd95ba3ff803fc074721cac868919574e70ae"
|
||||
},
|
||||
{
|
||||
"path": "commands/execute.md",
|
||||
"sha256": "b0c17fdd6044f88da841fcaae4c9519d7b5a5c4fa74c4e116f3f5a146eb06f2b"
|
||||
}
|
||||
],
|
||||
"dirSha256": "4fe3fc270827af47da839b413aa2f99c11549284d5c14a7423426d2802170409"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user