AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning
This commit is contained in:
172
skills/openclaw-self-healing/scripts/weekly-review-collector.sh
Normal file
172
skills/openclaw-self-healing/scripts/weekly-review-collector.sh
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/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)_"
|
||||
Reference in New Issue
Block a user