aiwf 0.3.21 → 0.3.23
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/package.json +3 -2
- package/src/cli/index.js +39 -336
- package/src/lib/installer.js +310 -90
- package/src/utils/messages.js +24 -0
- package/src/DEPENDENCY_MAP.md +0 -90
- package/src/cli/cache-cli.js +0 -459
- package/src/cli/checkpoint-cli.js +0 -417
- package/src/cli/language-cli.js +0 -286
- package/src/cli/sprint-cli.js +0 -287
- package/src/commands/ai-tool.js +0 -383
- package/src/commands/compress.js +0 -60
- package/src/commands/create-project.js +0 -160
- package/src/commands/evaluate.js +0 -459
- package/src/commands/persona.js +0 -309
- package/src/commands/sprint-task.js +0 -262
- package/src/commands/token.js +0 -312
- package/src/lib/ai-persona-manager.js +0 -711
- package/src/lib/cache-system.js +0 -332
- package/src/lib/context-engine.js +0 -568
- package/src/lib/github-integration.js +0 -402
- package/src/lib/memory-profiler.js +0 -471
- package/src/lib/metrics-collector.js +0 -885
- package/src/lib/offline-detector.js +0 -386
- package/src/lib/resource-loader-enhanced.js +0 -399
- package/src/lib/resource-loader.js +0 -250
- package/src/lib/resources/commands/ai-persona.js +0 -602
- package/src/lib/resources/commands/compress-context.js +0 -389
- package/src/lib/resources/commands/evaluate.js +0 -246
- package/src/lib/resources/commands/persona-context-apply.js +0 -562
- package/src/lib/resources/commands/token-tracking.js +0 -443
- package/src/lib/resources/config/commit-patterns.js +0 -103
- package/src/lib/resources/config/language.json +0 -6
- package/src/lib/resources/personas/PERSONA_INDEX.md +0 -57
- package/src/lib/resources/personas/analyst.json +0 -44
- package/src/lib/resources/personas/architect/best_practices.md +0 -136
- package/src/lib/resources/personas/architect/knowledge_base.md +0 -377
- package/src/lib/resources/personas/architect.json +0 -44
- package/src/lib/resources/personas/backend/best_practices.md +0 -766
- package/src/lib/resources/personas/backend/knowledge_base.md +0 -1070
- package/src/lib/resources/personas/data_analyst/best_practices.md +0 -594
- package/src/lib/resources/personas/data_analyst/knowledge_base.md +0 -1057
- package/src/lib/resources/personas/developer.json +0 -44
- package/src/lib/resources/personas/developer.md +0 -37
- package/src/lib/resources/personas/evaluation_criteria.json +0 -137
- package/src/lib/resources/personas/frontend/best_practices.md +0 -445
- package/src/lib/resources/personas/frontend/knowledge_base.md +0 -729
- package/src/lib/resources/personas/persona-index.json +0 -31
- package/src/lib/resources/personas/reviewer.json +0 -44
- package/src/lib/resources/personas/security/best_practices.md +0 -307
- package/src/lib/resources/personas/security/knowledge_base.md +0 -498
- package/src/lib/resources/personas/tester.json +0 -44
- package/src/lib/resources/templates/README.md +0 -78
- package/src/lib/resources/templates/api-server/config.json +0 -64
- package/src/lib/resources/templates/api-server/template/.aiwf/config.json +0 -56
- package/src/lib/resources/templates/api-server/template/.aiwf/feature-ledger.json +0 -42
- package/src/lib/resources/templates/api-server/template/.aiwf/personas/backend-engineer.json +0 -40
- package/src/lib/resources/templates/api-server/template/.aiwf/scripts/cli.js +0 -111
- package/src/lib/resources/templates/api-server/template/.env.example +0 -29
- package/src/lib/resources/templates/api-server/template/.eslintrc.json +0 -23
- package/src/lib/resources/templates/api-server/template/README.md +0 -171
- package/src/lib/resources/templates/api-server/template/jest.config.js +0 -26
- package/src/lib/resources/templates/api-server/template/nodemon.json +0 -9
- package/src/lib/resources/templates/api-server/template/package.json +0 -61
- package/src/lib/resources/templates/api-server/template/src/app.ts +0 -60
- package/src/lib/resources/templates/api-server/template/src/config/swagger.ts +0 -38
- package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +0 -54
- package/src/lib/resources/templates/api-server/template/src/controllers/authController.ts +0 -128
- package/src/lib/resources/templates/api-server/template/src/controllers/statusController.ts +0 -28
- package/src/lib/resources/templates/api-server/template/src/index.ts +0 -28
- package/src/lib/resources/templates/api-server/template/src/middleware/aiwfMiddleware.ts +0 -50
- package/src/lib/resources/templates/api-server/template/src/middleware/authMiddleware.ts +0 -41
- package/src/lib/resources/templates/api-server/template/src/middleware/errorHandler.ts +0 -42
- package/src/lib/resources/templates/api-server/template/src/middleware/notFoundHandler.ts +0 -14
- package/src/lib/resources/templates/api-server/template/src/middleware/requestLogger.ts +0 -20
- package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +0 -31
- package/src/lib/resources/templates/api-server/template/src/routes/index.ts +0 -13
- package/src/lib/resources/templates/api-server/template/src/routes/v1/index.ts +0 -67
- package/src/lib/resources/templates/api-server/template/src/utils/logger.ts +0 -37
- package/src/lib/resources/templates/api-server/template/tests/app.test.ts +0 -40
- package/src/lib/resources/templates/api-server/template/tsconfig.json +0 -43
- package/src/lib/resources/templates/npm-library/config.json +0 -61
- package/src/lib/resources/templates/npm-library/template/LICENSE +0 -21
- package/src/lib/resources/templates/npm-library/template/README.md +0 -201
- package/src/lib/resources/templates/npm-library/template/package.json +0 -78
- package/src/lib/resources/templates/web-app/config.json +0 -53
- package/src/lib/resources/templates/web-app/template/.aiwf/config.json +0 -54
- package/src/lib/resources/templates/web-app/template/.aiwf/feature-ledger.json +0 -37
- package/src/lib/resources/templates/web-app/template/.aiwf/personas/fullstack-developer.json +0 -40
- package/src/lib/resources/templates/web-app/template/.aiwf/scripts/cli.js +0 -110
- package/src/lib/resources/templates/web-app/template/.eslintrc.cjs +0 -20
- package/src/lib/resources/templates/web-app/template/README.md +0 -151
- package/src/lib/resources/templates/web-app/template/index.html +0 -14
- package/src/lib/resources/templates/web-app/template/package.json +0 -44
- package/src/lib/resources/templates/web-app/template/postcss.config.js +0 -6
- package/src/lib/resources/templates/web-app/template/public/vite.svg +0 -1
- package/src/lib/resources/templates/web-app/template/src/App.tsx +0 -21
- package/src/lib/resources/templates/web-app/template/src/components/Layout.tsx +0 -57
- package/src/lib/resources/templates/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -107
- package/src/lib/resources/templates/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -71
- package/src/lib/resources/templates/web-app/template/src/index.css +0 -60
- package/src/lib/resources/templates/web-app/template/src/main.tsx +0 -10
- package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +0 -57
- package/src/lib/resources/templates/web-app/template/src/pages/HomePage.tsx +0 -89
- package/src/lib/resources/templates/web-app/template/src/pages/NotFound.tsx +0 -25
- package/src/lib/resources/templates/web-app/template/src/stores/aiwfStore.ts +0 -126
- package/src/lib/resources/templates/web-app/template/src/types/global.d.ts +0 -9
- package/src/lib/resources/templates/web-app/template/src/vite-env.d.ts +0 -1
- package/src/lib/resources/templates/web-app/template/tailwind.config.js +0 -30
- package/src/lib/resources/templates/web-app/template/tsconfig.json +0 -37
- package/src/lib/resources/templates/web-app/template/tsconfig.node.json +0 -10
- package/src/lib/resources/templates/web-app/template/vite.config.ts +0 -23
- package/src/lib/resources/utils/background-monitor.js +0 -223
- package/src/lib/resources/utils/compression-metrics.js +0 -1031
- package/src/lib/resources/utils/compression-strategies.js +0 -557
- package/src/lib/resources/utils/content-normalizer.js +0 -455
- package/src/lib/resources/utils/context-compressor.js +0 -583
- package/src/lib/resources/utils/context-rule-parser.js +0 -180
- package/src/lib/resources/utils/context-token-monitor.js +0 -463
- package/src/lib/resources/utils/context-update-manager.js +0 -502
- package/src/lib/resources/utils/git-utils.js +0 -220
- package/src/lib/resources/utils/importance-classifier.js +0 -645
- package/src/lib/resources/utils/information-filter.js +0 -710
- package/src/lib/resources/utils/persona-aware-compressor.js +0 -598
- package/src/lib/resources/utils/prompt-injector.js +0 -242
- package/src/lib/resources/utils/simplified-evaluator.js +0 -119
- package/src/lib/resources/utils/text-summarizer.js +0 -492
- package/src/lib/resources/utils/token-counter.js +0 -177
- package/src/lib/resources/utils/token-monitor.js +0 -484
- package/src/lib/resources/utils/token-reporter.js +0 -452
- package/src/lib/resources/utils/token-storage.js +0 -433
- package/src/lib/resources/utils/token-tracker.js +0 -283
- package/src/lib/state/priority-calculator.js +0 -217
- package/src/lib/state/state-index.js +0 -155
- package/src/lib/state/task-scanner.js +0 -328
- package/src/lib/task-analyzer.js +0 -599
- package/src/lib/template-cache-system.js +0 -559
- package/src/lib/template-downloader.js +0 -467
- package/src/lib/template-version-manager.js +0 -491
- package/src/lib/token-optimizer.js +0 -517
- package/src/utils/engineering-guard.js +0 -399
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { createHash } from 'crypto';
|
|
6
|
-
import { createReadStream, createWriteStream } from 'fs';
|
|
7
|
-
import { pipeline } from 'stream/promises';
|
|
8
|
-
import { createGzip, createGunzip } from 'zlib';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 오프라인 템플릿 캐시 시스템
|
|
13
|
-
* - 모든 AI 도구 템플릿과 프로젝트 템플릿 캐시 관리
|
|
14
|
-
* - 압축 저장으로 디스크 사용량 최적화
|
|
15
|
-
* - 버전 관리 및 자동 업데이트
|
|
16
|
-
* - 네트워크 상태 감지 및 오프라인 폴백
|
|
17
|
-
*/
|
|
18
|
-
class TemplateCache {
|
|
19
|
-
constructor(options = {}) {
|
|
20
|
-
this.cacheDir = options.cacheDir || path.join(process.env.HOME || process.env.USERPROFILE, '.aiwf', 'cache');
|
|
21
|
-
this.templatesDir = path.join(this.cacheDir, 'templates');
|
|
22
|
-
this.aiToolsDir = path.join(this.templatesDir, 'ai-tools');
|
|
23
|
-
this.projectsDir = path.join(this.templatesDir, 'projects');
|
|
24
|
-
this.manifestPath = path.join(this.cacheDir, 'manifest.json');
|
|
25
|
-
this.lockPath = path.join(this.cacheDir, 'lock.json');
|
|
26
|
-
|
|
27
|
-
// 기본 설정
|
|
28
|
-
this.maxCacheSize = options.maxCacheSize || 100 * 1024 * 1024; // 100MB
|
|
29
|
-
this.compressionLevel = options.compressionLevel || 6;
|
|
30
|
-
this.updateCheckInterval = options.updateCheckInterval || 24 * 60 * 60 * 1000; // 24시간
|
|
31
|
-
|
|
32
|
-
// 내부 상태
|
|
33
|
-
this.manifest = null;
|
|
34
|
-
this.lock = null;
|
|
35
|
-
this.stats = {
|
|
36
|
-
totalSize: 0,
|
|
37
|
-
templatesCount: 0,
|
|
38
|
-
lastUpdate: null,
|
|
39
|
-
hitCount: 0,
|
|
40
|
-
missCount: 0
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 캐시 시스템 초기화
|
|
46
|
-
*/
|
|
47
|
-
async init() {
|
|
48
|
-
try {
|
|
49
|
-
// 캐시 디렉토리 생성
|
|
50
|
-
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
51
|
-
await fs.mkdir(this.templatesDir, { recursive: true });
|
|
52
|
-
await fs.mkdir(this.aiToolsDir, { recursive: true });
|
|
53
|
-
await fs.mkdir(this.projectsDir, { recursive: true });
|
|
54
|
-
|
|
55
|
-
// 매니페스트 파일 로드 또는 생성
|
|
56
|
-
await this.loadManifest();
|
|
57
|
-
await this.loadLock();
|
|
58
|
-
|
|
59
|
-
// 캐시 상태 계산
|
|
60
|
-
await this.calculateStats();
|
|
61
|
-
|
|
62
|
-
console.log('Template cache system initialized');
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.error('Failed to initialize template cache:', error.message);
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 매니페스트 파일 로드
|
|
71
|
-
*/
|
|
72
|
-
async loadManifest() {
|
|
73
|
-
try {
|
|
74
|
-
const manifestData = await fs.readFile(this.manifestPath, 'utf8');
|
|
75
|
-
this.manifest = JSON.parse(manifestData);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
// 매니페스트 파일이 없으면 새로 생성
|
|
78
|
-
this.manifest = {
|
|
79
|
-
version: '1.0.0',
|
|
80
|
-
templates: {
|
|
81
|
-
'ai-tools': {},
|
|
82
|
-
'projects': {}
|
|
83
|
-
},
|
|
84
|
-
last_update_check: null,
|
|
85
|
-
created_at: new Date().toISOString()
|
|
86
|
-
};
|
|
87
|
-
await this.saveManifest();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 락 파일 로드
|
|
93
|
-
*/
|
|
94
|
-
async loadLock() {
|
|
95
|
-
try {
|
|
96
|
-
const lockData = await fs.readFile(this.lockPath, 'utf8');
|
|
97
|
-
this.lock = JSON.parse(lockData);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
this.lock = {
|
|
100
|
-
locked: false,
|
|
101
|
-
process_id: null,
|
|
102
|
-
operation: null,
|
|
103
|
-
timestamp: null
|
|
104
|
-
};
|
|
105
|
-
await this.saveLock();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 매니페스트 저장
|
|
111
|
-
*/
|
|
112
|
-
async saveManifest() {
|
|
113
|
-
await fs.writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 락 파일 저장
|
|
118
|
-
*/
|
|
119
|
-
async saveLock() {
|
|
120
|
-
await fs.writeFile(this.lockPath, JSON.stringify(this.lock, null, 2));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 캐시 통계 계산
|
|
125
|
-
*/
|
|
126
|
-
async calculateStats() {
|
|
127
|
-
let totalSize = 0;
|
|
128
|
-
let templatesCount = 0;
|
|
129
|
-
|
|
130
|
-
const calculateDirectorySize = async (dir) => {
|
|
131
|
-
try {
|
|
132
|
-
const files = await fs.readdir(dir);
|
|
133
|
-
let dirSize = 0;
|
|
134
|
-
for (const file of files) {
|
|
135
|
-
const filePath = path.join(dir, file);
|
|
136
|
-
const stat = await fs.stat(filePath);
|
|
137
|
-
if (stat.isDirectory()) {
|
|
138
|
-
dirSize += await calculateDirectorySize(filePath);
|
|
139
|
-
} else {
|
|
140
|
-
dirSize += stat.size;
|
|
141
|
-
if (file.endsWith('.tar.gz')) {
|
|
142
|
-
templatesCount++;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return dirSize;
|
|
147
|
-
} catch (error) {
|
|
148
|
-
return 0;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
totalSize = await calculateDirectorySize(this.templatesDir);
|
|
153
|
-
|
|
154
|
-
this.stats = {
|
|
155
|
-
...this.stats,
|
|
156
|
-
totalSize,
|
|
157
|
-
templatesCount,
|
|
158
|
-
lastUpdate: this.manifest.last_update_check
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 템플릿 캐시 키 생성
|
|
164
|
-
*/
|
|
165
|
-
generateCacheKey(type, name, version = '1.0.0') {
|
|
166
|
-
return `${type}/${name}@${version}`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* 템플릿 압축 및 캐시 저장
|
|
171
|
-
*/
|
|
172
|
-
async cacheTemplate(type, name, sourcePath, version = '1.0.0') {
|
|
173
|
-
const cacheKey = this.generateCacheKey(type, name, version);
|
|
174
|
-
const targetDir = type === 'ai-tools' ? this.aiToolsDir : this.projectsDir;
|
|
175
|
-
const cacheFile = path.join(targetDir, `${name}@${version}.tar.gz`);
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
// 디렉토리 압축
|
|
179
|
-
await this.compressDirectory(sourcePath, cacheFile);
|
|
180
|
-
|
|
181
|
-
// 체크섬 계산
|
|
182
|
-
const checksum = await this.calculateChecksum(cacheFile);
|
|
183
|
-
const stat = await fs.stat(cacheFile);
|
|
184
|
-
|
|
185
|
-
// 매니페스트 업데이트
|
|
186
|
-
this.manifest.templates[type][name] = {
|
|
187
|
-
version,
|
|
188
|
-
cached_at: new Date().toISOString(),
|
|
189
|
-
size: stat.size,
|
|
190
|
-
checksum,
|
|
191
|
-
cache_file: cacheFile
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
await this.saveManifest();
|
|
195
|
-
console.log(`Template cached: ${cacheKey}`);
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
success: true,
|
|
199
|
-
cacheKey,
|
|
200
|
-
size: stat.size,
|
|
201
|
-
checksum
|
|
202
|
-
};
|
|
203
|
-
} catch (error) {
|
|
204
|
-
console.error(`Failed to cache template ${cacheKey}:`, error.message);
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* 템플릿 압축 (간단한 방식)
|
|
211
|
-
*/
|
|
212
|
-
async compressDirectory(sourcePath, targetFile) {
|
|
213
|
-
// Node.js 내장 zlib와 fs를 사용한 간단한 압축
|
|
214
|
-
const { createGzip } = await import('zlib');
|
|
215
|
-
const { createReadStream, createWriteStream } = await import('fs');
|
|
216
|
-
|
|
217
|
-
// 디렉토리 내용을 JSON으로 직렬화
|
|
218
|
-
const dirContents = await this.readDirectoryRecursive(sourcePath);
|
|
219
|
-
const jsonData = JSON.stringify(dirContents, null, 2);
|
|
220
|
-
|
|
221
|
-
// 압축하여 저장
|
|
222
|
-
const gzip = createGzip();
|
|
223
|
-
const input = Buffer.from(jsonData);
|
|
224
|
-
const output = createWriteStream(targetFile);
|
|
225
|
-
|
|
226
|
-
return new Promise((resolve, reject) => {
|
|
227
|
-
gzip.pipe(output);
|
|
228
|
-
gzip.on('error', reject);
|
|
229
|
-
output.on('error', reject);
|
|
230
|
-
output.on('finish', resolve);
|
|
231
|
-
gzip.end(input);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* 디렉토리 내용을 재귀적으로 읽기
|
|
237
|
-
*/
|
|
238
|
-
async readDirectoryRecursive(dirPath) {
|
|
239
|
-
const result = {};
|
|
240
|
-
const items = await fs.readdir(dirPath);
|
|
241
|
-
|
|
242
|
-
for (const item of items) {
|
|
243
|
-
const itemPath = path.join(dirPath, item);
|
|
244
|
-
const stat = await fs.stat(itemPath);
|
|
245
|
-
|
|
246
|
-
if (stat.isDirectory()) {
|
|
247
|
-
// node_modules와 .git 제외
|
|
248
|
-
if (!item.includes('node_modules') && !item.includes('.git')) {
|
|
249
|
-
result[item] = await this.readDirectoryRecursive(itemPath);
|
|
250
|
-
}
|
|
251
|
-
} else {
|
|
252
|
-
// 파일 내용 읽기
|
|
253
|
-
try {
|
|
254
|
-
const content = await fs.readFile(itemPath, 'utf8');
|
|
255
|
-
result[item] = {
|
|
256
|
-
type: 'file',
|
|
257
|
-
content: content,
|
|
258
|
-
size: stat.size
|
|
259
|
-
};
|
|
260
|
-
} catch (error) {
|
|
261
|
-
// 바이너리 파일은 base64로 저장
|
|
262
|
-
const content = await fs.readFile(itemPath);
|
|
263
|
-
result[item] = {
|
|
264
|
-
type: 'binary',
|
|
265
|
-
content: content.toString('base64'),
|
|
266
|
-
size: stat.size
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return result;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* 템플릿 압축 해제
|
|
277
|
-
*/
|
|
278
|
-
async extractTemplate(cacheFile, targetPath) {
|
|
279
|
-
const { createGunzip } = await import('zlib');
|
|
280
|
-
const { createReadStream } = await import('fs');
|
|
281
|
-
|
|
282
|
-
// 압축 해제
|
|
283
|
-
const gunzip = createGunzip();
|
|
284
|
-
const input = createReadStream(cacheFile);
|
|
285
|
-
|
|
286
|
-
return new Promise((resolve, reject) => {
|
|
287
|
-
let data = '';
|
|
288
|
-
|
|
289
|
-
input.pipe(gunzip);
|
|
290
|
-
gunzip.on('data', chunk => data += chunk);
|
|
291
|
-
gunzip.on('end', async () => {
|
|
292
|
-
try {
|
|
293
|
-
const dirContents = JSON.parse(data);
|
|
294
|
-
await this.writeDirectoryRecursive(targetPath, dirContents);
|
|
295
|
-
resolve();
|
|
296
|
-
} catch (error) {
|
|
297
|
-
reject(error);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
gunzip.on('error', reject);
|
|
301
|
-
input.on('error', reject);
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* 디렉토리 내용을 재귀적으로 쓰기
|
|
307
|
-
*/
|
|
308
|
-
async writeDirectoryRecursive(dirPath, contents) {
|
|
309
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
310
|
-
|
|
311
|
-
for (const [name, item] of Object.entries(contents)) {
|
|
312
|
-
const itemPath = path.join(dirPath, name);
|
|
313
|
-
|
|
314
|
-
if (item.type === 'file') {
|
|
315
|
-
await fs.writeFile(itemPath, item.content, 'utf8');
|
|
316
|
-
} else if (item.type === 'binary') {
|
|
317
|
-
const buffer = Buffer.from(item.content, 'base64');
|
|
318
|
-
await fs.writeFile(itemPath, buffer);
|
|
319
|
-
} else {
|
|
320
|
-
// 디렉토리
|
|
321
|
-
await this.writeDirectoryRecursive(itemPath, item);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* 파일 체크섬 계산
|
|
328
|
-
*/
|
|
329
|
-
async calculateChecksum(filePath) {
|
|
330
|
-
return new Promise((resolve, reject) => {
|
|
331
|
-
const hash = createHash('sha256');
|
|
332
|
-
const stream = createReadStream(filePath);
|
|
333
|
-
|
|
334
|
-
stream.on('error', reject);
|
|
335
|
-
stream.on('data', chunk => hash.update(chunk));
|
|
336
|
-
stream.on('end', () => resolve(hash.digest('hex')));
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* 캐시된 템플릿 가져오기
|
|
342
|
-
*/
|
|
343
|
-
async getTemplate(type, name, version = '1.0.0') {
|
|
344
|
-
const cacheKey = this.generateCacheKey(type, name, version);
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
const templateInfo = this.manifest.templates[type][name];
|
|
348
|
-
if (!templateInfo) {
|
|
349
|
-
this.stats.missCount++;
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const cacheFile = templateInfo.cache_file;
|
|
354
|
-
if (!await this.fileExists(cacheFile)) {
|
|
355
|
-
this.stats.missCount++;
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// 체크섬 검증
|
|
360
|
-
const currentChecksum = await this.calculateChecksum(cacheFile);
|
|
361
|
-
if (currentChecksum !== templateInfo.checksum) {
|
|
362
|
-
console.warn(`Checksum mismatch for ${cacheKey}, cache may be corrupted`);
|
|
363
|
-
this.stats.missCount++;
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
this.stats.hitCount++;
|
|
368
|
-
return {
|
|
369
|
-
cacheKey,
|
|
370
|
-
cacheFile,
|
|
371
|
-
templateInfo
|
|
372
|
-
};
|
|
373
|
-
} catch (error) {
|
|
374
|
-
console.error(`Failed to get template ${cacheKey}:`, error.message);
|
|
375
|
-
this.stats.missCount++;
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* 템플릿을 대상 디렉토리로 추출
|
|
382
|
-
*/
|
|
383
|
-
async extractToDirectory(type, name, targetPath, version = '1.0.0') {
|
|
384
|
-
const template = await this.getTemplate(type, name, version);
|
|
385
|
-
if (!template) {
|
|
386
|
-
throw new Error(`Template ${type}/${name}@${version} not found in cache`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
await fs.mkdir(targetPath, { recursive: true });
|
|
390
|
-
await this.extractTemplate(template.cacheFile, targetPath);
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
success: true,
|
|
394
|
-
extracted: true,
|
|
395
|
-
targetPath,
|
|
396
|
-
templateInfo: template.templateInfo
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* 파일 존재 확인
|
|
402
|
-
*/
|
|
403
|
-
async fileExists(filePath) {
|
|
404
|
-
try {
|
|
405
|
-
await fs.access(filePath);
|
|
406
|
-
return true;
|
|
407
|
-
} catch {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* 사용 가능한 템플릿 목록 조회
|
|
414
|
-
*/
|
|
415
|
-
async listTemplates(type = null) {
|
|
416
|
-
const result = {};
|
|
417
|
-
|
|
418
|
-
if (type) {
|
|
419
|
-
result[type] = this.manifest.templates[type] || {};
|
|
420
|
-
} else {
|
|
421
|
-
result['ai-tools'] = this.manifest.templates['ai-tools'] || {};
|
|
422
|
-
result['projects'] = this.manifest.templates['projects'] || {};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return result;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* 캐시 정리
|
|
430
|
-
*/
|
|
431
|
-
async cleanup(options = {}) {
|
|
432
|
-
const maxAge = options.maxAge || 7 * 24 * 60 * 60 * 1000; // 7일
|
|
433
|
-
const now = Date.now();
|
|
434
|
-
let removedCount = 0;
|
|
435
|
-
let freedSpace = 0;
|
|
436
|
-
|
|
437
|
-
for (const type of ['ai-tools', 'projects']) {
|
|
438
|
-
const templates = this.manifest.templates[type];
|
|
439
|
-
|
|
440
|
-
for (const [name, templateInfo] of Object.entries(templates)) {
|
|
441
|
-
const cachedAt = new Date(templateInfo.cached_at).getTime();
|
|
442
|
-
|
|
443
|
-
if (now - cachedAt > maxAge) {
|
|
444
|
-
try {
|
|
445
|
-
await fs.unlink(templateInfo.cache_file);
|
|
446
|
-
freedSpace += templateInfo.size;
|
|
447
|
-
removedCount++;
|
|
448
|
-
delete templates[name];
|
|
449
|
-
} catch (error) {
|
|
450
|
-
console.warn(`Failed to remove expired cache: ${name}`, error.message);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
await this.saveManifest();
|
|
457
|
-
await this.calculateStats();
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
removedCount,
|
|
461
|
-
freedSpace,
|
|
462
|
-
totalSize: this.stats.totalSize
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* 전체 캐시 초기화
|
|
468
|
-
*/
|
|
469
|
-
async clear() {
|
|
470
|
-
try {
|
|
471
|
-
await fs.rm(this.templatesDir, { recursive: true, force: true });
|
|
472
|
-
await fs.mkdir(this.templatesDir, { recursive: true });
|
|
473
|
-
await fs.mkdir(this.aiToolsDir, { recursive: true });
|
|
474
|
-
await fs.mkdir(this.projectsDir, { recursive: true });
|
|
475
|
-
|
|
476
|
-
this.manifest.templates = {
|
|
477
|
-
'ai-tools': {},
|
|
478
|
-
'projects': {}
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
await this.saveManifest();
|
|
482
|
-
await this.calculateStats();
|
|
483
|
-
|
|
484
|
-
return {
|
|
485
|
-
success: true,
|
|
486
|
-
message: 'Cache cleared successfully'
|
|
487
|
-
};
|
|
488
|
-
} catch (error) {
|
|
489
|
-
console.error('Failed to clear cache:', error.message);
|
|
490
|
-
throw error;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* 캐시 통계 정보
|
|
496
|
-
*/
|
|
497
|
-
getStats() {
|
|
498
|
-
return {
|
|
499
|
-
...this.stats,
|
|
500
|
-
maxCacheSize: this.maxCacheSize,
|
|
501
|
-
usagePercentage: ((this.stats.totalSize / this.maxCacheSize) * 100).toFixed(2) + '%',
|
|
502
|
-
hitRate: this.stats.hitCount + this.stats.missCount > 0
|
|
503
|
-
? ((this.stats.hitCount / (this.stats.hitCount + this.stats.missCount)) * 100).toFixed(2) + '%'
|
|
504
|
-
: '0%'
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* 캐시 크기 제한 확인
|
|
510
|
-
*/
|
|
511
|
-
async checkSizeLimit() {
|
|
512
|
-
if (this.stats.totalSize > this.maxCacheSize) {
|
|
513
|
-
// 가장 오래된 캐시부터 정리
|
|
514
|
-
const templates = [];
|
|
515
|
-
|
|
516
|
-
for (const type of ['ai-tools', 'projects']) {
|
|
517
|
-
for (const [name, templateInfo] of Object.entries(this.manifest.templates[type])) {
|
|
518
|
-
templates.push({
|
|
519
|
-
type,
|
|
520
|
-
name,
|
|
521
|
-
...templateInfo,
|
|
522
|
-
cachedAt: new Date(templateInfo.cached_at).getTime()
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
templates.sort((a, b) => a.cachedAt - b.cachedAt);
|
|
528
|
-
|
|
529
|
-
let currentSize = this.stats.totalSize;
|
|
530
|
-
let removedCount = 0;
|
|
531
|
-
|
|
532
|
-
for (const template of templates) {
|
|
533
|
-
if (currentSize <= this.maxCacheSize * 0.8) break;
|
|
534
|
-
|
|
535
|
-
try {
|
|
536
|
-
await fs.unlink(template.cache_file);
|
|
537
|
-
currentSize -= template.size;
|
|
538
|
-
removedCount++;
|
|
539
|
-
delete this.manifest.templates[template.type][template.name];
|
|
540
|
-
} catch (error) {
|
|
541
|
-
console.warn(`Failed to remove cache: ${template.name}`, error.message);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
await this.saveManifest();
|
|
546
|
-
await this.calculateStats();
|
|
547
|
-
|
|
548
|
-
return {
|
|
549
|
-
cleanedUp: true,
|
|
550
|
-
removedCount,
|
|
551
|
-
currentSize: this.stats.totalSize
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return { cleanedUp: false };
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
export { TemplateCache };
|