#!/bin/bash # Weekly Review Collector for V5.0.1 Layer 3 # ========================================= # 지난 7일간의 self-review 데이터를 수집하여 요약 # Node.js 기반 YAML 파싱 (grep보다 안전) # ========================================= set -euo pipefail REVIEW_DIR=~/openclaw/memory/self-review echo "# 주간 자기평가 요약 (V5.0.1 Layer 3)" echo "# 생성일: $(date '+%Y-%m-%d %H:%M')" echo "# 분석 대상: 지난 7일" echo "" # Node.js 존재 확인 if ! command -v node &>/dev/null; then echo "❌ Error: Node.js가 설치되어 있지 않습니다." >&2 exit 1 fi # Node.js로 YAML 파싱 (에러 핸들링 강화) node << 'NODEJS_SCRIPT' const fs = require('fs'); const path = require('path'); const reviewDir = path.join(process.env.HOME, 'openclaw', 'memory', 'self-review'); // 지난 7일 날짜 생성 const dates = []; for (let i = 0; i < 7; i++) { const d = new Date(Date.now() - i * 24 * 60 * 60 * 1000); dates.push(d.toISOString().split('T')[0]); } let totalReviews = 0; let scoreSum = 0; let lowScores = 0; // < 7점 let tooEasyCount = 0; const problems = []; // 간단한 YAML 값 추출 (정규식 기반, 에러 안전) function extractValue(content, key) { try { const regex = new RegExp(`^\\s*${key}:\\s*["']?([^"'\\n]+)["']?`, 'm'); const match = content.match(regex); return match ? match[1].trim() : null; } catch (e) { return null; } } function extractNumber(content, key) { const val = extractValue(content, key); if (!val) return null; const num = parseFloat(val); return isNaN(num) ? null : num; } function extractBoolean(content, key) { const val = extractValue(content, key); return val === 'true'; } for (const date of dates) { const dayDir = path.join(reviewDir, date); // 디렉토리 존재 확인 (에러 안전) try { if (!fs.existsSync(dayDir)) continue; if (!fs.statSync(dayDir).isDirectory()) continue; } catch (e) { continue; } let files; try { files = fs.readdirSync(dayDir).filter(f => f.endsWith('.yaml')); } catch (e) { console.error(`⚠️ ${date} 디렉토리 읽기 실패: ${e.message}`); continue; } for (const file of files) { let content; try { content = fs.readFileSync(path.join(dayDir, file), 'utf8'); } catch (e) { console.error(`⚠️ ${file} 파일 읽기 실패: ${e.message}`); continue; } totalReviews++; // 점수 추출 const score = extractNumber(content, 'score'); if (score !== null) { scoreSum += score; if (score < 7) lowScores++; } // 편향 체크 if (extractBoolean(content, 'am_i_being_too_easy')) { tooEasyCount++; } // 문제점 수집 const cronName = extractValue(content, 'cron_name'); const wrong = extractValue(content, 'what_went_wrong'); const action = extractValue(content, 'next_action'); if (wrong && wrong !== '없음' && wrong !== 'N/A') { problems.push({ date, cronName, wrong, action }); } } } // 통계 출력 console.log('## 📊 통계\n'); console.log(`- 총 자기평가 수: ${totalReviews}`); console.log(`- 평균 점수: ${totalReviews > 0 ? (scoreSum / totalReviews).toFixed(1) : 'N/A'}`); console.log(`- 목표 미달 (< 7점): ${lowScores}`); console.log(`- 관대함 인정 (am_i_being_too_easy): ${tooEasyCount}`); console.log(''); // 문제점 목록 console.log('## 🔧 발견된 문제점\n'); if (problems.length === 0) { console.log('_문제점 없음 (⚠️ 너무 관대한 것은 아닌지 확인 필요)_\n'); } else { for (const p of problems.slice(0, 10)) { // 최대 10개 console.log(`### ${p.date} - ${p.cronName || 'Unknown'}`); console.log(`- 문제: ${p.wrong}`); console.log(`- 액션: ${p.action || '없음'}`); console.log(''); } } // 패턴 분석 console.log('## 🔍 패턴 분석\n'); // 같은 문제 반복 체크 const wrongCounts = {}; for (const p of problems) { const key = p.wrong.toLowerCase().substring(0, 30); wrongCounts[key] = (wrongCounts[key] || 0) + 1; } const repeated = Object.entries(wrongCounts).filter(([k, v]) => v >= 2); if (repeated.length > 0) { console.log('### ⚠️ 반복되는 문제'); for (const [problem, count] of repeated) { console.log(`- "${problem}..." (${count}회)`); } console.log(''); } else { console.log('_반복 패턴 없음_\n'); } // 외부 검증 질문 console.log('## 🎯 Layer 3 검증 질문\n'); console.log('1. 이번 주 자기평가들이 너무 관대했는가?'); console.log(` - 관대함 인정률: ${totalReviews > 0 ? ((tooEasyCount / totalReviews) * 100).toFixed(0) : 0}%`); console.log('2. 같은 실수가 반복되고 있는가?'); console.log(` - 반복 패턴: ${repeated.length}개 발견`); console.log('3. 개선 항목이 실제로 적용됐는가?'); console.log('4. 다음 주 집중해야 할 영역은?'); NODEJS_SCRIPT echo "" echo "---" echo "_Generated by weekly-review-collector.sh (V5.0.1)_"