#!/bin/bash # Self-Review V5.0.1 Logger # ========================================= # 크론 종료 시 호출하여 메트릭 + 자기성찰 기록 # # ⚠️ 주의: "자동 메트릭"은 거짓말입니다. # duration과 tokens는 호출자가 제공해야 합니다. # OpenClaw 크론은 이 값들을 자동으로 알 수 없습니다. # ========================================= # # Usage: self-review-logger.sh "크론명" "점수" "tokens_in" "tokens_out" "exit_status" \ # "what_went_wrong" "why" "next_action" # # 예시: # bash ~/openclaw/scripts/self-review-logger.sh \ # "TQQQ 모니터링" "8.5" "100" "200" "ok" \ # "지연 태그 누락" "습관" "다음부터 추가" set -euo pipefail # === 에러 핸들링 === error_exit() { echo "❌ Error: $1" >&2 exit 1 } # === 인자 검증 === if [ $# -lt 5 ]; then error_exit "Usage: $0 'cron_name' 'score' 'tokens_in' 'tokens_out' 'status' ['wrong'] ['why'] ['action']" fi # === 인자 파싱 === CRON_NAME="${1:-unknown}" SCORE="${2:-0}" # 1-10 점수 (LLM 자기평가) TOKENS_IN="${3:-0}" # 입력 토큰 (추정치 허용) TOKENS_OUT="${4:-0}" # 출력 토큰 (추정치 허용) EXIT_STATUS="${5:-ok}" # ok | error WHAT_WENT_WRONG="${6:-없음}" WHY="${7:-N/A}" NEXT_ACTION="${8:-없음}" # === YAML 인젝션 방지: 특수문자 이스케이프 === escape_yaml() { local input="$1" # 따옴표와 백슬래시 이스케이프 echo "$input" | sed 's/\\/\\\\/g; s/"/\\"/g; s/'"'"'/\\'"'"'/g' } CRON_NAME_SAFE=$(escape_yaml "$CRON_NAME") WHAT_WENT_WRONG_SAFE=$(escape_yaml "$WHAT_WENT_WRONG") WHY_SAFE=$(escape_yaml "$WHY") NEXT_ACTION_SAFE=$(escape_yaml "$NEXT_ACTION") # === 날짜/시간 === DATE=$(date '+%Y-%m-%d') TIME=$(date '+%H%M%S') TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ') # === 디렉토리 생성 === DIR=~/openclaw/memory/self-review/$DATE mkdir -p "$DIR" || error_exit "Failed to create directory: $DIR" # === 안전한 파일명 생성 (타임스탬프 포함!) === # 이모지, 특수문자 제거 + 한글 유지 + 숫자 보존 SAFE_NAME=$(echo "$CRON_NAME" | sed 's/[^가-힣a-zA-Z0-9_-]/_/g' | sed 's/__*/_/g' | sed 's/^_//' | sed 's/_$//') if [ -z "$SAFE_NAME" ] || [ "$SAFE_NAME" = "_" ]; then SAFE_NAME="cron" fi # ✅ 타임스탬프 포함 → 덮어쓰기 방지 FILE="$DIR/${SAFE_NAME}_${TIME}.yaml" # === 크론별 목표 로드 === TARGETS_FILE=~/openclaw/templates/targets-by-cron.yaml DEFAULT_TOKENS=500 # 크론별 목표가 있으면 사용, 없으면 기본값 TOKENS_BUDGET=$DEFAULT_TOKENS if [ -f "$TARGETS_FILE" ]; then # 정규화된 키로 검색 (공백→언더스코어, 특수문자 제거) SEARCH_KEY=$(echo "$CRON_NAME" | sed 's/[^가-힣a-zA-Z0-9_-]/_/g' | sed 's/__*/_/g' | sed 's/^_//' | sed 's/_$//') # grep으로 토큰 목표 검색 (2줄 이내에서) CRON_TOKENS=$(grep -A3 "^${SEARCH_KEY}:" "$TARGETS_FILE" 2>/dev/null | grep "tokens:" | head -1 | awk '{print $2}' || echo "") if [ -n "$CRON_TOKENS" ] && [ "$CRON_TOKENS" -gt 0 ] 2>/dev/null; then TOKENS_BUDGET=$CRON_TOKENS fi fi # === 목표 대비 계산 === # 점수 기반 판정 SCORE_MET="false" if command -v bc &>/dev/null; then if [ "$(echo "$SCORE >= 7" | bc -l 2>/dev/null)" = "1" ]; then SCORE_MET="true" fi else # bc 없으면 정수 비교 SCORE_INT=${SCORE%.*} if [ "${SCORE_INT:-0}" -ge 7 ] 2>/dev/null; then SCORE_MET="true" fi fi # 토큰 사용률 USAGE_PCT="0" if command -v bc &>/dev/null && [ "$TOKENS_OUT" -gt 0 ] 2>/dev/null; then USAGE_PCT=$(echo "scale=1; $TOKENS_OUT / $TOKENS_BUDGET * 100" | bc 2>/dev/null || echo "0") fi # === 마감일 계산 (Linux/macOS 호환) === if date -v+7d '+%Y-%m-%d' &>/dev/null 2>&1; then DEADLINE=$(date -v+7d '+%Y-%m-%d') else DEADLINE=$(date -d '+7 days' '+%Y-%m-%d' 2>/dev/null || date '+%Y-%m-%d') fi # === YAML 생성 === cat > "$FILE" << EOF # Self-Review V5.0.1 # Generated: $TIMESTAMP # File: ${SAFE_NAME}_${TIME}.yaml # === 메트릭 (호출자 제공) === # ⚠️ 이 값들은 "자동 수집"이 아닙니다. # 크론이 종료 시 명시적으로 전달해야 합니다. metrics: cron_name: "${CRON_NAME_SAFE}" timestamp: "${TIMESTAMP}" score: ${SCORE} tokens_in: ${TOKENS_IN} tokens_out: ${TOKENS_OUT} exit_status: "${EXIT_STATUS}" # === LLM 자기성찰 === self_reflection: what_went_wrong: "${WHAT_WENT_WRONG_SAFE}" why: "${WHY_SAFE}" next_action: "${NEXT_ACTION_SAFE}" deadline: "${DEADLINE}" # === 편향 점검 === # ⚠️ 기본값 true = 관대함 의심 (보수적 접근) bias_check: am_i_being_too_easy: true evidence: "주간 리뷰에서 검증 필요" user_flagged_before: false # === 목표 대비 === targets: score: goal: 7.0 actual: ${SCORE} met: ${SCORE_MET} tokens: budget: ${TOKENS_BUDGET} actual: ${TOKENS_OUT} usage_pct: ${USAGE_PCT} # === 메타 === meta: version: "5.0.1" reviewed_by: null review_date: null EOF echo "✅ Self-review logged: $FILE"