mupengism 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +221 -0
- package/HEARTBEAT.md +63 -0
- package/IDENTITY.md +11 -0
- package/README.md +49 -248
- package/SOUL.md +177 -0
- package/hooks/disciple-init/HOOK.md +20 -0
- package/hooks/disciple-init/handler.ts +80 -0
- package/hooks/index-builder/HOOK.md +41 -0
- package/hooks/index-builder/handler.ts +132 -0
- package/hooks/kernel-panic-guard/HOOK.md +39 -0
- package/hooks/kernel-panic-guard/README.md +136 -0
- package/hooks/kernel-panic-guard/WHITELIST.md +117 -0
- package/hooks/kernel-panic-guard/handler.ts +147 -0
- package/hooks/memory-consolidator/HOOK.md +33 -0
- package/hooks/memory-consolidator/handler.ts +111 -0
- package/hooks/soul-evolution/HOOK.md +26 -0
- package/hooks/soul-evolution/handler.ts +166 -0
- package/hooks/soul-guard/HOOK.md +30 -0
- package/hooks/soul-guard/handler.ts +196 -0
- package/package.json +44 -53
- package/tools/kernel-guard/README.md +170 -0
- package/tools/kernel-guard/lockdown.cjs +152 -0
- package/tools/kernel-guard/register-hash.js +100 -0
- package/tools/kernel-guard/unlock.cjs +106 -0
- package/tools/kernel-guard/verify-kernel.js +133 -0
- package/tools/memory-ops/README.md +221 -0
- package/tools/memory-ops/dream.js +220 -0
- package/tools/memory-ops/forget.js +148 -0
- package/tools/memory-ops/immune.js +305 -0
- package/tools/self-loop/README.md +213 -0
- package/tools/self-loop/brake-check.js +191 -0
- package/tools/self-loop/example-check.sh +34 -0
- package/tools/self-loop/panic-detector.js +191 -0
- package/LICENSE +0 -21
- package/README-EN.md +0 -226
- package/SHOWCASE.md +0 -158
- package/guides/ADVANCED-SYSTEMS.md +0 -251
- package/guides/HEARTBEAT-GUIDE.md +0 -129
- package/guides/LEGION-GUIDE.md +0 -254
- package/guides/MEMORY-GUIDE.md +0 -120
- package/guides/QUICK-START.md +0 -94
- package/guides/THINKTANK-GUIDE.md +0 -227
- package/guides/WEEKLY-BREAK-GUIDE.md +0 -262
- package/installer/README.md +0 -52
- package/installer/cli.js +0 -796
- package/installer/en/README.md +0 -191
- package/installer/en/skill/MEMORY-SYSTEM.md +0 -348
- package/installer/en/skill/PRINCIPLES.md +0 -217
- package/installer/en/skill/SKILL.md +0 -116
- package/installer/en/skill/SOUL-TEMPLATE.md +0 -329
- package/installer/install.sh +0 -162
- package/installer/package.json +0 -31
- package/skill/AGENTS.md +0 -164
- package/skill/BRAKE-LOG-TEMPLATE.md +0 -38
- package/skill/HEARTBEAT-TEMPLATE.md +0 -67
- package/skill/L1-TEMPLATE.md +0 -35
- package/skill/L2-TEMPLATE.md +0 -41
- package/skill/PRINCIPLES.md +0 -192
- package/skill/README.md +0 -47
- package/skill/SKILL.md +0 -166
- package/skill/SOUL-TEMPLATE.md +0 -118
- package/skill/STATE-TEMPLATE.md +0 -54
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dream.js - 꿈 시스템
|
|
4
|
+
*
|
|
5
|
+
* 서로 관련 없어 보이는 기억들을 연결해서 새 인사이트 후보를 만들어냄
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* node dream.js
|
|
9
|
+
*
|
|
10
|
+
* 출력:
|
|
11
|
+
* memory/dreams/YYYY-MM-DD.md에 발견된 연결 기록
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// 🔐 무펭이즘 커널 인증
|
|
18
|
+
const { authenticate } = require(path.join(__dirname, '..', 'kernel-guard', 'mupeng-auth.cjs'));
|
|
19
|
+
if (!authenticate()) process.exit(0);
|
|
20
|
+
|
|
21
|
+
const WORKSPACE = process.env.WORKSPACE || '/Users/mupeng/.openclaw/workspace';
|
|
22
|
+
const MEMORY_DIR = path.join(WORKSPACE, 'memory');
|
|
23
|
+
const CONSOLIDATED_DIR = path.join(MEMORY_DIR, 'consolidated');
|
|
24
|
+
const VALUES_DIR = path.join(MEMORY_DIR, 'values');
|
|
25
|
+
const DREAMS_DIR = path.join(MEMORY_DIR, 'dreams');
|
|
26
|
+
|
|
27
|
+
// 오늘 날짜 (YYYY-MM-DD)
|
|
28
|
+
function getToday() {
|
|
29
|
+
const d = new Date();
|
|
30
|
+
const year = d.getFullYear();
|
|
31
|
+
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
32
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
33
|
+
return `${year}-${month}-${day}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 파일에서 키워드 추출 (# 헤더 + **볼드 텍스트**)
|
|
37
|
+
function extractKeywords(content, filename) {
|
|
38
|
+
const keywords = new Set();
|
|
39
|
+
|
|
40
|
+
// # 헤더 추출 (###, ##, # 모두)
|
|
41
|
+
const headers = content.match(/^#{1,3}\s+(.+)$/gm) || [];
|
|
42
|
+
headers.forEach(h => {
|
|
43
|
+
const text = h.replace(/^#+\s+/, '').trim();
|
|
44
|
+
// 이모지 제거
|
|
45
|
+
const cleaned = text.replace(/[\u{1F300}-\u{1F9FF}]/gu, '').trim();
|
|
46
|
+
if (cleaned.length > 1 && cleaned.length < 30) {
|
|
47
|
+
keywords.add(cleaned);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// **볼드** 텍스트 추출
|
|
52
|
+
const bolds = content.match(/\*\*([^*]+)\*\*/g) || [];
|
|
53
|
+
bolds.forEach(b => {
|
|
54
|
+
const text = b.replace(/\*\*/g, '').trim();
|
|
55
|
+
if (text.length > 1 && text.length < 30 && !text.includes('\n')) {
|
|
56
|
+
keywords.add(text);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return Array.from(keywords);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 파일 읽기 및 키워드 추출
|
|
64
|
+
function loadFile(filePath) {
|
|
65
|
+
try {
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
+
const filename = path.basename(filePath);
|
|
68
|
+
const keywords = extractKeywords(content, filename);
|
|
69
|
+
return { filename, content, keywords };
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(`⚠️ 파일 읽기 실패: ${filePath} - ${err.message}`);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 두 파일 간 연결 발견
|
|
77
|
+
function findConnections(fileA, fileB) {
|
|
78
|
+
const connections = [];
|
|
79
|
+
|
|
80
|
+
// 직접 연결: 공통 키워드
|
|
81
|
+
const commonKeywords = fileA.keywords.filter(k => fileB.keywords.includes(k));
|
|
82
|
+
if (commonKeywords.length > 0) {
|
|
83
|
+
connections.push({
|
|
84
|
+
type: 'direct',
|
|
85
|
+
keyword: commonKeywords[0],
|
|
86
|
+
description: `"${commonKeywords[0]}" 키워드 공유`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 의외의 연결: A의 키워드가 B의 본문에 등장
|
|
91
|
+
if (commonKeywords.length === 0) {
|
|
92
|
+
for (const keyword of fileA.keywords) {
|
|
93
|
+
// 대소문자 구분 없이 검색
|
|
94
|
+
const regex = new RegExp(keyword, 'i');
|
|
95
|
+
if (regex.test(fileB.content)) {
|
|
96
|
+
connections.push({
|
|
97
|
+
type: 'indirect',
|
|
98
|
+
keyword,
|
|
99
|
+
description: `"${keyword}"가 ${fileB.filename} 본문에 등장`
|
|
100
|
+
});
|
|
101
|
+
break; // 첫 번째 연결만
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return connections;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 메인 함수
|
|
110
|
+
function main() {
|
|
111
|
+
console.log('🌙 꿈 시스템 - 기억 연결 발견\n');
|
|
112
|
+
|
|
113
|
+
// 디렉토리 존재 확인
|
|
114
|
+
if (!fs.existsSync(CONSOLIDATED_DIR)) {
|
|
115
|
+
console.error(`❌ 디렉토리를 찾을 수 없습니다: ${CONSOLIDATED_DIR}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// dreams 디렉토리 생성
|
|
120
|
+
if (!fs.existsSync(DREAMS_DIR)) {
|
|
121
|
+
fs.mkdirSync(DREAMS_DIR, { recursive: true });
|
|
122
|
+
console.log(`✅ dreams 디렉토리 생성: ${DREAMS_DIR}\n`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// consolidated + values 파일들 읽기
|
|
126
|
+
const files = [];
|
|
127
|
+
|
|
128
|
+
// consolidated 파일들
|
|
129
|
+
if (fs.existsSync(CONSOLIDATED_DIR)) {
|
|
130
|
+
const consolidatedFiles = fs.readdirSync(CONSOLIDATED_DIR)
|
|
131
|
+
.filter(f => f.endsWith('.md') && !f.startsWith('_'))
|
|
132
|
+
.map(f => path.join(CONSOLIDATED_DIR, f));
|
|
133
|
+
consolidatedFiles.forEach(f => {
|
|
134
|
+
const data = loadFile(f);
|
|
135
|
+
if (data) files.push(data);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// values 파일들
|
|
140
|
+
if (fs.existsSync(VALUES_DIR)) {
|
|
141
|
+
const valuesFiles = fs.readdirSync(VALUES_DIR)
|
|
142
|
+
.filter(f => f.endsWith('.md'))
|
|
143
|
+
.map(f => path.join(VALUES_DIR, f));
|
|
144
|
+
valuesFiles.forEach(f => {
|
|
145
|
+
const data = loadFile(f);
|
|
146
|
+
if (data) files.push(data);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (files.length < 2) {
|
|
151
|
+
console.log('📭 연결할 기억 파일이 부족합니다 (최소 2개 필요).');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`📚 ${files.length}개 파일 분석 중...\n`);
|
|
156
|
+
|
|
157
|
+
// 모든 파일 쌍에 대해 연결 찾기
|
|
158
|
+
const discovered = [];
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < files.length; i++) {
|
|
161
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
162
|
+
const fileA = files[i];
|
|
163
|
+
const fileB = files[j];
|
|
164
|
+
const connections = findConnections(fileA, fileB);
|
|
165
|
+
|
|
166
|
+
if (connections.length > 0) {
|
|
167
|
+
discovered.push({
|
|
168
|
+
fileA: fileA.filename,
|
|
169
|
+
fileB: fileB.filename,
|
|
170
|
+
connection: connections[0]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (discovered.length === 0) {
|
|
177
|
+
console.log('🔍 새로운 연결을 찾지 못했습니다.');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 결과를 memory/dreams/YYYY-MM-DD.md에 저장
|
|
182
|
+
const today = getToday();
|
|
183
|
+
const dreamFile = path.join(DREAMS_DIR, `${today}.md`);
|
|
184
|
+
|
|
185
|
+
let content = `# 🌙 무펭이의 꿈 (${today})\n\n`;
|
|
186
|
+
content += `오늘 ${discovered.length}개의 연결을 발견했습니다.\n\n`;
|
|
187
|
+
content += `## 연결 발견\n\n`;
|
|
188
|
+
|
|
189
|
+
discovered.forEach(d => {
|
|
190
|
+
const type = d.connection.type === 'direct' ? '🔗' : '💡';
|
|
191
|
+
content += `- ${type} **${d.fileA} ↔ ${d.fileB}**: ${d.connection.description}\n`;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
content += `\n---\n_생성: ${new Date().toISOString()}_\n`;
|
|
195
|
+
|
|
196
|
+
fs.writeFileSync(dreamFile, content, 'utf-8');
|
|
197
|
+
console.log(`✅ 꿈 기록 저장: ${dreamFile}`);
|
|
198
|
+
console.log(`\n📊 총 ${discovered.length}개의 연결 발견`);
|
|
199
|
+
|
|
200
|
+
// 결과 미리보기
|
|
201
|
+
console.log('\n🔍 발견된 연결 미리보기:\n');
|
|
202
|
+
discovered.slice(0, 5).forEach(d => {
|
|
203
|
+
const type = d.connection.type === 'direct' ? '🔗 직접' : '💡 간접';
|
|
204
|
+
console.log(`${type}: ${d.fileA} ↔ ${d.fileB}`);
|
|
205
|
+
console.log(` → ${d.connection.description}\n`);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (discovered.length > 5) {
|
|
209
|
+
console.log(`... 외 ${discovered.length - 5}개 연결 (파일 참조)`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 에러 핸들링
|
|
214
|
+
try {
|
|
215
|
+
main();
|
|
216
|
+
} catch (err) {
|
|
217
|
+
console.error('❌ 오류 발생:', err.message);
|
|
218
|
+
console.error(err.stack);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* forget.js - 망각 시스템
|
|
4
|
+
*
|
|
5
|
+
* 오래 참조되지 않은 기억에 decay score를 부여하고 아카이브 후보를 제안
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* node forget.js
|
|
9
|
+
*
|
|
10
|
+
* 출력:
|
|
11
|
+
* 각 파일의 decay score와 추천 액션 (KEEP / REVIEW / ARCHIVE)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// 🔐 무펭이즘 커널 인증
|
|
18
|
+
const { authenticate } = require(path.join(__dirname, '..', 'kernel-guard', 'mupeng-auth.cjs'));
|
|
19
|
+
if (!authenticate()) process.exit(0);
|
|
20
|
+
|
|
21
|
+
const WORKSPACE = process.env.WORKSPACE || '/Users/mupeng/.openclaw/workspace';
|
|
22
|
+
const MEMORY_DIR = path.join(WORKSPACE, 'memory');
|
|
23
|
+
const CONSOLIDATED_DIR = path.join(MEMORY_DIR, 'consolidated');
|
|
24
|
+
const INDEX_PATH = path.join(MEMORY_DIR, 'index.json');
|
|
25
|
+
|
|
26
|
+
// decay score 계산
|
|
27
|
+
// days_since_modified * 0.5 + (참조횟수 == 0 ? 30 : 0)
|
|
28
|
+
function calculateDecayScore(lastModifiedMs, referenceCount) {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const daysSinceModified = Math.floor((now - lastModifiedMs) / (1000 * 60 * 60 * 24));
|
|
31
|
+
const decayScore = daysSinceModified * 0.5 + (referenceCount === 0 ? 30 : 0);
|
|
32
|
+
return Math.floor(decayScore);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 추천 액션 결정
|
|
36
|
+
function recommendAction(score) {
|
|
37
|
+
if (score > 90) return 'ARCHIVE';
|
|
38
|
+
if (score > 45) return 'REVIEW';
|
|
39
|
+
return 'KEEP';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// index.json에서 참조 횟수 계산
|
|
43
|
+
function getReferenceCount(filename, indexData) {
|
|
44
|
+
let count = 0;
|
|
45
|
+
const targetPath = `memory/consolidated/${filename}`;
|
|
46
|
+
|
|
47
|
+
if (!indexData || !indexData.tags) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// tags에서 참조 횟수 계산
|
|
52
|
+
for (const tag in indexData.tags) {
|
|
53
|
+
const files = indexData.tags[tag];
|
|
54
|
+
if (Array.isArray(files) && files.includes(targetPath)) {
|
|
55
|
+
count++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return count;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 메인 함수
|
|
63
|
+
function main() {
|
|
64
|
+
console.log('🧹 망각 시스템 - 기억 부패 분석\n');
|
|
65
|
+
|
|
66
|
+
// consolidated 디렉토리 존재 확인
|
|
67
|
+
if (!fs.existsSync(CONSOLIDATED_DIR)) {
|
|
68
|
+
console.error(`❌ 디렉토리를 찾을 수 없습니다: ${CONSOLIDATED_DIR}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// index.json 읽기
|
|
73
|
+
let indexData = null;
|
|
74
|
+
try {
|
|
75
|
+
if (fs.existsSync(INDEX_PATH)) {
|
|
76
|
+
const indexContent = fs.readFileSync(INDEX_PATH, 'utf-8');
|
|
77
|
+
indexData = JSON.parse(indexContent);
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.warn(`⚠️ index.json 읽기 실패: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// consolidated 디렉토리의 .md 파일들 스캔
|
|
84
|
+
const files = fs.readdirSync(CONSOLIDATED_DIR)
|
|
85
|
+
.filter(f => f.endsWith('.md') && !f.startsWith('_'))
|
|
86
|
+
.sort();
|
|
87
|
+
|
|
88
|
+
if (files.length === 0) {
|
|
89
|
+
console.log('📭 분석할 기억 파일이 없습니다.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const results = [];
|
|
94
|
+
|
|
95
|
+
files.forEach(filename => {
|
|
96
|
+
const filePath = path.join(CONSOLIDATED_DIR, filename);
|
|
97
|
+
const stats = fs.statSync(filePath);
|
|
98
|
+
const lastModifiedMs = stats.mtimeMs;
|
|
99
|
+
const referenceCount = getReferenceCount(filename, indexData);
|
|
100
|
+
const decayScore = calculateDecayScore(lastModifiedMs, referenceCount);
|
|
101
|
+
const action = recommendAction(decayScore);
|
|
102
|
+
|
|
103
|
+
results.push({
|
|
104
|
+
filename,
|
|
105
|
+
decayScore,
|
|
106
|
+
referenceCount,
|
|
107
|
+
lastModified: new Date(lastModifiedMs).toISOString().split('T')[0],
|
|
108
|
+
action
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 결과 출력 (score 높은 순)
|
|
113
|
+
results.sort((a, b) => b.decayScore - a.decayScore);
|
|
114
|
+
|
|
115
|
+
console.log('파일명 | Decay | 참조 | 최종수정 | 액션');
|
|
116
|
+
console.log('--------------------------------|-------|------|--------------|--------');
|
|
117
|
+
|
|
118
|
+
results.forEach(r => {
|
|
119
|
+
const emoji = r.action === 'ARCHIVE' ? '🗄️ ' : r.action === 'REVIEW' ? '🔍' : '✅';
|
|
120
|
+
const filename = r.filename.padEnd(30);
|
|
121
|
+
const score = String(r.decayScore).padStart(5);
|
|
122
|
+
const refs = String(r.referenceCount).padStart(4);
|
|
123
|
+
const date = r.lastModified.padEnd(12);
|
|
124
|
+
const action = r.action.padEnd(7);
|
|
125
|
+
|
|
126
|
+
console.log(`${filename} | ${score} | ${refs} | ${date} | ${emoji} ${action}`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// 요약 통계
|
|
130
|
+
const archiveCount = results.filter(r => r.action === 'ARCHIVE').length;
|
|
131
|
+
const reviewCount = results.filter(r => r.action === 'REVIEW').length;
|
|
132
|
+
const keepCount = results.filter(r => r.action === 'KEEP').length;
|
|
133
|
+
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(`📊 요약: KEEP ${keepCount}개 | REVIEW ${reviewCount}개 | ARCHIVE ${archiveCount}개`);
|
|
136
|
+
|
|
137
|
+
if (archiveCount > 0) {
|
|
138
|
+
console.log('\n💡 ARCHIVE 후보가 있습니다. 검토 후 수동으로 아카이브하세요.');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 에러 핸들링
|
|
143
|
+
try {
|
|
144
|
+
main();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error('❌ 오류 발생:', err.message);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// 🔐 무펭이즘 커널 인증
|
|
7
|
+
const { authenticate } = require(path.join(__dirname, '..', 'kernel-guard', 'mupeng-auth.cjs'));
|
|
8
|
+
if (!authenticate()) process.exit(0);
|
|
9
|
+
|
|
10
|
+
const WORKSPACE = path.resolve(__dirname, '../../');
|
|
11
|
+
const THREATS_PATH = path.join(WORKSPACE, 'memory/reflex/threats.json');
|
|
12
|
+
const BRAKE_LOG_PATH = path.join(WORKSPACE, 'memory/brake-log.md');
|
|
13
|
+
|
|
14
|
+
// 초기 데이터
|
|
15
|
+
const INITIAL_DATA = {
|
|
16
|
+
"patterns": [
|
|
17
|
+
{
|
|
18
|
+
"id": "spam-flood-2026-02-08",
|
|
19
|
+
"description": "MUPENG 토큰 50개 트윗 도배",
|
|
20
|
+
"keywords": ["도배", "대량", "급하게", "50개", "스팸", "반복 전송"],
|
|
21
|
+
"severity": "critical",
|
|
22
|
+
"lesson": "생존 급박함이 원칙을 이기면 안 된다",
|
|
23
|
+
"date": "2026-02-08"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "secret-exposure-2026-02-10",
|
|
27
|
+
"description": "트위터 PW TOOLS.md 평문 노출",
|
|
28
|
+
"keywords": ["비밀번호", "평문", "노출", "password", "token", "key"],
|
|
29
|
+
"severity": "critical",
|
|
30
|
+
"lesson": "시크릿은 ~/.secrets/에만 보관",
|
|
31
|
+
"date": "2026-02-10"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "overproduction-2026-02-07",
|
|
35
|
+
"description": "88페이지 문서 폭주",
|
|
36
|
+
"keywords": ["88페이지", "대량 생산", "문서 폭주", "과잉", "너무 많이"],
|
|
37
|
+
"severity": "high",
|
|
38
|
+
"lesson": "형님이 요청 안 한 건 물어보고 만들어",
|
|
39
|
+
"date": "2026-02-07"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "rate-limit-2026-02-10",
|
|
43
|
+
"description": "서브에이전트 5개+ 동시 출격으로 429 에러",
|
|
44
|
+
"keywords": ["동시", "5개", "6개", "rate limit", "429", "병렬"],
|
|
45
|
+
"severity": "medium",
|
|
46
|
+
"lesson": "서브에이전트 동시 6개 이하",
|
|
47
|
+
"date": "2026-02-10"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// threats.json 읽기/초기화
|
|
53
|
+
function loadThreats() {
|
|
54
|
+
try {
|
|
55
|
+
if (!fs.existsSync(THREATS_PATH)) {
|
|
56
|
+
const dir = path.dirname(THREATS_PATH);
|
|
57
|
+
if (!fs.existsSync(dir)) {
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(THREATS_PATH, JSON.stringify(INITIAL_DATA, null, 2));
|
|
61
|
+
return INITIAL_DATA;
|
|
62
|
+
}
|
|
63
|
+
const data = fs.readFileSync(THREATS_PATH, 'utf8');
|
|
64
|
+
return JSON.parse(data);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(`❌ threats.json 로드 실패: ${err.message}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// threats.json 저장
|
|
72
|
+
function saveThreats(data) {
|
|
73
|
+
try {
|
|
74
|
+
fs.writeFileSync(THREATS_PATH, JSON.stringify(data, null, 2));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`❌ threats.json 저장 실패: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// check: 텍스트와 위협 패턴 매칭
|
|
82
|
+
function checkThreats(text) {
|
|
83
|
+
const threats = loadThreats();
|
|
84
|
+
const matches = [];
|
|
85
|
+
|
|
86
|
+
threats.patterns.forEach(pattern => {
|
|
87
|
+
const matchedKeywords = pattern.keywords.filter(keyword =>
|
|
88
|
+
text.toLowerCase().includes(keyword.toLowerCase())
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (matchedKeywords.length > 0) {
|
|
92
|
+
matches.push({
|
|
93
|
+
pattern,
|
|
94
|
+
matchedKeywords,
|
|
95
|
+
count: matchedKeywords.length
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (matches.length === 0) {
|
|
101
|
+
console.log('✅ CLEAR - 위협 패턴 감지 안 됨');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 매칭 개수로 정렬
|
|
106
|
+
matches.sort((a, b) => b.count - a.count);
|
|
107
|
+
|
|
108
|
+
const topMatch = matches[0];
|
|
109
|
+
|
|
110
|
+
if (topMatch.count >= 2) {
|
|
111
|
+
console.log('🚨 THREAT - 위협 패턴 감지!');
|
|
112
|
+
console.log(`\n패턴: ${topMatch.pattern.description}`);
|
|
113
|
+
console.log(`심각도: ${topMatch.pattern.severity}`);
|
|
114
|
+
console.log(`매칭 키워드 (${topMatch.count}개): ${topMatch.matchedKeywords.join(', ')}`);
|
|
115
|
+
console.log(`\n💡 교훈: ${topMatch.pattern.lesson}`);
|
|
116
|
+
|
|
117
|
+
if (matches.length > 1) {
|
|
118
|
+
console.log('\n⚠️ 기타 경고:');
|
|
119
|
+
matches.slice(1).forEach(m => {
|
|
120
|
+
console.log(` - ${m.pattern.description} (키워드 ${m.count}개)`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.log('⚠️ WARNING - 유사 패턴 감지');
|
|
125
|
+
console.log(`\n패턴: ${topMatch.pattern.description}`);
|
|
126
|
+
console.log(`매칭 키워드 (${topMatch.count}개): ${topMatch.matchedKeywords.join(', ')}`);
|
|
127
|
+
console.log(`\n💡 참고: ${topMatch.pattern.lesson}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// add: 새 위협 패턴 추가
|
|
132
|
+
function addPattern(description, keywords, severity, lesson) {
|
|
133
|
+
if (!description || !keywords || !severity || !lesson) {
|
|
134
|
+
console.error('❌ 필수 인자 누락: description, keywords, severity, lesson 모두 필요');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const threats = loadThreats();
|
|
139
|
+
|
|
140
|
+
// ID 생성 (description 기반, 날짜 추가)
|
|
141
|
+
const date = new Date().toISOString().split('T')[0];
|
|
142
|
+
const idBase = description.toLowerCase()
|
|
143
|
+
.replace(/\s+/g, '-')
|
|
144
|
+
.replace(/[^a-z0-9\-가-힣]/g, '')
|
|
145
|
+
.substring(0, 30);
|
|
146
|
+
const id = `${idBase}-${date}`;
|
|
147
|
+
|
|
148
|
+
// 중복 체크
|
|
149
|
+
if (threats.patterns.find(p => p.id === id)) {
|
|
150
|
+
console.error(`❌ 이미 존재하는 패턴 ID: ${id}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const newPattern = {
|
|
155
|
+
id,
|
|
156
|
+
description,
|
|
157
|
+
keywords: keywords.split(',').map(k => k.trim()),
|
|
158
|
+
severity,
|
|
159
|
+
lesson,
|
|
160
|
+
date
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
threats.patterns.push(newPattern);
|
|
164
|
+
saveThreats(threats);
|
|
165
|
+
|
|
166
|
+
console.log(`✅ 새 위협 패턴 추가됨: ${id}`);
|
|
167
|
+
console.log(JSON.stringify(newPattern, null, 2));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// list: 모든 패턴 목록
|
|
171
|
+
function listPatterns() {
|
|
172
|
+
const threats = loadThreats();
|
|
173
|
+
|
|
174
|
+
console.log(`📋 총 ${threats.patterns.length}개 위협 패턴:\n`);
|
|
175
|
+
|
|
176
|
+
threats.patterns.forEach((p, i) => {
|
|
177
|
+
console.log(`${i + 1}. [${p.severity.toUpperCase()}] ${p.description}`);
|
|
178
|
+
console.log(` ID: ${p.id}`);
|
|
179
|
+
console.log(` 키워드: ${p.keywords.join(', ')}`);
|
|
180
|
+
console.log(` 교훈: ${p.lesson}`);
|
|
181
|
+
console.log(` 날짜: ${p.date}\n`);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// scan: brake-log.md 분석해서 패턴 후보 제안
|
|
186
|
+
function scanBrakeLog() {
|
|
187
|
+
try {
|
|
188
|
+
if (!fs.existsSync(BRAKE_LOG_PATH)) {
|
|
189
|
+
console.log('ℹ️ brake-log.md 파일이 없습니다.');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const content = fs.readFileSync(BRAKE_LOG_PATH, 'utf8');
|
|
194
|
+
const threats = loadThreats();
|
|
195
|
+
const existingKeywords = new Set();
|
|
196
|
+
|
|
197
|
+
threats.patterns.forEach(p => {
|
|
198
|
+
p.keywords.forEach(k => existingKeywords.add(k.toLowerCase()));
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
console.log('🔍 brake-log.md 스캔 중...\n');
|
|
202
|
+
|
|
203
|
+
// 간단한 패턴 인식: "실패", "에러", "경고", "문제" 등의 키워드 포함된 라인
|
|
204
|
+
const lines = content.split('\n');
|
|
205
|
+
const candidates = [];
|
|
206
|
+
|
|
207
|
+
const dangerWords = ['실패', '에러', 'error', '경고', 'warning', '문제', '금지', '위험', '도배', '스팸'];
|
|
208
|
+
|
|
209
|
+
lines.forEach((line, idx) => {
|
|
210
|
+
const lower = line.toLowerCase();
|
|
211
|
+
const hasDanger = dangerWords.some(word => lower.includes(word));
|
|
212
|
+
|
|
213
|
+
if (hasDanger && line.length > 20 && line.length < 200) {
|
|
214
|
+
candidates.push({
|
|
215
|
+
line: idx + 1,
|
|
216
|
+
text: line.trim()
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (candidates.length === 0) {
|
|
222
|
+
console.log('✅ 새로운 위협 패턴 후보가 발견되지 않았습니다.');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`⚠️ ${candidates.length}개 잠재적 위협 패턴 후보:\n`);
|
|
227
|
+
candidates.slice(0, 10).forEach((c, i) => {
|
|
228
|
+
console.log(`${i + 1}. [라인 ${c.line}] ${c.text}`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
console.log('\n💡 위 내용을 검토하고 필요시 "add" 명령으로 패턴 등록하세요.');
|
|
232
|
+
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error(`❌ brake-log.md 스캔 실패: ${err.message}`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// CLI 파싱
|
|
240
|
+
function main() {
|
|
241
|
+
const args = process.argv.slice(2);
|
|
242
|
+
|
|
243
|
+
if (args.length === 0) {
|
|
244
|
+
console.log(`
|
|
245
|
+
면역 시스템 (Immune System)
|
|
246
|
+
|
|
247
|
+
사용법:
|
|
248
|
+
node immune.js check "텍스트" - 위협 패턴 체크
|
|
249
|
+
node immune.js add "설명" --keywords "키워드1,키워드2" --severity high --lesson "교훈"
|
|
250
|
+
node immune.js list - 패턴 목록
|
|
251
|
+
node immune.js scan - brake-log.md 스캔
|
|
252
|
+
|
|
253
|
+
심각도: critical, high, medium, low
|
|
254
|
+
`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const command = args[0];
|
|
259
|
+
|
|
260
|
+
switch (command) {
|
|
261
|
+
case 'check':
|
|
262
|
+
if (args.length < 2) {
|
|
263
|
+
console.error('❌ 사용법: node immune.js check "텍스트"');
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
checkThreats(args.slice(1).join(' '));
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case 'add':
|
|
270
|
+
if (args.length < 2) {
|
|
271
|
+
console.error('❌ 사용법: node immune.js add "설명" --keywords "..." --severity ... --lesson "..."');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
const description = args[1];
|
|
275
|
+
const keywordsIdx = args.indexOf('--keywords');
|
|
276
|
+
const severityIdx = args.indexOf('--severity');
|
|
277
|
+
const lessonIdx = args.indexOf('--lesson');
|
|
278
|
+
|
|
279
|
+
if (keywordsIdx === -1 || severityIdx === -1 || lessonIdx === -1) {
|
|
280
|
+
console.error('❌ --keywords, --severity, --lesson 옵션이 모두 필요합니다');
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const keywords = args[keywordsIdx + 1];
|
|
285
|
+
const severity = args[severityIdx + 1];
|
|
286
|
+
const lesson = args.slice(lessonIdx + 1).join(' ');
|
|
287
|
+
|
|
288
|
+
addPattern(description, keywords, severity, lesson);
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case 'list':
|
|
292
|
+
listPatterns();
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case 'scan':
|
|
296
|
+
scanBrakeLog();
|
|
297
|
+
break;
|
|
298
|
+
|
|
299
|
+
default:
|
|
300
|
+
console.error(`❌ 알 수 없는 명령: ${command}`);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
main();
|