centralize guides and rails under mosaic with runtime compatibility links

This commit is contained in:
Jason Woltje
2026-02-17 11:39:52 -06:00
parent a1c2efef1c
commit 4eac2c76e6
85 changed files with 10785 additions and 0 deletions

15
rails/qa/debug-hook.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Debug hook to identify available variables
echo "=== Hook Debug ===" >> /tmp/hook-debug.log
echo "Date: $(date)" >> /tmp/hook-debug.log
echo "All args: $@" >> /tmp/hook-debug.log
echo "Arg count: $#" >> /tmp/hook-debug.log
echo "Arg 1: ${1:-EMPTY}" >> /tmp/hook-debug.log
echo "Arg 2: ${2:-EMPTY}" >> /tmp/hook-debug.log
echo "Arg 3: ${3:-EMPTY}" >> /tmp/hook-debug.log
echo "Environment:" >> /tmp/hook-debug.log
env | grep -i file >> /tmp/hook-debug.log 2>/dev/null || true
env | grep -i path >> /tmp/hook-debug.log 2>/dev/null || true
env | grep -i tool >> /tmp/hook-debug.log 2>/dev/null || true
echo "==================" >> /tmp/hook-debug.log

197
rails/qa/qa-hook-handler.sh Executable file
View File

@@ -0,0 +1,197 @@
#!/bin/bash
# Universal QA hook handler with robust error handling
# Location: ~/.mosaic/rails/qa-hook-handler.sh
# Don't exit on unset variables initially to handle missing params gracefully
set -eo pipefail
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
TOOL_NAME="${1:-}"
FILE_PATH="${2:-}"
# Debug logging
echo "[DEBUG] Script called with args: \$1='$1' \$2='$2'" >> "$PROJECT_ROOT/logs/qa-automation.log" 2>/dev/null || true
# Validate inputs
if [ -z "$FILE_PATH" ] || [ -z "$TOOL_NAME" ]; then
echo "[ERROR] Missing required parameters: tool='$TOOL_NAME' file='$FILE_PATH'" >&2
echo "[ERROR] Usage: $0 <tool> <file_path>" >&2
# Log to file if possible
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] Missing parameters - tool='$TOOL_NAME' file='$FILE_PATH'" >> "$PROJECT_ROOT/logs/qa-automation.log" 2>/dev/null || true
exit 1
fi
# Now enable strict mode after parameter handling
set -u
# Skip non-JS/TS files
if ! [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx|mjs|cjs)$ ]]; then
echo "[INFO] Skipping non-JS/TS file: $FILE_PATH"
exit 0
fi
# Generate naming components
TIMESTAMP=$(date '+%Y%m%d-%H%M')
SANITIZED_NAME=$(echo "$FILE_PATH" | sed 's/\//-/g' | sed 's/^-//' | sed 's/\.\./\./g')
ITERATION=1
# Log file for debugging
LOG_FILE="$PROJECT_ROOT/logs/qa-automation.log"
mkdir -p "$(dirname "$LOG_FILE")"
# Function to detect Epic with fallback
detect_epic() {
local file_path="$1"
local epic=""
# Try to detect Epic from path patterns
case "$file_path" in
*/apps/frontend/src/components/*adapter*|*/apps/frontend/src/views/*adapter*)
epic="E.3001-ADAPTER-CONFIG-SYSTEM"
;;
*/services/backend/src/adapters/*)
epic="E.3001-ADAPTER-CONFIG-SYSTEM"
;;
*/services/backend/src/*)
epic="E.2004-enterprise-data-synchronization-engine"
;;
*/services/syncagent-debezium/*|*/services/syncagent-n8n/*)
epic="E.2004-enterprise-data-synchronization-engine"
;;
*)
epic="" # General QA
;;
esac
echo "$epic"
}
# Detect Epic association
EPIC_FOLDER=$(detect_epic "$FILE_PATH")
# Function to setup report directory with creation if needed
setup_report_dir() {
local epic="$1"
local project_root="$2"
local report_dir=""
if [ -n "$epic" ]; then
# Check if Epic directory exists
local epic_dir="$project_root/docs/task-management/epics/active/$epic"
if [ -d "$epic_dir" ]; then
# Epic exists, use it
report_dir="$epic_dir/reports/qa-automation/pending"
echo "[INFO] Using existing Epic: $epic" | tee -a "$LOG_FILE"
else
# Epic doesn't exist, check if we should create it
local epic_parent="$project_root/docs/task-management/epics/active"
if [ -d "$epic_parent" ]; then
# Parent exists, create Epic structure
echo "[WARN] Epic $epic not found, creating structure..." | tee -a "$LOG_FILE"
mkdir -p "$epic_dir/reports/qa-automation/pending"
mkdir -p "$epic_dir/reports/qa-automation/in-progress"
mkdir -p "$epic_dir/reports/qa-automation/done"
mkdir -p "$epic_dir/reports/qa-automation/escalated"
# Create Epic README
cat > "$epic_dir/README.md" << EOF
# Epic: $epic
**Status**: Active
**Created**: $(date '+%Y-%m-%d')
**Purpose**: Auto-created by QA automation system
## Description
This Epic was automatically created to organize QA remediation reports.
## QA Automation
- Reports are stored in \`reports/qa-automation/\`
- Pending issues: \`reports/qa-automation/pending/\`
- Escalated issues: \`reports/qa-automation/escalated/\`
EOF
report_dir="$epic_dir/reports/qa-automation/pending"
echo "[INFO] Created Epic structure: $epic" | tee -a "$LOG_FILE"
else
# Epic structure doesn't exist, fall back to general
echo "[WARN] Epic structure not found, using general QA" | tee -a "$LOG_FILE"
report_dir="$project_root/docs/reports/qa-automation/pending"
fi
fi
else
# No Epic association, use general
report_dir="$project_root/docs/reports/qa-automation/pending"
echo "[INFO] No Epic association, using general QA" | tee -a "$LOG_FILE"
fi
# Ensure directory exists
mkdir -p "$report_dir"
echo "$report_dir"
}
# Setup report directory (capture only the last line which is the path)
REPORT_DIR=$(setup_report_dir "$EPIC_FOLDER" "$PROJECT_ROOT" | tail -1)
# Check for existing reports from same timestamp
check_existing_iteration() {
local dir="$1"
local name="$2"
local timestamp="$3"
local max_iter=0
for file in "$dir"/${name}_${timestamp}_*_remediation_needed.md; do
if [ -f "$file" ]; then
# Extract iteration number
local iter=$(echo "$file" | sed 's/.*_\([0-9]\+\)_remediation_needed\.md$/\1/')
if [ "$iter" -gt "$max_iter" ]; then
max_iter=$iter
fi
fi
done
echo $((max_iter + 1))
}
ITERATION=$(check_existing_iteration "$REPORT_DIR" "$SANITIZED_NAME" "$TIMESTAMP")
# Check if we're at max iterations
if [ "$ITERATION" -gt 5 ]; then
echo "[ERROR] Max iterations (5) reached for $FILE_PATH" | tee -a "$LOG_FILE"
# Move to escalated immediately
REPORT_DIR="${REPORT_DIR/pending/escalated}"
mkdir -p "$REPORT_DIR"
ITERATION=5 # Cap at 5
fi
# Create report filename
REPORT_FILE="${SANITIZED_NAME}_${TIMESTAMP}_${ITERATION}_remediation_needed.md"
REPORT_PATH="$REPORT_DIR/$REPORT_FILE"
# Log the action
echo "[$(date '+%Y-%m-%d %H:%M:%S')] QA Hook: $TOOL_NAME on $FILE_PATH" | tee -a "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating report: $REPORT_PATH" | tee -a "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Epic: ${EPIC_FOLDER:-general}, Iteration: $ITERATION" | tee -a "$LOG_FILE"
# Create a task file for the QA agent instead of calling Claude directly
cat > "$REPORT_PATH" << EOF
# QA Remediation Report
**File:** $FILE_PATH
**Tool Used:** $TOOL_NAME
**Epic:** ${EPIC_FOLDER:-general}
**Iteration:** $ITERATION
**Generated:** $(date '+%Y-%m-%d %H:%M:%S')
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
\`\`\`bash
claude -p "Use Task tool to launch universal-qa-agent for report: $REPORT_PATH"
\`\`\`
EOF
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Created report template at: $REPORT_PATH" | tee -a "$LOG_FILE"

59
rails/qa/qa-hook-stdin.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# QA Hook handler that reads from stdin
# Location: ~/.mosaic/rails/qa-hook-stdin.sh
set -eo pipefail
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
LOG_FILE="$PROJECT_ROOT/logs/qa-automation.log"
mkdir -p "$(dirname "$LOG_FILE")"
# Read JSON from stdin
JSON_INPUT=$(cat)
# Log raw input for debugging
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Hook triggered with JSON:" >> "$LOG_FILE"
echo "$JSON_INPUT" >> "$LOG_FILE"
# Extract file path using jq if available, otherwise use grep/sed
if command -v jq &> /dev/null; then
# Try multiple paths - tool_input.file_path is the actual structure from Claude Code
FILE_PATH=$(echo "$JSON_INPUT" | jq -r '.tool_input.file_path // .tool_response.filePath // .file_path // .path // .file // empty' 2>/dev/null || echo "")
TOOL_NAME=$(echo "$JSON_INPUT" | jq -r '.tool_name // .tool // .matcher // "Edit"' 2>/dev/null || echo "Edit")
else
# Fallback parsing without jq - search in tool_input first
FILE_PATH=$(echo "$JSON_INPUT" | grep -o '"tool_input"[^}]*}' | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | head -1)
if [ -z "$FILE_PATH" ]; then
FILE_PATH=$(echo "$JSON_INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | head -1)
fi
if [ -z "$FILE_PATH" ]; then
FILE_PATH=$(echo "$JSON_INPUT" | grep -o '"filePath"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"filePath"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | head -1)
fi
TOOL_NAME=$(echo "$JSON_INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | head -1)
if [ -z "$TOOL_NAME" ]; then
TOOL_NAME=$(echo "$JSON_INPUT" | grep -o '"tool"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"tool"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | head -1)
fi
[ -z "$TOOL_NAME" ] && TOOL_NAME="Edit"
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Extracted: tool=$TOOL_NAME file=$FILE_PATH" >> "$LOG_FILE"
# Validate we got a file path
if [ -z "$FILE_PATH" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] Could not extract file path from JSON" >> "$LOG_FILE"
exit 0 # Exit successfully to not block Claude
fi
# Skip non-JS/TS files
if ! [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx|mjs|cjs)$ ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Skipping non-JS/TS file: $FILE_PATH" >> "$LOG_FILE"
exit 0
fi
# Call the main QA handler with extracted parameters
if [ -f ~/.mosaic/rails/qa-hook-handler.sh ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Calling QA handler for $FILE_PATH" >> "$LOG_FILE"
~/.mosaic/rails/qa-hook-handler.sh "$TOOL_NAME" "$FILE_PATH" 2>&1 | tee -a "$LOG_FILE"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] QA handler script not found" >> "$LOG_FILE"
fi

19
rails/qa/qa-hook-wrapper.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Wrapper script that handles hook invocation more robustly
# Get the most recently modified JS/TS file as a fallback
RECENT_FILE=$(find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) -mmin -1 2>/dev/null | head -1)
# Use provided file path or fallback to recent file
FILE_PATH="${2:-$RECENT_FILE}"
TOOL_NAME="${1:-Edit}"
# Log the attempt
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Hook wrapper called: tool=$TOOL_NAME file=$FILE_PATH" >> logs/qa-automation.log 2>/dev/null || true
# Call the actual QA handler if we have a file
if [ -n "$FILE_PATH" ]; then
~/.mosaic/rails/qa-hook-handler.sh "$TOOL_NAME" "$FILE_PATH"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] No file path available for QA check" >> logs/qa-automation.log 2>/dev/null || true
fi

91
rails/qa/qa-queue-monitor.sh Executable file
View File

@@ -0,0 +1,91 @@
#!/bin/bash
# Monitor QA queues with graceful handling of missing directories
# Location: ~/.mosaic/rails/qa-queue-monitor.sh
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
echo "=== QA Automation Queue Status ==="
echo "Project: $(basename "$PROJECT_ROOT")"
echo "Time: $(date '+%Y-%m-%d %H:%M:%S')"
echo
# Function to count files safely
count_files() {
local dir="$1"
if [ -d "$dir" ]; then
ls "$dir" 2>/dev/null | wc -l
else
echo "0"
fi
}
# Check Epic-specific queues
EPIC_BASE="$PROJECT_ROOT/docs/task-management/epics/active"
if [ -d "$EPIC_BASE" ]; then
for EPIC_DIR in "$EPIC_BASE"/*/; do
if [ -d "$EPIC_DIR" ]; then
EPIC_NAME=$(basename "$EPIC_DIR")
QA_DIR="$EPIC_DIR/reports/qa-automation"
if [ -d "$QA_DIR" ]; then
echo "Epic: $EPIC_NAME"
echo " Pending: $(count_files "$QA_DIR/pending")"
echo " In Progress: $(count_files "$QA_DIR/in-progress")"
echo " Done: $(count_files "$QA_DIR/done")"
echo " Escalated: $(count_files "$QA_DIR/escalated")"
# Show escalated files if any
if [ -d "$QA_DIR/escalated" ] && [ "$(ls "$QA_DIR/escalated" 2>/dev/null | wc -l)" -gt 0 ]; then
echo " ⚠️ Escalated Issues:"
for file in "$QA_DIR/escalated"/*_remediation_needed.md; do
if [ -f "$file" ]; then
echo " - $(basename "$file")"
fi
done
fi
echo
fi
fi
done
else
echo "[WARN] No Epic structure found at: $EPIC_BASE"
echo
fi
# Check general queue
GENERAL_DIR="$PROJECT_ROOT/docs/reports/qa-automation"
if [ -d "$GENERAL_DIR" ]; then
echo "General (Non-Epic):"
echo " Pending: $(count_files "$GENERAL_DIR/pending")"
echo " In Progress: $(count_files "$GENERAL_DIR/in-progress")"
echo " Done: $(count_files "$GENERAL_DIR/done")"
echo " Escalated: $(count_files "$GENERAL_DIR/escalated")"
# Show escalated files
if [ -d "$GENERAL_DIR/escalated" ] && [ "$(ls "$GENERAL_DIR/escalated" 2>/dev/null | wc -l)" -gt 0 ]; then
echo " ⚠️ Escalated Issues:"
for file in "$GENERAL_DIR/escalated"/*_remediation_needed.md; do
if [ -f "$file" ]; then
echo " - $(basename "$file")"
fi
done
fi
else
echo "[INFO] No general QA directory found (will be created on first use)"
fi
echo
echo "=== Recent Activity ==="
# Show last 5 log entries
if [ -f "$PROJECT_ROOT/logs/qa-automation.log" ]; then
tail -5 "$PROJECT_ROOT/logs/qa-automation.log"
else
echo "No activity log found"
fi
echo
echo "=== Queue Processing Tips ==="
echo "• View pending reports: ls -la $PROJECT_ROOT/docs/reports/qa-automation/pending/"
echo "• Check stale reports: find $PROJECT_ROOT -path '*/in-progress/*' -mmin +60"
echo "• Manual escalation: mv {report} {path}/escalated/"
echo "• View full log: tail -f $PROJECT_ROOT/logs/qa-automation.log"

View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Universal remediation hook handler with error recovery
# Location: ~/.mosaic/rails/remediation-hook-handler.sh
set -euo pipefail
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
REPORT_FILE="${1:-}"
# Validate input
if [ -z "$REPORT_FILE" ] || [ ! -f "$REPORT_FILE" ]; then
echo "[ERROR] Invalid or missing report file: $REPORT_FILE" >&2
exit 1
fi
LOG_FILE="$PROJECT_ROOT/logs/qa-automation.log"
mkdir -p "$(dirname "$LOG_FILE")"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Remediation triggered for: $REPORT_FILE" | tee -a "$LOG_FILE"
# Extract components from path and filename
BASE_NAME=$(basename "$REPORT_FILE" _remediation_needed.md)
DIR_PATH=$(dirname "$REPORT_FILE")
# Validate directory structure
if [[ ! "$DIR_PATH" =~ /pending$ ]]; then
echo "[ERROR] Report not in pending directory: $DIR_PATH" | tee -a "$LOG_FILE"
exit 1
fi
# Setup in-progress directory
IN_PROGRESS_DIR="${DIR_PATH/pending/in-progress}"
# Handle missing in-progress directory
if [ ! -d "$IN_PROGRESS_DIR" ]; then
echo "[WARN] Creating missing in-progress directory: $IN_PROGRESS_DIR" | tee -a "$LOG_FILE"
mkdir -p "$IN_PROGRESS_DIR"
# Also ensure done and escalated exist
mkdir -p "${DIR_PATH/pending/done}"
mkdir -p "${DIR_PATH/pending/escalated}"
fi
# Move from pending to in-progress (with error handling)
if ! mv "$REPORT_FILE" "$IN_PROGRESS_DIR/" 2>/dev/null; then
echo "[ERROR] Failed to move report to in-progress" | tee -a "$LOG_FILE"
# Check if already in progress
if [ -f "$IN_PROGRESS_DIR/$(basename "$REPORT_FILE")" ]; then
echo "[WARN] Report already in progress, skipping" | tee -a "$LOG_FILE"
exit 0
fi
exit 1
fi
# Create actions file
ACTIONS_FILE="${BASE_NAME}_remediation_actions.md"
ACTIONS_PATH="$IN_PROGRESS_DIR/$ACTIONS_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting remediation: $ACTIONS_PATH" | tee -a "$LOG_FILE"
# Trigger remediation agent
claude -p "Use Task tool to launch auto-remediation-agent for:
- Remediation Report: $IN_PROGRESS_DIR/$(basename "$REPORT_FILE")
- Actions File: $ACTIONS_PATH
- Max Iterations: 5
Process the report, create action plan using Sequential Thinking, research with Context7, and execute fixes systematically." 2>&1 | tee -a "$LOG_FILE"