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.
Files changed (140) hide show
  1. package/package.json +3 -2
  2. package/src/cli/index.js +39 -336
  3. package/src/lib/installer.js +294 -90
  4. package/src/utils/messages.js +24 -0
  5. package/src/DEPENDENCY_MAP.md +0 -90
  6. package/src/cli/cache-cli.js +0 -459
  7. package/src/cli/checkpoint-cli.js +0 -417
  8. package/src/cli/language-cli.js +0 -286
  9. package/src/cli/sprint-cli.js +0 -287
  10. package/src/commands/ai-tool.js +0 -383
  11. package/src/commands/compress.js +0 -60
  12. package/src/commands/create-project.js +0 -160
  13. package/src/commands/evaluate.js +0 -459
  14. package/src/commands/persona.js +0 -309
  15. package/src/commands/sprint-task.js +0 -262
  16. package/src/commands/token.js +0 -312
  17. package/src/lib/ai-persona-manager.js +0 -711
  18. package/src/lib/cache-system.js +0 -332
  19. package/src/lib/context-engine.js +0 -568
  20. package/src/lib/github-integration.js +0 -402
  21. package/src/lib/memory-profiler.js +0 -471
  22. package/src/lib/metrics-collector.js +0 -885
  23. package/src/lib/offline-detector.js +0 -386
  24. package/src/lib/resource-loader-enhanced.js +0 -399
  25. package/src/lib/resource-loader.js +0 -250
  26. package/src/lib/resources/commands/ai-persona.js +0 -602
  27. package/src/lib/resources/commands/compress-context.js +0 -389
  28. package/src/lib/resources/commands/evaluate.js +0 -246
  29. package/src/lib/resources/commands/persona-context-apply.js +0 -562
  30. package/src/lib/resources/commands/token-tracking.js +0 -443
  31. package/src/lib/resources/config/commit-patterns.js +0 -103
  32. package/src/lib/resources/config/language.json +0 -6
  33. package/src/lib/resources/personas/PERSONA_INDEX.md +0 -57
  34. package/src/lib/resources/personas/analyst.json +0 -44
  35. package/src/lib/resources/personas/architect/best_practices.md +0 -136
  36. package/src/lib/resources/personas/architect/knowledge_base.md +0 -377
  37. package/src/lib/resources/personas/architect.json +0 -44
  38. package/src/lib/resources/personas/backend/best_practices.md +0 -766
  39. package/src/lib/resources/personas/backend/knowledge_base.md +0 -1070
  40. package/src/lib/resources/personas/data_analyst/best_practices.md +0 -594
  41. package/src/lib/resources/personas/data_analyst/knowledge_base.md +0 -1057
  42. package/src/lib/resources/personas/developer.json +0 -44
  43. package/src/lib/resources/personas/developer.md +0 -37
  44. package/src/lib/resources/personas/evaluation_criteria.json +0 -137
  45. package/src/lib/resources/personas/frontend/best_practices.md +0 -445
  46. package/src/lib/resources/personas/frontend/knowledge_base.md +0 -729
  47. package/src/lib/resources/personas/persona-index.json +0 -31
  48. package/src/lib/resources/personas/reviewer.json +0 -44
  49. package/src/lib/resources/personas/security/best_practices.md +0 -307
  50. package/src/lib/resources/personas/security/knowledge_base.md +0 -498
  51. package/src/lib/resources/personas/tester.json +0 -44
  52. package/src/lib/resources/templates/README.md +0 -78
  53. package/src/lib/resources/templates/api-server/config.json +0 -64
  54. package/src/lib/resources/templates/api-server/template/.aiwf/config.json +0 -56
  55. package/src/lib/resources/templates/api-server/template/.aiwf/feature-ledger.json +0 -42
  56. package/src/lib/resources/templates/api-server/template/.aiwf/personas/backend-engineer.json +0 -40
  57. package/src/lib/resources/templates/api-server/template/.aiwf/scripts/cli.js +0 -111
  58. package/src/lib/resources/templates/api-server/template/.env.example +0 -29
  59. package/src/lib/resources/templates/api-server/template/.eslintrc.json +0 -23
  60. package/src/lib/resources/templates/api-server/template/README.md +0 -171
  61. package/src/lib/resources/templates/api-server/template/jest.config.js +0 -26
  62. package/src/lib/resources/templates/api-server/template/nodemon.json +0 -9
  63. package/src/lib/resources/templates/api-server/template/package.json +0 -61
  64. package/src/lib/resources/templates/api-server/template/src/app.ts +0 -60
  65. package/src/lib/resources/templates/api-server/template/src/config/swagger.ts +0 -38
  66. package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +0 -54
  67. package/src/lib/resources/templates/api-server/template/src/controllers/authController.ts +0 -128
  68. package/src/lib/resources/templates/api-server/template/src/controllers/statusController.ts +0 -28
  69. package/src/lib/resources/templates/api-server/template/src/index.ts +0 -28
  70. package/src/lib/resources/templates/api-server/template/src/middleware/aiwfMiddleware.ts +0 -50
  71. package/src/lib/resources/templates/api-server/template/src/middleware/authMiddleware.ts +0 -41
  72. package/src/lib/resources/templates/api-server/template/src/middleware/errorHandler.ts +0 -42
  73. package/src/lib/resources/templates/api-server/template/src/middleware/notFoundHandler.ts +0 -14
  74. package/src/lib/resources/templates/api-server/template/src/middleware/requestLogger.ts +0 -20
  75. package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +0 -31
  76. package/src/lib/resources/templates/api-server/template/src/routes/index.ts +0 -13
  77. package/src/lib/resources/templates/api-server/template/src/routes/v1/index.ts +0 -67
  78. package/src/lib/resources/templates/api-server/template/src/utils/logger.ts +0 -37
  79. package/src/lib/resources/templates/api-server/template/tests/app.test.ts +0 -40
  80. package/src/lib/resources/templates/api-server/template/tsconfig.json +0 -43
  81. package/src/lib/resources/templates/npm-library/config.json +0 -61
  82. package/src/lib/resources/templates/npm-library/template/LICENSE +0 -21
  83. package/src/lib/resources/templates/npm-library/template/README.md +0 -201
  84. package/src/lib/resources/templates/npm-library/template/package.json +0 -78
  85. package/src/lib/resources/templates/web-app/config.json +0 -53
  86. package/src/lib/resources/templates/web-app/template/.aiwf/config.json +0 -54
  87. package/src/lib/resources/templates/web-app/template/.aiwf/feature-ledger.json +0 -37
  88. package/src/lib/resources/templates/web-app/template/.aiwf/personas/fullstack-developer.json +0 -40
  89. package/src/lib/resources/templates/web-app/template/.aiwf/scripts/cli.js +0 -110
  90. package/src/lib/resources/templates/web-app/template/.eslintrc.cjs +0 -20
  91. package/src/lib/resources/templates/web-app/template/README.md +0 -151
  92. package/src/lib/resources/templates/web-app/template/index.html +0 -14
  93. package/src/lib/resources/templates/web-app/template/package.json +0 -44
  94. package/src/lib/resources/templates/web-app/template/postcss.config.js +0 -6
  95. package/src/lib/resources/templates/web-app/template/public/vite.svg +0 -1
  96. package/src/lib/resources/templates/web-app/template/src/App.tsx +0 -21
  97. package/src/lib/resources/templates/web-app/template/src/components/Layout.tsx +0 -57
  98. package/src/lib/resources/templates/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -107
  99. package/src/lib/resources/templates/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -71
  100. package/src/lib/resources/templates/web-app/template/src/index.css +0 -60
  101. package/src/lib/resources/templates/web-app/template/src/main.tsx +0 -10
  102. package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +0 -57
  103. package/src/lib/resources/templates/web-app/template/src/pages/HomePage.tsx +0 -89
  104. package/src/lib/resources/templates/web-app/template/src/pages/NotFound.tsx +0 -25
  105. package/src/lib/resources/templates/web-app/template/src/stores/aiwfStore.ts +0 -126
  106. package/src/lib/resources/templates/web-app/template/src/types/global.d.ts +0 -9
  107. package/src/lib/resources/templates/web-app/template/src/vite-env.d.ts +0 -1
  108. package/src/lib/resources/templates/web-app/template/tailwind.config.js +0 -30
  109. package/src/lib/resources/templates/web-app/template/tsconfig.json +0 -37
  110. package/src/lib/resources/templates/web-app/template/tsconfig.node.json +0 -10
  111. package/src/lib/resources/templates/web-app/template/vite.config.ts +0 -23
  112. package/src/lib/resources/utils/background-monitor.js +0 -223
  113. package/src/lib/resources/utils/compression-metrics.js +0 -1031
  114. package/src/lib/resources/utils/compression-strategies.js +0 -557
  115. package/src/lib/resources/utils/content-normalizer.js +0 -455
  116. package/src/lib/resources/utils/context-compressor.js +0 -583
  117. package/src/lib/resources/utils/context-rule-parser.js +0 -180
  118. package/src/lib/resources/utils/context-token-monitor.js +0 -463
  119. package/src/lib/resources/utils/context-update-manager.js +0 -502
  120. package/src/lib/resources/utils/git-utils.js +0 -220
  121. package/src/lib/resources/utils/importance-classifier.js +0 -645
  122. package/src/lib/resources/utils/information-filter.js +0 -710
  123. package/src/lib/resources/utils/persona-aware-compressor.js +0 -598
  124. package/src/lib/resources/utils/prompt-injector.js +0 -242
  125. package/src/lib/resources/utils/simplified-evaluator.js +0 -119
  126. package/src/lib/resources/utils/text-summarizer.js +0 -492
  127. package/src/lib/resources/utils/token-counter.js +0 -177
  128. package/src/lib/resources/utils/token-monitor.js +0 -484
  129. package/src/lib/resources/utils/token-reporter.js +0 -452
  130. package/src/lib/resources/utils/token-storage.js +0 -433
  131. package/src/lib/resources/utils/token-tracker.js +0 -283
  132. package/src/lib/state/priority-calculator.js +0 -217
  133. package/src/lib/state/state-index.js +0 -155
  134. package/src/lib/state/task-scanner.js +0 -328
  135. package/src/lib/task-analyzer.js +0 -599
  136. package/src/lib/template-cache-system.js +0 -559
  137. package/src/lib/template-downloader.js +0 -467
  138. package/src/lib/template-version-manager.js +0 -491
  139. package/src/lib/token-optimizer.js +0 -517
  140. 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;