aiwf 0.3.22 → 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 +294 -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,399 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AIWF Enhanced Resource Loader with Memory Caching
|
|
3
|
-
* 번들된 리소스와 사용자 디렉토리 리소스를 통합 관리하며 메모리 캐싱 기능 제공
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs/promises';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { dirname } from 'path';
|
|
10
|
-
import { existsSync } from 'fs';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = dirname(__filename);
|
|
14
|
-
|
|
15
|
-
// 리소스 기본 경로
|
|
16
|
-
const BUNDLED_RESOURCES_PATH = path.join(__dirname, 'resources');
|
|
17
|
-
const USER_RESOURCES_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.aiwf');
|
|
18
|
-
|
|
19
|
-
export class EnhancedResourceLoader {
|
|
20
|
-
constructor(options = {}) {
|
|
21
|
-
this.bundledPath = options.bundledPath || BUNDLED_RESOURCES_PATH;
|
|
22
|
-
this.userPath = options.userPath || USER_RESOURCES_PATH;
|
|
23
|
-
this.preferUserResources = options.preferUserResources ?? true;
|
|
24
|
-
|
|
25
|
-
// 메모리 캐시 설정
|
|
26
|
-
this.cache = new Map();
|
|
27
|
-
this.cacheEnabled = options.cacheEnabled ?? true;
|
|
28
|
-
this.maxCacheSize = options.maxCacheSize || 100; // 최대 캐시 항목 수
|
|
29
|
-
this.cacheTTL = options.cacheTTL || 5 * 60 * 1000; // 5분 TTL
|
|
30
|
-
|
|
31
|
-
// 캐시 통계
|
|
32
|
-
this.stats = {
|
|
33
|
-
hits: 0,
|
|
34
|
-
misses: 0,
|
|
35
|
-
evictions: 0,
|
|
36
|
-
totalRequests: 0
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// 주기적 캐시 정리 (5분마다)
|
|
40
|
-
if (this.cacheEnabled) {
|
|
41
|
-
this.cleanupInterval = setInterval(() => {
|
|
42
|
-
this.cleanExpiredCache();
|
|
43
|
-
}, 5 * 60 * 1000);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 캐시 키 생성
|
|
49
|
-
*/
|
|
50
|
-
generateCacheKey(resourceType, resourceName, operation = 'load') {
|
|
51
|
-
return `${operation}:${resourceType}:${resourceName}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 캐시에서 리소스 조회
|
|
56
|
-
*/
|
|
57
|
-
getFromCache(cacheKey) {
|
|
58
|
-
if (!this.cacheEnabled) return null;
|
|
59
|
-
|
|
60
|
-
const cached = this.cache.get(cacheKey);
|
|
61
|
-
if (!cached) return null;
|
|
62
|
-
|
|
63
|
-
// TTL 확인
|
|
64
|
-
if (Date.now() > cached.expiresAt) {
|
|
65
|
-
this.cache.delete(cacheKey);
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
this.stats.hits++;
|
|
70
|
-
cached.lastAccessed = Date.now();
|
|
71
|
-
return cached.data;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 캐시에 리소스 저장
|
|
76
|
-
*/
|
|
77
|
-
setCache(cacheKey, data) {
|
|
78
|
-
if (!this.cacheEnabled) return;
|
|
79
|
-
|
|
80
|
-
// 캐시 크기 제한
|
|
81
|
-
if (this.cache.size >= this.maxCacheSize) {
|
|
82
|
-
this.evictLRU();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.cache.set(cacheKey, {
|
|
86
|
-
data,
|
|
87
|
-
createdAt: Date.now(),
|
|
88
|
-
lastAccessed: Date.now(),
|
|
89
|
-
expiresAt: Date.now() + this.cacheTTL
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* LRU 방식으로 캐시 항목 제거
|
|
95
|
-
*/
|
|
96
|
-
evictLRU() {
|
|
97
|
-
let oldestKey = null;
|
|
98
|
-
let oldestTime = Date.now();
|
|
99
|
-
|
|
100
|
-
for (const [key, value] of this.cache.entries()) {
|
|
101
|
-
if (value.lastAccessed < oldestTime) {
|
|
102
|
-
oldestTime = value.lastAccessed;
|
|
103
|
-
oldestKey = key;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (oldestKey) {
|
|
108
|
-
this.cache.delete(oldestKey);
|
|
109
|
-
this.stats.evictions++;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 만료된 캐시 항목 정리
|
|
115
|
-
*/
|
|
116
|
-
cleanExpiredCache() {
|
|
117
|
-
const now = Date.now();
|
|
118
|
-
const keysToDelete = [];
|
|
119
|
-
|
|
120
|
-
for (const [key, value] of this.cache.entries()) {
|
|
121
|
-
if (now > value.expiresAt) {
|
|
122
|
-
keysToDelete.push(key);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
keysToDelete.forEach(key => this.cache.delete(key));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* 리소스 파일 경로 해결 (캐시된 버전)
|
|
131
|
-
* 사용자 디렉토리 우선, 없으면 번들된 리소스 사용
|
|
132
|
-
*/
|
|
133
|
-
async resolvePath(resourceType, resourceName) {
|
|
134
|
-
const cacheKey = this.generateCacheKey(resourceType, resourceName, 'resolve');
|
|
135
|
-
|
|
136
|
-
// 캐시 확인
|
|
137
|
-
const cached = this.getFromCache(cacheKey);
|
|
138
|
-
if (cached) {
|
|
139
|
-
return cached;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
this.stats.misses++;
|
|
143
|
-
this.stats.totalRequests++;
|
|
144
|
-
|
|
145
|
-
const userResourcePath = path.join(this.userPath, resourceType, resourceName);
|
|
146
|
-
const bundledResourcePath = path.join(this.bundledPath, resourceType, resourceName);
|
|
147
|
-
|
|
148
|
-
let resolvedPath = null;
|
|
149
|
-
|
|
150
|
-
if (this.preferUserResources && existsSync(userResourcePath)) {
|
|
151
|
-
resolvedPath = userResourcePath;
|
|
152
|
-
} else if (existsSync(bundledResourcePath)) {
|
|
153
|
-
resolvedPath = bundledResourcePath;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// 결과 캐시
|
|
157
|
-
this.setCache(cacheKey, resolvedPath);
|
|
158
|
-
|
|
159
|
-
return resolvedPath;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 템플릿 로드 (캐시된 버전)
|
|
164
|
-
*/
|
|
165
|
-
async loadTemplate(templateName) {
|
|
166
|
-
const cacheKey = this.generateCacheKey('templates', templateName);
|
|
167
|
-
|
|
168
|
-
// 캐시 확인
|
|
169
|
-
const cached = this.getFromCache(cacheKey);
|
|
170
|
-
if (cached) {
|
|
171
|
-
return cached;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
this.stats.misses++;
|
|
175
|
-
this.stats.totalRequests++;
|
|
176
|
-
|
|
177
|
-
const templatePath = await this.resolvePath('templates', templateName);
|
|
178
|
-
|
|
179
|
-
if (!templatePath) {
|
|
180
|
-
throw new Error(`Template ${templateName} not found`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const content = await fs.readFile(templatePath, 'utf8');
|
|
185
|
-
|
|
186
|
-
// 결과 캐시
|
|
187
|
-
this.setCache(cacheKey, content);
|
|
188
|
-
|
|
189
|
-
return content;
|
|
190
|
-
} catch (error) {
|
|
191
|
-
throw new Error(`Failed to load template ${templateName}: ${error.message}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 페르소나 로드 (캐시된 버전)
|
|
197
|
-
*/
|
|
198
|
-
async loadPersona(personaName) {
|
|
199
|
-
const cacheKey = this.generateCacheKey('personas', personaName);
|
|
200
|
-
|
|
201
|
-
// 캐시 확인
|
|
202
|
-
const cached = this.getFromCache(cacheKey);
|
|
203
|
-
if (cached) {
|
|
204
|
-
return cached;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.stats.misses++;
|
|
208
|
-
this.stats.totalRequests++;
|
|
209
|
-
|
|
210
|
-
const personaPath = await this.resolvePath('personas', `${personaName}.json`);
|
|
211
|
-
|
|
212
|
-
if (!personaPath) {
|
|
213
|
-
throw new Error(`Persona ${personaName} not found`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const content = await fs.readFile(personaPath, 'utf8');
|
|
218
|
-
const personaData = JSON.parse(content);
|
|
219
|
-
|
|
220
|
-
// 결과 캐시
|
|
221
|
-
this.setCache(cacheKey, personaData);
|
|
222
|
-
|
|
223
|
-
return personaData;
|
|
224
|
-
} catch (error) {
|
|
225
|
-
throw new Error(`Failed to load persona ${personaName}: ${error.message}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* 유틸리티 모듈 로드 (캐시된 버전)
|
|
231
|
-
*/
|
|
232
|
-
async loadUtil(utilName) {
|
|
233
|
-
const cacheKey = this.generateCacheKey('utils', utilName);
|
|
234
|
-
|
|
235
|
-
// 캐시 확인 (모듈은 실행 코드이므로 매번 새로 import 필요)
|
|
236
|
-
// 하지만 경로 해결은 캐시 가능
|
|
237
|
-
const pathCacheKey = this.generateCacheKey('utils', utilName, 'path');
|
|
238
|
-
let utilPath = this.getFromCache(pathCacheKey);
|
|
239
|
-
|
|
240
|
-
if (!utilPath) {
|
|
241
|
-
this.stats.misses++;
|
|
242
|
-
utilPath = await this.resolvePath('utils', utilName);
|
|
243
|
-
this.setCache(pathCacheKey, utilPath);
|
|
244
|
-
} else {
|
|
245
|
-
this.stats.hits++;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
this.stats.totalRequests++;
|
|
249
|
-
|
|
250
|
-
if (!utilPath) {
|
|
251
|
-
throw new Error(`Utility ${utilName} not found`);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
// 동적 import (모듈은 캐시하지 않음)
|
|
256
|
-
const module = await import(utilPath);
|
|
257
|
-
return module;
|
|
258
|
-
} catch (error) {
|
|
259
|
-
throw new Error(`Failed to load utility ${utilName}: ${error.message}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* 명령어 템플릿 로드 (캐시된 버전)
|
|
265
|
-
*/
|
|
266
|
-
async loadCommand(commandName) {
|
|
267
|
-
const cacheKey = this.generateCacheKey('commands', commandName);
|
|
268
|
-
|
|
269
|
-
// 캐시 확인
|
|
270
|
-
const cached = this.getFromCache(cacheKey);
|
|
271
|
-
if (cached) {
|
|
272
|
-
return cached;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
this.stats.misses++;
|
|
276
|
-
this.stats.totalRequests++;
|
|
277
|
-
|
|
278
|
-
const commandPath = await this.resolvePath('commands', `${commandName}.md`);
|
|
279
|
-
|
|
280
|
-
if (!commandPath) {
|
|
281
|
-
throw new Error(`Command ${commandName} not found`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
const content = await fs.readFile(commandPath, 'utf8');
|
|
286
|
-
|
|
287
|
-
// 결과 캐시
|
|
288
|
-
this.setCache(cacheKey, content);
|
|
289
|
-
|
|
290
|
-
return content;
|
|
291
|
-
} catch (error) {
|
|
292
|
-
throw new Error(`Failed to load command ${commandName}: ${error.message}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* 사용자 디렉토리 초기화 (캐시된 체크)
|
|
298
|
-
*/
|
|
299
|
-
async initUserDirectory() {
|
|
300
|
-
const cacheKey = 'system:user_directory:initialized';
|
|
301
|
-
|
|
302
|
-
// 캐시 확인
|
|
303
|
-
const cached = this.getFromCache(cacheKey);
|
|
304
|
-
if (cached) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (!existsSync(this.userPath)) {
|
|
309
|
-
await fs.mkdir(this.userPath, { recursive: true });
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// 하위 디렉토리 생성
|
|
313
|
-
const subdirs = ['templates', 'personas', 'utils', 'commands', 'cache'];
|
|
314
|
-
for (const subdir of subdirs) {
|
|
315
|
-
const subdirPath = path.join(this.userPath, subdir);
|
|
316
|
-
if (!existsSync(subdirPath)) {
|
|
317
|
-
await fs.mkdir(subdirPath, { recursive: true });
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// 초기화 완료 캐시
|
|
322
|
-
this.setCache(cacheKey, true);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* 캐시 통계 조회
|
|
327
|
-
*/
|
|
328
|
-
getCacheStats() {
|
|
329
|
-
const hitRate = this.stats.totalRequests > 0
|
|
330
|
-
? (this.stats.hits / this.stats.totalRequests * 100).toFixed(2)
|
|
331
|
-
: 0;
|
|
332
|
-
|
|
333
|
-
return {
|
|
334
|
-
...this.stats,
|
|
335
|
-
hitRate: `${hitRate}%`,
|
|
336
|
-
cacheSize: this.cache.size,
|
|
337
|
-
maxCacheSize: this.maxCacheSize,
|
|
338
|
-
memoryUsage: this.estimateMemoryUsage()
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* 캐시 메모리 사용량 추정
|
|
344
|
-
*/
|
|
345
|
-
estimateMemoryUsage() {
|
|
346
|
-
let totalSize = 0;
|
|
347
|
-
|
|
348
|
-
for (const [key, value] of this.cache.entries()) {
|
|
349
|
-
// 대략적인 크기 추정 (문자열 기준)
|
|
350
|
-
totalSize += key.length * 2; // key size
|
|
351
|
-
if (typeof value.data === 'string') {
|
|
352
|
-
totalSize += value.data.length * 2;
|
|
353
|
-
} else {
|
|
354
|
-
totalSize += JSON.stringify(value.data).length * 2;
|
|
355
|
-
}
|
|
356
|
-
totalSize += 64; // 메타데이터 크기 추정
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return `${(totalSize / 1024).toFixed(2)} KB`;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* 캐시 지우기
|
|
364
|
-
*/
|
|
365
|
-
clearCache() {
|
|
366
|
-
this.cache.clear();
|
|
367
|
-
this.stats = {
|
|
368
|
-
hits: 0,
|
|
369
|
-
misses: 0,
|
|
370
|
-
evictions: 0,
|
|
371
|
-
totalRequests: 0
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* 특정 리소스 타입의 캐시 무효화
|
|
377
|
-
*/
|
|
378
|
-
invalidateResourceType(resourceType) {
|
|
379
|
-
const keysToDelete = [];
|
|
380
|
-
|
|
381
|
-
for (const key of this.cache.keys()) {
|
|
382
|
-
if (key.includes(`:${resourceType}:`)) {
|
|
383
|
-
keysToDelete.push(key);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
keysToDelete.forEach(key => this.cache.delete(key));
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* 리소스 로더 종료 시 정리
|
|
392
|
-
*/
|
|
393
|
-
destroy() {
|
|
394
|
-
if (this.cleanupInterval) {
|
|
395
|
-
clearInterval(this.cleanupInterval);
|
|
396
|
-
}
|
|
397
|
-
this.clearCache();
|
|
398
|
-
}
|
|
399
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AIWF Resource Loader
|
|
3
|
-
* 번들된 리소스와 사용자 디렉토리 리소스를 통합 관리
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs/promises';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { dirname } from 'path';
|
|
10
|
-
import { existsSync } from 'fs';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = dirname(__filename);
|
|
14
|
-
|
|
15
|
-
// 리소스 기본 경로
|
|
16
|
-
const BUNDLED_RESOURCES_PATH = path.join(__dirname, 'resources');
|
|
17
|
-
const USER_RESOURCES_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.aiwf');
|
|
18
|
-
|
|
19
|
-
export class ResourceLoader {
|
|
20
|
-
constructor(options = {}) {
|
|
21
|
-
this.bundledPath = options.bundledPath || BUNDLED_RESOURCES_PATH;
|
|
22
|
-
this.userPath = options.userPath || USER_RESOURCES_PATH;
|
|
23
|
-
this.preferUserResources = options.preferUserResources ?? true;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 리소스 파일 경로 해결
|
|
28
|
-
* 사용자 디렉토리 우선, 없으면 번들된 리소스 사용
|
|
29
|
-
*/
|
|
30
|
-
async resolvePath(resourceType, resourceName) {
|
|
31
|
-
const paths = this.preferUserResources
|
|
32
|
-
? [
|
|
33
|
-
path.join(this.userPath, resourceType, resourceName),
|
|
34
|
-
path.join(this.bundledPath, resourceType, resourceName)
|
|
35
|
-
]
|
|
36
|
-
: [
|
|
37
|
-
path.join(this.bundledPath, resourceType, resourceName),
|
|
38
|
-
path.join(this.userPath, resourceType, resourceName)
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
for (const resourcePath of paths) {
|
|
42
|
-
if (existsSync(resourcePath)) {
|
|
43
|
-
return resourcePath;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
throw new Error(`Resource not found: ${resourceType}/${resourceName}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* JSON 리소스 로드
|
|
52
|
-
*/
|
|
53
|
-
async loadJSON(resourceType, resourceName) {
|
|
54
|
-
const resourcePath = await this.resolvePath(resourceType, resourceName);
|
|
55
|
-
const content = await fs.readFile(resourcePath, 'utf8');
|
|
56
|
-
return JSON.parse(content);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 텍스트 리소스 로드
|
|
61
|
-
*/
|
|
62
|
-
async loadText(resourceType, resourceName) {
|
|
63
|
-
const resourcePath = await this.resolvePath(resourceType, resourceName);
|
|
64
|
-
return await fs.readFile(resourcePath, 'utf8');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 리소스 목록 가져오기
|
|
69
|
-
*/
|
|
70
|
-
async listResources(resourceType, options = {}) {
|
|
71
|
-
const resources = new Set();
|
|
72
|
-
|
|
73
|
-
// 번들된 리소스 목록
|
|
74
|
-
const bundledDir = path.join(this.bundledPath, resourceType);
|
|
75
|
-
if (existsSync(bundledDir)) {
|
|
76
|
-
const bundledFiles = await fs.readdir(bundledDir);
|
|
77
|
-
bundledFiles.forEach(file => resources.add(file));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 사용자 리소스 목록
|
|
81
|
-
const userDir = path.join(this.userPath, resourceType);
|
|
82
|
-
if (existsSync(userDir)) {
|
|
83
|
-
const userFiles = await fs.readdir(userDir);
|
|
84
|
-
userFiles.forEach(file => resources.add(file));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 필터링
|
|
88
|
-
let fileList = Array.from(resources);
|
|
89
|
-
if (options.extension) {
|
|
90
|
-
fileList = fileList.filter(file => file.endsWith(options.extension));
|
|
91
|
-
}
|
|
92
|
-
if (options.filter) {
|
|
93
|
-
fileList = fileList.filter(options.filter);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return fileList.sort();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 페르소나 로드
|
|
101
|
-
*/
|
|
102
|
-
async loadPersona(personaName) {
|
|
103
|
-
if (!personaName.endsWith('.json')) {
|
|
104
|
-
personaName += '.json';
|
|
105
|
-
}
|
|
106
|
-
return await this.loadJSON('personas', personaName);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 모든 페르소나 목록
|
|
111
|
-
*/
|
|
112
|
-
async listPersonas() {
|
|
113
|
-
const personas = await this.listResources('personas', {
|
|
114
|
-
extension: '.json',
|
|
115
|
-
filter: (file) => file !== 'persona-index.json' && file !== 'evaluation_criteria.json'
|
|
116
|
-
});
|
|
117
|
-
return personas.map(file => path.basename(file, '.json'));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 템플릿 설정 로드
|
|
122
|
-
*/
|
|
123
|
-
async loadTemplateConfig(templateName) {
|
|
124
|
-
return await this.loadJSON('templates', path.join(templateName, 'config.json'));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 템플릿 목록
|
|
129
|
-
*/
|
|
130
|
-
async listTemplates() {
|
|
131
|
-
const templates = [];
|
|
132
|
-
const templateDir = path.join(this.bundledPath, 'templates');
|
|
133
|
-
|
|
134
|
-
if (existsSync(templateDir)) {
|
|
135
|
-
const entries = await fs.readdir(templateDir, { withFileTypes: true });
|
|
136
|
-
for (const entry of entries) {
|
|
137
|
-
if (entry.isDirectory()) {
|
|
138
|
-
const configPath = path.join(templateDir, entry.name, 'config.json');
|
|
139
|
-
if (existsSync(configPath)) {
|
|
140
|
-
templates.push(entry.name);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return templates;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* 템플릿 경로 가져오기
|
|
151
|
-
*/
|
|
152
|
-
async getTemplatePath(templateName) {
|
|
153
|
-
const templatePath = await this.resolvePath('templates', templateName);
|
|
154
|
-
const configPath = path.join(templatePath, 'config.json');
|
|
155
|
-
|
|
156
|
-
if (!existsSync(configPath)) {
|
|
157
|
-
throw new Error(`Invalid template: ${templateName}`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return path.join(templatePath, 'template');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 명령어 모듈 로드
|
|
165
|
-
*/
|
|
166
|
-
async loadCommand(commandName) {
|
|
167
|
-
if (!commandName.endsWith('.js')) {
|
|
168
|
-
commandName += '.js';
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const commandPath = await this.resolvePath('commands', commandName);
|
|
172
|
-
return await import(commandPath);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 유틸리티 모듈 로드
|
|
177
|
-
*/
|
|
178
|
-
async loadUtil(utilName) {
|
|
179
|
-
if (!utilName.endsWith('.js')) {
|
|
180
|
-
utilName += '.js';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const utilPath = await this.resolvePath('utils', utilName);
|
|
184
|
-
return await import(utilPath);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* 사용자 리소스 디렉토리 초기화
|
|
189
|
-
*/
|
|
190
|
-
async initUserDirectory() {
|
|
191
|
-
const dirs = ['personas', 'templates', 'commands', 'utils', 'config'];
|
|
192
|
-
|
|
193
|
-
for (const dir of dirs) {
|
|
194
|
-
const dirPath = path.join(this.userPath, dir);
|
|
195
|
-
if (!existsSync(dirPath)) {
|
|
196
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 기본 설정 파일 복사
|
|
201
|
-
const configFiles = ['language.json', 'personas/current.json'];
|
|
202
|
-
for (const configFile of configFiles) {
|
|
203
|
-
const userFile = path.join(this.userPath, configFile);
|
|
204
|
-
if (!existsSync(userFile)) {
|
|
205
|
-
try {
|
|
206
|
-
const bundledFile = path.join(this.bundledPath, configFile);
|
|
207
|
-
if (existsSync(bundledFile)) {
|
|
208
|
-
await fs.copyFile(bundledFile, userFile);
|
|
209
|
-
}
|
|
210
|
-
} catch (error) {
|
|
211
|
-
// 기본 설정 파일이 없어도 계속 진행
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* 리소스 존재 여부 확인
|
|
219
|
-
*/
|
|
220
|
-
async exists(resourceType, resourceName) {
|
|
221
|
-
try {
|
|
222
|
-
await this.resolvePath(resourceType, resourceName);
|
|
223
|
-
return true;
|
|
224
|
-
} catch {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* 리소스 복사 (번들 -> 사용자 디렉토리)
|
|
231
|
-
*/
|
|
232
|
-
async copyToUserDirectory(resourceType, resourceName) {
|
|
233
|
-
const bundledPath = path.join(this.bundledPath, resourceType, resourceName);
|
|
234
|
-
const userPath = path.join(this.userPath, resourceType, resourceName);
|
|
235
|
-
|
|
236
|
-
if (!existsSync(bundledPath)) {
|
|
237
|
-
throw new Error(`Bundled resource not found: ${resourceType}/${resourceName}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
await fs.mkdir(path.dirname(userPath), { recursive: true });
|
|
241
|
-
await fs.copyFile(bundledPath, userPath);
|
|
242
|
-
|
|
243
|
-
return userPath;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// 기본 인스턴스 export
|
|
248
|
-
export const defaultLoader = new ResourceLoader();
|
|
249
|
-
|
|
250
|
-
export default ResourceLoader;
|