memento-mcp-server 1.16.3-a → 1.16.3-c
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/dist/domains/search/algorithms/vector-search-engine-migration.d.ts +13 -8
- package/dist/domains/search/algorithms/vector-search-engine-migration.d.ts.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine-migration.js +19 -41
- package/dist/domains/search/algorithms/vector-search-engine-migration.js.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine.d.ts +17 -36
- package/dist/domains/search/algorithms/vector-search-engine.d.ts.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine.js +94 -481
- package/dist/domains/search/algorithms/vector-search-engine.js.map +1 -1
- package/dist/domains/search/repositories/vector-search.repository.d.ts.map +1 -1
- package/dist/domains/search/repositories/vector-search.repository.js +28 -12
- package/dist/domains/search/repositories/vector-search.repository.js.map +1 -1
- package/dist/domains/search/services/vector-search/vector-performance-tester.js +1 -1
- package/dist/domains/search/services/vector-search/vector-performance-tester.js.map +1 -1
- package/dist/domains/search/services/vector-search/vector-search-container.d.ts +1 -1
- package/dist/domains/search/services/vector-search/vector-search-container.d.ts.map +1 -1
- package/dist/domains/search/services/vector-search/vector-search-container.js +1 -1
- package/dist/domains/search/services/vector-search/vector-search-container.js.map +1 -1
- package/dist/domains/search/services/vector-search/vector-search-facade.js +3 -3
- package/dist/domains/search/services/vector-search/vector-search-facade.js.map +1 -1
- package/dist/domains/search/services/vector-search/vector-search.service.js +1 -1
- package/dist/domains/search/services/vector-search/vector-search.service.js.map +1 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +2 -7
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +33 -7
- package/dist/server/index.js.map +1 -1
- package/dist/server/server-factory.d.ts +65 -0
- package/dist/server/server-factory.d.ts.map +1 -0
- package/dist/server/server-factory.js +40 -0
- package/dist/server/server-factory.js.map +1 -0
- package/dist/server/servers/sse-server.d.ts +33 -0
- package/dist/server/servers/sse-server.d.ts.map +1 -0
- package/dist/server/servers/sse-server.js +48 -0
- package/dist/server/servers/sse-server.js.map +1 -0
- package/dist/server/servers/stdio-server.d.ts +34 -0
- package/dist/server/servers/stdio-server.d.ts.map +1 -0
- package/dist/server/servers/stdio-server.js +58 -0
- package/dist/server/servers/stdio-server.js.map +1 -0
- package/dist/server/simple-mcp-server.d.ts +5 -0
- package/dist/server/simple-mcp-server.d.ts.map +1 -1
- package/dist/server/simple-mcp-server.js +17 -7
- package/dist/server/simple-mcp-server.js.map +1 -1
- package/dist/server/sse-server-impl.d.ts +22 -0
- package/dist/server/sse-server-impl.d.ts.map +1 -0
- package/dist/server/sse-server-impl.js +39 -0
- package/dist/server/sse-server-impl.js.map +1 -0
- package/dist/server/stdio-server-impl.d.ts +12 -0
- package/dist/server/stdio-server-impl.d.ts.map +1 -0
- package/dist/server/stdio-server-impl.js +19 -0
- package/dist/server/stdio-server-impl.js.map +1 -0
- package/dist/shared/types/vector-search.types.d.ts +1 -0
- package/dist/shared/types/vector-search.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/__tests__/check-db-integrity.integration.spec.ts +163 -0
- package/scripts/__tests__/fix-migration.integration.spec.ts +203 -0
- package/scripts/__tests__/migrate-embedding-data.integration.spec.ts +219 -0
- package/scripts/__tests__/regenerate-embeddings.integration.spec.ts +192 -0
- package/scripts/backup-embeddings.js +52 -61
- package/scripts/check-db-integrity.js +49 -25
- package/scripts/check-file-sizes.ts +4 -4
- package/scripts/check-pii-masking.ts +0 -3
- package/scripts/check-sql-injection.ts +0 -12
- package/scripts/debug-embeddings.js +74 -93
- package/scripts/fix-migration.js +115 -80
- package/scripts/fix-vector-dimensions.js +70 -89
- package/scripts/migrate-embedding-data.js +111 -25
- package/scripts/regenerate-embeddings.js +31 -15
- package/scripts/run-migration.js +144 -107
- package/scripts/safe-migration.js +192 -142
- package/scripts/save-work-memory.ts +6 -7
- package/scripts/simple-migrate.js +66 -34
- package/scripts/simple-update.js +147 -109
- package/dist/domains/search/algorithms/vector-search-engine-refactored.d.ts +0 -56
- package/dist/domains/search/algorithms/vector-search-engine-refactored.d.ts.map +0 -1
- package/dist/domains/search/algorithms/vector-search-engine-refactored.js +0 -101
- package/dist/domains/search/algorithms/vector-search-engine-refactored.js.map +0 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* regenerate-embeddings.js 통합 테스트
|
|
3
|
+
*
|
|
4
|
+
* Given/When/Then 구조를 따르는 통합 테스트
|
|
5
|
+
* 공통 모듈(initializeDatabase)을 사용하는 버전으로 리팩토링 후 검증
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
|
+
import { existsSync, unlinkSync, mkdirSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { tmpdir } from 'os';
|
|
12
|
+
import { initializeDatabase, closeDatabase } from '../../src/infrastructure/database/database/init.js';
|
|
13
|
+
import Database from 'better-sqlite3';
|
|
14
|
+
|
|
15
|
+
describe('regenerate-embeddings 통합 테스트', () => {
|
|
16
|
+
let testDbPath: string;
|
|
17
|
+
let originalDbPath: string | undefined;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// 테스트용 임시 디렉토리 생성
|
|
21
|
+
const testDir = join(tmpdir(), `memento-test-${Date.now()}`);
|
|
22
|
+
mkdirSync(testDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
testDbPath = join(testDir, 'memory.db');
|
|
25
|
+
|
|
26
|
+
// 환경 변수 백업 및 설정
|
|
27
|
+
originalDbPath = process.env.DB_PATH;
|
|
28
|
+
process.env.DB_PATH = testDbPath;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
// 환경 변수 복원
|
|
33
|
+
if (originalDbPath !== undefined) {
|
|
34
|
+
process.env.DB_PATH = originalDbPath;
|
|
35
|
+
} else {
|
|
36
|
+
delete process.env.DB_PATH;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 테스트 파일 정리
|
|
40
|
+
try {
|
|
41
|
+
if (existsSync(testDbPath)) {
|
|
42
|
+
unlinkSync(testDbPath);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// 파일 정리 실패는 무시
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @given 공통 모듈(initializeDatabase)을 사용하는 regenerate-embeddings 함수
|
|
51
|
+
* @when 데이터베이스 연결 및 기억 조회
|
|
52
|
+
* @then 기억 목록이 정상적으로 조회됨
|
|
53
|
+
*/
|
|
54
|
+
it('should query memories successfully', async () => {
|
|
55
|
+
// Given: 정상적인 데이터베이스 초기화
|
|
56
|
+
const db = await initializeDatabase();
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// memory_item 테이블 확인
|
|
60
|
+
const tableExists = db.prepare(`
|
|
61
|
+
SELECT name FROM sqlite_master
|
|
62
|
+
WHERE type='table' AND name='memory_item'
|
|
63
|
+
`).get();
|
|
64
|
+
|
|
65
|
+
if (tableExists) {
|
|
66
|
+
// When: 기억 조회
|
|
67
|
+
const memories = db.prepare(`
|
|
68
|
+
SELECT id, content, type, importance, created_at
|
|
69
|
+
FROM memory_item
|
|
70
|
+
ORDER BY created_at
|
|
71
|
+
`).all();
|
|
72
|
+
|
|
73
|
+
// Then: 기억 목록이 정상적으로 조회됨
|
|
74
|
+
expect(memories).toBeDefined();
|
|
75
|
+
expect(Array.isArray(memories)).toBe(true);
|
|
76
|
+
} else {
|
|
77
|
+
console.log('⚠️ memory_item 테이블이 없습니다. 테스트 스킵');
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
closeDatabase(db);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @given 공통 모듈(initializeDatabase)을 사용하는 regenerate-embeddings 함수
|
|
86
|
+
* @when 임베딩 통계 조회
|
|
87
|
+
* @then 통계가 정상적으로 반환됨
|
|
88
|
+
*/
|
|
89
|
+
it('should query embedding statistics successfully', async () => {
|
|
90
|
+
// Given: 정상적인 데이터베이스 초기화
|
|
91
|
+
const db = await initializeDatabase();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// memory_embedding 테이블 확인
|
|
95
|
+
const tableExists = db.prepare(`
|
|
96
|
+
SELECT name FROM sqlite_master
|
|
97
|
+
WHERE type='table' AND name='memory_embedding'
|
|
98
|
+
`).get();
|
|
99
|
+
|
|
100
|
+
if (tableExists) {
|
|
101
|
+
// When: 임베딩 통계 조회
|
|
102
|
+
const finalStats = db.prepare(`
|
|
103
|
+
SELECT
|
|
104
|
+
COUNT(*) as total,
|
|
105
|
+
AVG(dim) as avg_dim,
|
|
106
|
+
MIN(dim) as min_dim,
|
|
107
|
+
MAX(dim) as max_dim
|
|
108
|
+
FROM memory_embedding
|
|
109
|
+
`).get();
|
|
110
|
+
|
|
111
|
+
// Then: 통계가 정상적으로 반환됨
|
|
112
|
+
expect(finalStats).toBeDefined();
|
|
113
|
+
expect(finalStats.total).toBeGreaterThanOrEqual(0);
|
|
114
|
+
} else {
|
|
115
|
+
console.log('⚠️ memory_embedding 테이블이 없습니다. 테스트 스킵');
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
closeDatabase(db);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @given 공통 모듈(initializeDatabase)을 사용하는 regenerate-embeddings 함수
|
|
124
|
+
* @when 차원 일치성 확인
|
|
125
|
+
* @then 차원 불일치가 정상적으로 감지됨
|
|
126
|
+
*/
|
|
127
|
+
it('should detect dimension mismatches correctly', async () => {
|
|
128
|
+
// Given: 정상적인 데이터베이스 초기화
|
|
129
|
+
const db = await initializeDatabase();
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
// memory_embedding 테이블 확인
|
|
133
|
+
const tableExists = db.prepare(`
|
|
134
|
+
SELECT name FROM sqlite_master
|
|
135
|
+
WHERE type='table' AND name='memory_embedding'
|
|
136
|
+
`).get();
|
|
137
|
+
|
|
138
|
+
if (tableExists) {
|
|
139
|
+
// When: 차원 일치성 확인
|
|
140
|
+
const expectedDim = 384; // 예상 차원
|
|
141
|
+
const mismatchedDims = db.prepare(`
|
|
142
|
+
SELECT COUNT(*) as count FROM memory_embedding WHERE dim != ?
|
|
143
|
+
`).get(expectedDim) as { count: number };
|
|
144
|
+
|
|
145
|
+
// Then: 차원 불일치가 정상적으로 감지됨
|
|
146
|
+
expect(mismatchedDims).toBeDefined();
|
|
147
|
+
expect(mismatchedDims.count).toBeGreaterThanOrEqual(0);
|
|
148
|
+
} else {
|
|
149
|
+
console.log('⚠️ memory_embedding 테이블이 없습니다. 테스트 스킵');
|
|
150
|
+
}
|
|
151
|
+
} finally {
|
|
152
|
+
closeDatabase(db);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @given 공통 모듈(initializeDatabase)을 사용하는 regenerate-embeddings 함수
|
|
158
|
+
* @when 빈 기억 목록 처리
|
|
159
|
+
* @then 적절한 메시지 출력 및 종료
|
|
160
|
+
*/
|
|
161
|
+
it('should handle empty memory list gracefully', async () => {
|
|
162
|
+
// Given: 정상적인 데이터베이스 초기화
|
|
163
|
+
const db = await initializeDatabase();
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// memory_item 테이블 확인
|
|
167
|
+
const tableExists = db.prepare(`
|
|
168
|
+
SELECT name FROM sqlite_master
|
|
169
|
+
WHERE type='table' AND name='memory_item'
|
|
170
|
+
`).get();
|
|
171
|
+
|
|
172
|
+
if (tableExists) {
|
|
173
|
+
// When: 기억 조회
|
|
174
|
+
const memories = db.prepare(`
|
|
175
|
+
SELECT id, content, type, importance, created_at
|
|
176
|
+
FROM memory_item
|
|
177
|
+
ORDER BY created_at
|
|
178
|
+
`).all();
|
|
179
|
+
|
|
180
|
+
// Then: 빈 목록도 정상적으로 처리됨
|
|
181
|
+
expect(memories).toBeDefined();
|
|
182
|
+
expect(Array.isArray(memories)).toBe(true);
|
|
183
|
+
// 빈 목록인 경우에도 에러 없이 처리되어야 함
|
|
184
|
+
} else {
|
|
185
|
+
console.log('⚠️ memory_item 테이블이 없습니다. 테스트 스킵');
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
closeDatabase(db);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* 임베딩 백업 스크립트
|
|
5
5
|
* 기존 벡터값을 백업한 후 삭제하고 재생성하는 스크립트
|
|
6
|
+
*
|
|
7
|
+
* 리팩토링: 공통 모듈(initializeDatabase)을 사용하여 일관된 DB 초기화 보장
|
|
8
|
+
*
|
|
9
|
+
* 사용법:
|
|
10
|
+
* - 개발 환경: npx tsx scripts/backup-embeddings.js
|
|
11
|
+
* - 프로덕션: npm run build && node dist/scripts/backup-embeddings.js
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
|
-
import
|
|
14
|
+
// TypeScript 소스를 직접 import (tsx로 실행 시)
|
|
15
|
+
// 빌드된 파일을 사용하려면 '../dist/infrastructure/database/database/init.js'로 변경
|
|
16
|
+
import { initializeDatabase, closeDatabase } from '../src/infrastructure/database/database/init.js';
|
|
9
17
|
import fs from 'fs';
|
|
10
18
|
import path from 'path';
|
|
11
19
|
import { fileURLToPath } from 'url';
|
|
@@ -14,17 +22,9 @@ import { validateFilePath, sanitizeFileName } from '../src/shared/utils/path-val
|
|
|
14
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
23
|
const __dirname = path.dirname(__filename);
|
|
16
24
|
|
|
17
|
-
//
|
|
25
|
+
// 백업 디렉토리 설정
|
|
18
26
|
// PRD 0019: 보안 강화 (Phase 1) - Path Traversal 방지
|
|
19
|
-
const
|
|
20
|
-
if (!validateFilePath(dbPath, 'data')) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
`Path Traversal 방지: 허용되지 않은 데이터베이스 경로입니다. ` +
|
|
23
|
-
`경로: ${dbPath}`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const backupDir = path.join(__dirname, '..', 'backup');
|
|
27
|
+
const backupDir = path.join(process.cwd(), 'backup');
|
|
28
28
|
if (!validateFilePath(backupDir, 'backup')) {
|
|
29
29
|
throw new Error(
|
|
30
30
|
`Path Traversal 방지: 허용되지 않은 백업 디렉토리 경로입니다. ` +
|
|
@@ -46,83 +46,74 @@ async function backupEmbeddings() {
|
|
|
46
46
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
const db = new Database(dbPath);
|
|
49
|
+
let db = null;
|
|
51
50
|
|
|
52
51
|
try {
|
|
53
|
-
//
|
|
52
|
+
// 공통 모듈을 사용하여 데이터베이스 초기화
|
|
53
|
+
// initializeDatabase는 DB 파일이 없으면 자동으로 생성하고 초기화함
|
|
54
|
+
db = await initializeDatabase();
|
|
55
|
+
|
|
56
|
+
// 모든 임베딩 데이터 조회
|
|
54
57
|
const embeddings = db.prepare(`
|
|
55
58
|
SELECT
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
mi.type
|
|
63
|
-
FROM memory_embedding me
|
|
64
|
-
JOIN memory_item mi ON me.memory_id = mi.id
|
|
65
|
-
ORDER BY me.created_at
|
|
59
|
+
memory_id,
|
|
60
|
+
embedding,
|
|
61
|
+
dim,
|
|
62
|
+
model,
|
|
63
|
+
created_at
|
|
64
|
+
FROM memory_embedding
|
|
66
65
|
`).all();
|
|
67
66
|
|
|
68
|
-
console.log(`📊
|
|
67
|
+
console.log(`📊 백업할 임베딩 개수: ${embeddings.length}`);
|
|
69
68
|
|
|
70
69
|
if (embeddings.length === 0) {
|
|
71
70
|
console.log('⚠️ 백업할 임베딩이 없습니다.');
|
|
72
71
|
return;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
//
|
|
76
|
-
const dimensionStats = {};
|
|
77
|
-
embeddings.forEach(emb => {
|
|
78
|
-
const dim = emb.dim;
|
|
79
|
-
dimensionStats[dim] = (dimensionStats[dim] || 0) + 1;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
console.log('📈 차원별 통계:');
|
|
83
|
-
Object.entries(dimensionStats).forEach(([dim, count]) => {
|
|
84
|
-
console.log(` - ${dim}차원: ${count}개`);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// 백업 데이터 생성
|
|
74
|
+
// 백업 데이터 준비
|
|
88
75
|
const backupData = {
|
|
89
76
|
timestamp: new Date().toISOString(),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
dim: emb.dim,
|
|
98
|
-
model: emb.model,
|
|
99
|
-
created_at: emb.created_at
|
|
77
|
+
count: embeddings.length,
|
|
78
|
+
embeddings: embeddings.map(e => ({
|
|
79
|
+
memory_id: e.memory_id,
|
|
80
|
+
embedding: e.embedding,
|
|
81
|
+
dim: e.dim,
|
|
82
|
+
model: e.model,
|
|
83
|
+
created_at: e.created_at
|
|
100
84
|
}))
|
|
101
85
|
};
|
|
102
86
|
|
|
103
|
-
//
|
|
104
|
-
fs.writeFileSync(backupFile, JSON.stringify(backupData, null, 2));
|
|
87
|
+
// JSON 파일로 저장
|
|
88
|
+
fs.writeFileSync(backupFile, JSON.stringify(backupData, null, 2), 'utf8');
|
|
105
89
|
console.log(`✅ 백업 완료: ${backupFile}`);
|
|
90
|
+
console.log(`📦 백업 크기: ${(fs.statSync(backupFile).size / 1024).toFixed(2)} KB`);
|
|
106
91
|
|
|
107
|
-
//
|
|
108
|
-
console.log('
|
|
109
|
-
|
|
110
|
-
console.log(
|
|
111
|
-
|
|
112
|
-
console.log('🎉 백업 및 삭제 완료!');
|
|
113
|
-
console.log('다음 단계: npm run regenerate-embeddings');
|
|
92
|
+
// 사용자 확인
|
|
93
|
+
console.log('\n⚠️ 백업이 완료되었습니다.');
|
|
94
|
+
console.log('다음 단계로 임베딩을 삭제하고 재생성할 수 있습니다.');
|
|
95
|
+
console.log('백업 파일:', backupFile);
|
|
114
96
|
|
|
115
97
|
} catch (error) {
|
|
116
|
-
console.error('❌ 백업 실패:', error);
|
|
98
|
+
console.error('❌ 백업 실패:', error.message);
|
|
99
|
+
if (error.stack) {
|
|
100
|
+
console.error(' 스택 트레이스:', error.stack);
|
|
101
|
+
}
|
|
117
102
|
process.exit(1);
|
|
118
103
|
} finally {
|
|
119
|
-
|
|
104
|
+
// 데이터베이스 연결 종료
|
|
105
|
+
if (db) {
|
|
106
|
+
closeDatabase(db);
|
|
107
|
+
}
|
|
120
108
|
}
|
|
121
109
|
}
|
|
122
110
|
|
|
123
111
|
// 스크립트 실행
|
|
124
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
125
|
-
backupEmbeddings().catch(
|
|
112
|
+
if (import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith(process.argv[1])) {
|
|
113
|
+
backupEmbeddings().catch((error) => {
|
|
114
|
+
console.error('❌ 스크립트 실행 중 오류 발생:', error);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
126
117
|
}
|
|
127
118
|
|
|
128
119
|
export { backupEmbeddings };
|
|
@@ -1,46 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Memento 데이터베이스 무결성 검사 스크립트
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* 리팩토링: 공통 모듈(initializeDatabase)을 사용하여 일관된 DB 초기화 보장
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* - 개발 환경: npx tsx scripts/check-db-integrity.js
|
|
9
|
+
* - 프로덕션: npm run build && node dist/scripts/check-db-integrity.js
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
// TypeScript 소스를 직접 import (tsx로 실행 시)
|
|
13
|
+
// 빌드된 파일을 사용하려면 '../dist/infrastructure/database/database/init.js'로 변경
|
|
14
|
+
import { initializeDatabase, closeDatabase } from '../src/infrastructure/database/database/init.js';
|
|
15
|
+
import { existsSync, mkdirSync, appendFileSync } from 'fs';
|
|
16
|
+
import { join } from 'path';
|
|
10
17
|
|
|
11
|
-
const DB_PATH = './data/memory.db';
|
|
12
18
|
const LOG_PATH = './logs/db-integrity.log';
|
|
13
19
|
|
|
14
20
|
// 로그 디렉토리 생성
|
|
15
|
-
if (!
|
|
16
|
-
|
|
21
|
+
if (!existsSync('./logs')) {
|
|
22
|
+
mkdirSync('./logs', { recursive: true });
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
/**
|
|
26
|
+
* 로그 메시지 출력 및 파일 기록
|
|
27
|
+
*/
|
|
19
28
|
function log(message) {
|
|
20
29
|
const timestamp = new Date().toISOString();
|
|
21
30
|
const logMessage = `[${timestamp}] ${message}\n`;
|
|
22
31
|
console.log(message);
|
|
23
|
-
|
|
32
|
+
appendFileSync(LOG_PATH, logMessage);
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
/**
|
|
36
|
+
* 데이터베이스 무결성 검사
|
|
37
|
+
*
|
|
38
|
+
* @returns {Promise<boolean>} 검사 통과 여부
|
|
39
|
+
*/
|
|
40
|
+
async function checkDatabaseIntegrity() {
|
|
27
41
|
log('데이터베이스 무결성 검사 시작...');
|
|
28
42
|
|
|
43
|
+
let db = null;
|
|
44
|
+
|
|
29
45
|
try {
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 데이터베이스 연결 테스트
|
|
37
|
-
const db = new Database(DB_PATH);
|
|
46
|
+
// 공통 모듈을 사용하여 데이터베이스 초기화
|
|
47
|
+
// initializeDatabase는 DB 파일이 없으면 자동으로 생성하고 초기화함
|
|
48
|
+
db = await initializeDatabase();
|
|
38
49
|
|
|
39
50
|
// PRAGMA integrity_check 실행
|
|
40
51
|
const integrityResult = db.prepare('PRAGMA integrity_check').get();
|
|
41
52
|
if (integrityResult.integrity_check !== 'ok') {
|
|
42
53
|
log(`❌ 데이터베이스 무결성 검사 실패: ${integrityResult.integrity_check}`);
|
|
43
|
-
db.close();
|
|
44
54
|
return false;
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -52,7 +62,7 @@ function checkDatabaseIntegrity() {
|
|
|
52
62
|
|
|
53
63
|
if (tables.length < 3) {
|
|
54
64
|
log('❌ 필수 테이블이 누락되었습니다.');
|
|
55
|
-
|
|
65
|
+
log(` 발견된 테이블: ${tables.map(t => t.name).join(', ')}`);
|
|
56
66
|
return false;
|
|
57
67
|
}
|
|
58
68
|
|
|
@@ -65,17 +75,27 @@ function checkDatabaseIntegrity() {
|
|
|
65
75
|
log(` - 임베딩: ${embeddingCount.count}개`);
|
|
66
76
|
log(` - 테이블: ${tables.length}개`);
|
|
67
77
|
|
|
68
|
-
db.close();
|
|
69
78
|
return true;
|
|
70
79
|
|
|
71
80
|
} catch (error) {
|
|
72
81
|
log(`❌ 데이터베이스 검사 중 오류 발생: ${error.message}`);
|
|
82
|
+
if (error.stack) {
|
|
83
|
+
log(` 스택 트레이스: ${error.stack}`);
|
|
84
|
+
}
|
|
73
85
|
return false;
|
|
86
|
+
} finally {
|
|
87
|
+
// 데이터베이스 연결 종료
|
|
88
|
+
if (db) {
|
|
89
|
+
closeDatabase(db);
|
|
90
|
+
}
|
|
74
91
|
}
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
/**
|
|
95
|
+
* 메인 함수
|
|
96
|
+
*/
|
|
97
|
+
async function main() {
|
|
98
|
+
const isHealthy = await checkDatabaseIntegrity();
|
|
79
99
|
|
|
80
100
|
if (!isHealthy) {
|
|
81
101
|
log('🚨 데이터베이스에 문제가 있습니다. 백업에서 복구를 고려하세요.');
|
|
@@ -86,8 +106,12 @@ function main() {
|
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
// 스크립트가 직접 실행될 때만 main 함수 호출
|
|
110
|
+
if (import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith(process.argv[1])) {
|
|
111
|
+
main().catch((error) => {
|
|
112
|
+
console.error('❌ 스크립트 실행 중 오류 발생:', error);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
});
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
|
|
117
|
+
export { checkDatabaseIntegrity };
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* tsx scripts/check-file-sizes.ts --ci
|
|
10
10
|
* tsx scripts/check-file-sizes.ts --threshold 500
|
|
11
11
|
* tsx scripts/check-file-sizes.ts --directory src/
|
|
12
|
-
* tsx scripts/check-file-sizes.ts --exclude
|
|
12
|
+
* tsx scripts/check-file-sizes.ts --exclude '*.spec.ts'
|
|
13
13
|
*
|
|
14
14
|
* 목표:
|
|
15
15
|
* - 핵심 핸들러/서비스 파일이 500줄 이하
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - 경고/에러 출력
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { readFileSync
|
|
20
|
+
import { readFileSync } from 'fs';
|
|
21
21
|
import { readdir } from 'fs/promises';
|
|
22
22
|
import { join, relative } from 'path';
|
|
23
23
|
|
|
@@ -111,7 +111,7 @@ function printHelp(): void {
|
|
|
111
111
|
tsx scripts/check-file-sizes.ts
|
|
112
112
|
tsx scripts/check-file-sizes.ts --ci
|
|
113
113
|
tsx scripts/check-file-sizes.ts --threshold 500
|
|
114
|
-
tsx scripts/check-file-sizes.ts --directory src/ --exclude
|
|
114
|
+
tsx scripts/check-file-sizes.ts --directory src/ --exclude '*.spec.ts'
|
|
115
115
|
`);
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -164,7 +164,7 @@ function validateFileSize(filePath: string, threshold: number): FileSizeResult {
|
|
|
164
164
|
* 패턴 매칭 (간단한 glob 패턴 지원)
|
|
165
165
|
*
|
|
166
166
|
* @param path - 파일 경로
|
|
167
|
-
* @param pattern - 패턴 (예:
|
|
167
|
+
* @param pattern - 패턴 (예: node_modules, *.spec.ts)
|
|
168
168
|
* @returns 매칭 여부
|
|
169
169
|
*/
|
|
170
170
|
function matchesPattern(path: string, pattern: string): boolean {
|
|
@@ -176,9 +176,6 @@ function checkFile(filePath: string): PIIMaskingLocation[] {
|
|
|
176
176
|
// 로거 파일인지 확인 (logger, file-logger, error-logging-service 등)
|
|
177
177
|
const isLoggerFile = /logger|log|error-logging/i.test(relativePath);
|
|
178
178
|
|
|
179
|
-
// PIIMasker import 확인
|
|
180
|
-
const hasPIIMaskerImport = /import.*PIIMasker|from.*pii-masker/.test(content);
|
|
181
|
-
|
|
182
179
|
// logger.error, logger.warn, logger.info, logger.debug 호출 확인
|
|
183
180
|
// logger.ts를 import하는 경우는 이미 마스킹이 적용되어 있으므로 제외
|
|
184
181
|
if (!usesLoggerUtils) {
|
|
@@ -183,18 +183,6 @@ function findSqlInjectionPatterns(filePath: string): SqlInjectionLocation[] {
|
|
|
183
183
|
const content = readFileSync(filePath, 'utf-8');
|
|
184
184
|
const lines = content.split('\n');
|
|
185
185
|
|
|
186
|
-
// 패턴 1: 문자열 연결을 통한 SQL 쿼리 생성
|
|
187
|
-
// sql +=, query +=, sql = sql + 등
|
|
188
|
-
const stringConcatenationPattern = /\b(sql|query|stmt|statement)\s*([+]=|=.*\+)/gi;
|
|
189
|
-
|
|
190
|
-
// 패턴 2: 템플릿 리터럴로 동적 테이블명/컬럼명 사용
|
|
191
|
-
// FROM ${, JOIN ${, WHERE ${ 등 (일부는 허용 가능하지만 검사 대상)
|
|
192
|
-
const templateLiteralPattern = /\b(FROM|JOIN|WHERE|SELECT|INSERT|UPDATE|DELETE|INTO|SET)\s+\$\{/gi;
|
|
193
|
-
|
|
194
|
-
// 패턴 3: 파라미터 바인딩 미사용 (문자열 연결)
|
|
195
|
-
// '...' + variable, "..." + variable (SQL 쿼리 컨텍스트에서)
|
|
196
|
-
const parameterBindingPattern = /(['"]).*?\1\s*\+\s*\w+/g;
|
|
197
|
-
|
|
198
186
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
199
187
|
const line = lines[lineIndex];
|
|
200
188
|
const trimmedLine = line.trim();
|