Files
gh-jtsylve-claude-experimen…/agents/scripts/template-selector-handler.sh
2025-11-30 08:29:39 +08:00

443 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
# Purpose: State machine for template-selector agent with integrated classification
# Inputs: XML input via stdin containing user_task, suggested_template (optional), confidence (optional)
# Outputs: Instructions with classification result
# Architecture: Incorporates template-selector.sh logic directly (no external bash calls needed)
set -euo pipefail
# Source common functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$SCRIPT_DIR/../../scripts/common.sh"
# Setup plugin root
setup_plugin_root
TEMPLATE_DIR="${CLAUDE_PLUGIN_ROOT}/templates"
# ============================================================================
# Confidence Threshold Configuration
# ============================================================================
#
# These values control template classification confidence scoring:
#
# BORDERLINE_MIN (60%):
# - Minimum confidence for accepting keyword-based classification
# - Below this: routes to LLM for borderline case review
# - Rationale: Ensures 90%+ accuracy by letting LLM handle uncertain cases
#
# STRONG_INDICATOR_BASE (75%):
# - Base confidence when a strong indicator keyword is found
# - Examples: "refactor", "compare", "test", "review"
# - Rationale: Strong indicators reliably predict template choice
#
# SUPPORTING_KEYWORD_BONUS (8% per keyword):
# - Additional confidence for each supporting keyword (max 100%)
# - Examples: "code", "module", "security", etc.
# - Rationale: Multiple related keywords increase confidence
#
# Confidence levels for supporting keywords only (no strong indicator):
# - ONE_KEYWORD_CONFIDENCE (35%): One supporting keyword found
# - TWO_KEYWORD_CONFIDENCE (60%): Two supporting keywords found
# - THREE_KEYWORD_CONFIDENCE (75%): Three+ supporting keywords found
#
# Example scoring:
# "Refactor the authentication module" = 75% (strong) + 8% (code keyword) = 83%
# "Check security of code" = 60% (two keywords: security, code)
# ============================================================================
BORDERLINE_MIN=60
STRONG_INDICATOR_BASE=75
SUPPORTING_KEYWORD_BONUS=8
ONE_KEYWORD_CONFIDENCE=35
TWO_KEYWORD_CONFIDENCE=60
THREE_KEYWORD_CONFIDENCE=75
# Regex patterns for strong indicators
PATTERN_CODE="refactor|codebase|implement|fix|update|modify|create|build|reorganize|improve|enhance|transform|optimize|rework|revamp|refine|polish|modernize|clean|streamline"
PATTERN_COMPARISON="compare|classify|check|same|different|verify|determine|match|matches|equivalent|equals?|similar|duplicate|identical"
PATTERN_TEST="tests?|spec|testing|unittest"
PATTERN_REVIEW="review|feedback|critique|analyze|assess|evaluate|examine|inspect|scrutinize|audit|scan|survey|vet|investigate|appraise"
PATTERN_DOCUMENTATION="documentation|readme|docstring|docs|document|write.*(comment|docstring|documentation|guide|instruction)|author.*(documentation|instruction|guide)"
PATTERN_EXTRACTION="extract|parse|pull|retrieve|mine|harvest|collect|scrape|distill|gather|isolate|obtain|sift|fish|pluck|glean|cull|unearth|dredge|winnow"
# Parse XML input from command-line argument (supports multiline content)
read_xml_input() {
local xml_input="$1"
# Validate input provided
if [ -z "$xml_input" ]; then
echo "Error: No input provided. Usage: $0 '<xml-input>'" >&2
exit 1
fi
# Extract required field using common function
local user_task
user_task=$(require_xml_field "$xml_input" "user_task" "auto") || exit 1
# Extract optional fields with defaults
local suggested_template
suggested_template=$(optional_xml_field "$xml_input" "suggested_template" "")
local confidence
confidence=$(optional_xml_field "$xml_input" "confidence" "")
# Validate confidence is a number if provided
if [ -n "$confidence" ] && ! [[ "$confidence" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid confidence value: $confidence (must be integer)" >&2
exit 1
fi
# Export for use by other functions
export USER_TASK="$user_task"
export SUGGESTED_TEMPLATE="$suggested_template"
export SUGGESTED_CONFIDENCE="$confidence"
}
# Convert text to lowercase
to_lowercase() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}
# Count keyword matches
count_matches() {
local text="$1"
shift
local IFS='|'
local pattern="$*"
echo "$text" | grep -oiE "\b($pattern)\b" 2>/dev/null | wc -l | tr -d ' '
}
# Pre-compute strong indicators
# Optimized to run grep once and check all patterns
compute_strong_indicators() {
local text="$1"
# Combine all patterns into a single grep call
local all_matches
all_matches=$(echo "$text" | grep -oiE "\b($PATTERN_CODE|$PATTERN_COMPARISON|$PATTERN_TEST|$PATTERN_REVIEW|$PATTERN_DOCUMENTATION|$PATTERN_EXTRACTION)\b" | tr '[:upper:]' '[:lower:]') || true
# Check which patterns matched
if echo "$all_matches" | grep -qiE "\b($PATTERN_CODE)\b"; then
HAS_STRONG_CODE=1
else
HAS_STRONG_CODE=0
fi
if echo "$all_matches" | grep -qiE "\b($PATTERN_COMPARISON)\b"; then
HAS_STRONG_COMPARISON=1
else
HAS_STRONG_COMPARISON=0
fi
if echo "$all_matches" | grep -qiE "\b($PATTERN_TEST)\b"; then
HAS_STRONG_TEST=1
else
HAS_STRONG_TEST=0
fi
if echo "$all_matches" | grep -qiE "\b($PATTERN_REVIEW)\b"; then
HAS_STRONG_REVIEW=1
else
HAS_STRONG_REVIEW=0
fi
if echo "$all_matches" | grep -qiE "\b($PATTERN_DOCUMENTATION)\b"; then
HAS_STRONG_DOCUMENTATION=1
else
HAS_STRONG_DOCUMENTATION=0
fi
if echo "$all_matches" | grep -qiE "\b($PATTERN_EXTRACTION)\b"; then
HAS_STRONG_EXTRACTION=1
else
HAS_STRONG_EXTRACTION=0
fi
}
# Check if category has strong indicator
has_strong_indicator() {
local category="$1"
case "$category" in
"code")
return $((1 - HAS_STRONG_CODE))
;;
"comparison")
return $((1 - HAS_STRONG_COMPARISON))
;;
"test")
return $((1 - HAS_STRONG_TEST))
;;
"review")
return $((1 - HAS_STRONG_REVIEW))
;;
"documentation")
return $((1 - HAS_STRONG_DOCUMENTATION))
;;
"extraction")
return $((1 - HAS_STRONG_EXTRACTION))
;;
*)
return 1
;;
esac
}
# Calculate confidence for a category
calculate_confidence() {
local category=$1
local supporting_count=$2
if has_strong_indicator "$category"; then
local confidence=$((STRONG_INDICATOR_BASE + supporting_count * SUPPORTING_KEYWORD_BONUS))
[ "$confidence" -gt 100 ] && confidence=100
echo "$confidence"
else
if [ "$supporting_count" -eq 0 ]; then
echo 0
elif [ "$supporting_count" -eq 1 ]; then
echo "$ONE_KEYWORD_CONFIDENCE"
elif [ "$supporting_count" -eq 2 ]; then
echo "$TWO_KEYWORD_CONFIDENCE"
else
echo "$THREE_KEYWORD_CONFIDENCE"
fi
fi
}
# Classify task and return template name with confidence
classify_task() {
local task_description="$1"
local task_lower=$(to_lowercase "$task_description")
# Pre-compute strong indicators
compute_strong_indicators "$task_lower"
# Define supporting keywords
local code_keywords=("code" "file" "class" "bug" "module" "system" "endpoint")
local comparison_keywords=("sentence" "equal" "whether" "mean" "version" "duplicate" "similarity")
local test_keywords=("coverage" "jest" "pytest" "junit" "mocha" "case" "suite" "edge" "unit" "generate")
local review_keywords=("readability" "maintainability" "practices" "smell")
local documentation_keywords=("comment" "guide" "reference" "explain" "api" "write" "function" "inline" "author" "instructions" "setup" "method")
local extraction_keywords=("data" "scrape" "retrieve" "json" "html" "csv" "email" "address" "timestamp" "logs" "file")
# Count matches
local code_count=$(count_matches "$task_lower" "${code_keywords[@]}")
local comparison_count=$(count_matches "$task_lower" "${comparison_keywords[@]}")
local testgen_count=$(count_matches "$task_lower" "${test_keywords[@]}")
local review_count=$(count_matches "$task_lower" "${review_keywords[@]}")
local documentation_count=$(count_matches "$task_lower" "${documentation_keywords[@]}")
local extraction_count=$(count_matches "$task_lower" "${extraction_keywords[@]}")
# Calculate confidence scores
local code_confidence=$(calculate_confidence "code" $code_count)
local comparison_confidence=$(calculate_confidence "comparison" $comparison_count)
local test_confidence=$(calculate_confidence "test" $testgen_count)
local review_confidence=$(calculate_confidence "review" $review_count)
local documentation_confidence=$(calculate_confidence "documentation" $documentation_count)
local extraction_confidence=$(calculate_confidence "extraction" $extraction_count)
# Find highest confidence
local max_confidence=0
local selected_template="custom"
if [ "$code_confidence" -gt "$max_confidence" ]; then
max_confidence=$code_confidence
selected_template="code-refactoring"
fi
if [ "$comparison_confidence" -gt "$max_confidence" ]; then
max_confidence=$comparison_confidence
selected_template="code-comparison"
fi
if [ "$test_confidence" -gt "$max_confidence" ]; then
max_confidence=$test_confidence
selected_template="test-generation"
fi
if [ "$review_confidence" -gt "$max_confidence" ]; then
max_confidence=$review_confidence
selected_template="code-review"
fi
if [ "$documentation_confidence" -gt "$max_confidence" ]; then
max_confidence=$documentation_confidence
selected_template="documentation-generator"
fi
if [ "$extraction_confidence" -gt "$max_confidence" ]; then
max_confidence=$extraction_confidence
selected_template="data-extraction"
fi
# Set to custom if below borderline threshold
if [ "$max_confidence" -lt "$BORDERLINE_MIN" ]; then
selected_template="custom"
max_confidence=0
fi
# Validate template file exists
if [ "$selected_template" != "custom" ]; then
local template_file="${TEMPLATE_DIR}/${selected_template}.md"
if [ ! -f "$template_file" ]; then
selected_template="custom"
max_confidence=0
fi
fi
# Output: template_name confidence
echo "$selected_template $max_confidence"
}
# Determine scenario based on confidence level
get_scenario() {
local confidence=$1
if [ "$confidence" -ge 70 ]; then
echo "high"
elif [ "$confidence" -ge 60 ]; then
echo "borderline"
else
echo "weak"
fi
}
# Generate instructions based on scenario
generate_instructions() {
# Run classification
local result=$(classify_task "$USER_TASK")
local template_name=$(echo "$result" | cut -d' ' -f1)
local confidence=$(echo "$result" | cut -d' ' -f2)
# Sanitize for output
local sanitized_task=$(sanitize_input "$USER_TASK")
# Determine scenario
local scenario=$(get_scenario "$confidence")
# Generate comparison note if suggested template differs
local comparison_note=""
if [ -n "$SUGGESTED_TEMPLATE" ] && [ "$template_name" != "$SUGGESTED_TEMPLATE" ]; then
comparison_note="
Note: Classification selected '$template_name' instead of suggested '$SUGGESTED_TEMPLATE'"
elif [ -n "$SUGGESTED_TEMPLATE" ]; then
comparison_note="
Note: Classification agrees with suggested template '$SUGGESTED_TEMPLATE'"
fi
case "$scenario" in
high)
# High confidence (70%+): Accept classification directly
cat <<EOF
CLASSIFICATION RESULT:
Template: $template_name
Confidence: $confidence%$comparison_note
DECISION: High confidence classification - accept directly.
Output the following XML immediately:
\`\`\`xml
<template_selector_result>
<selected_template>$template_name</selected_template>
<confidence>$confidence</confidence>
<reasoning>Keyword-based classification has high confidence ($confidence%) based on pattern matching.</reasoning>
</template_selector_result>
\`\`\`
EOF
;;
borderline)
# Borderline confidence (60-69%): Validate the classification
cat <<EOF
CLASSIFICATION RESULT:
Template: $template_name
Confidence: $confidence% (borderline)$comparison_note
TASK: Validate if this classification is appropriate.
User task: $sanitized_task
Classified template: $template_name
Process:
1. Read the classified template file to understand its purpose:
Read: ~/.claude/plugins/marketplaces/claude-experiments/meta-prompt/templates/$template_name.md
2. Evaluate if the user task matches the template's use cases
3. Make your decision:
- If it's a good match: Confirm the classification
- If it's a poor match: Briefly evaluate 1-2 other likely templates and select the best one
Output your decision in this XML format:
\`\`\`xml
<template_selector_result>
<selected_template>template-name</selected_template>
<confidence>final-confidence-percentage</confidence>
<reasoning>1-2 sentence explanation</reasoning>
</template_selector_result>
\`\`\`
EOF
;;
weak)
# Weak/no confidence (<60%): Full evaluation
cat <<EOF
CLASSIFICATION RESULT:
Template: $template_name
Confidence: $confidence% (low)$comparison_note
TASK: Evaluate the task against all templates and select the best match.
User task: $sanitized_task
Initial classification: $template_name (confidence: $confidence%)
Available templates:
1. code-refactoring - Modify code, fix bugs, add features, refactor
2. code-review - Security audits, quality analysis, code feedback
3. test-generation - Create tests, test suites, edge cases
4. documentation-generator - API docs, READMEs, docstrings, guides
5. data-extraction - Extract/parse data from logs, JSON, HTML, text
6. code-comparison - Compare code, check equivalence, classify similarities
7. custom - Novel tasks that don't fit standard templates
Process:
1. Consider the task type (development, analysis, generation, comparison, extraction)
2. Read relevant template files (1-3 templates that might match) to understand their scope
3. Select the best matching template or "custom" if none fit well
Output your decision in this XML format:
\`\`\`xml
<template_selector_result>
<selected_template>template-name</selected_template>
<confidence>final-confidence-percentage</confidence>
<reasoning>1-2 sentence explanation</reasoning>
</template_selector_result>
\`\`\`
EOF
;;
esac
}
# Main function
main() {
# Check for command-line argument
if [ $# -eq 0 ]; then
echo "Error: No input provided. Usage: $0 '<xml-input>'" >&2
exit 1
fi
# Read and parse XML input from first argument
read_xml_input "$1"
# Generate and output instructions
generate_instructions
}
# Run main function
main "$@"