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,389 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { ContextCompressor } from '../utils/context-compressor.js';
|
|
4
|
-
import { PersonaAwareCompressor } from '../utils/persona-aware-compressor.js';
|
|
5
|
-
import { getBackgroundMonitor } from '../utils/background-monitor.js';
|
|
6
|
-
import { TokenCounter } from '../utils/token-counter.js';
|
|
7
|
-
import { CompressionMetrics } from '../utils/compression-metrics.js';
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
import ora from 'ora';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
13
|
-
import { dirname } from 'path';
|
|
14
|
-
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = dirname(__filename);
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* compress-context 명령어 실행
|
|
20
|
-
*/
|
|
21
|
-
export async function executeCompressContext(args = []) {
|
|
22
|
-
const spinner = ora('Context 압축 준비 중...').start();
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// 파라미터 파싱
|
|
26
|
-
const { mode, targetPath, usePersona } = parseArguments(args);
|
|
27
|
-
|
|
28
|
-
spinner.text = '압축 대상 분석 중...';
|
|
29
|
-
|
|
30
|
-
// 대상 경로 결정
|
|
31
|
-
const resolvedPath = await resolveTargetPath(targetPath);
|
|
32
|
-
const files = await collectMarkdownFiles(resolvedPath);
|
|
33
|
-
|
|
34
|
-
if (files.length === 0) {
|
|
35
|
-
spinner.fail('압축할 마크다운 파일이 없습니다.');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 압축 전 통계
|
|
40
|
-
spinner.text = '원본 토큰 수 계산 중...';
|
|
41
|
-
const beforeStats = await calculateStats(files);
|
|
42
|
-
|
|
43
|
-
// 압축 시작
|
|
44
|
-
console.log('');
|
|
45
|
-
console.log(chalk.cyan('🗜️ Context 압축 시작'));
|
|
46
|
-
console.log(chalk.gray('━'.repeat(50)));
|
|
47
|
-
console.log(`압축 모드: ${chalk.yellow(mode)}`);
|
|
48
|
-
console.log(`대상: ${chalk.yellow(resolvedPath)}`);
|
|
49
|
-
console.log(`페르소나 연동: ${chalk.yellow(usePersona ? '활성화' : '비활성화')}`);
|
|
50
|
-
console.log('');
|
|
51
|
-
|
|
52
|
-
console.log(chalk.cyan('📊 압축 전 통계:'));
|
|
53
|
-
console.log(`- 총 파일 수: ${files.length}`);
|
|
54
|
-
console.log(`- 원본 토큰: ${chalk.red(beforeStats.totalTokens.toLocaleString())}`);
|
|
55
|
-
console.log(`- 원본 크기: ${(beforeStats.totalSize / 1024).toFixed(1)} KB`);
|
|
56
|
-
console.log('');
|
|
57
|
-
|
|
58
|
-
spinner.text = '압축 진행 중...';
|
|
59
|
-
|
|
60
|
-
// 압축 수행
|
|
61
|
-
const compressor = usePersona
|
|
62
|
-
? new PersonaAwareCompressor(mode)
|
|
63
|
-
: new ContextCompressor(mode);
|
|
64
|
-
const compressionResults = [];
|
|
65
|
-
const startTime = Date.now();
|
|
66
|
-
|
|
67
|
-
console.log(chalk.cyan('⚙️ 압축 진행 중...'));
|
|
68
|
-
|
|
69
|
-
for (const file of files) {
|
|
70
|
-
const fileName = path.basename(file);
|
|
71
|
-
const content = await fs.readFile(file, 'utf8');
|
|
72
|
-
|
|
73
|
-
const result = await compressor.compress(content, {
|
|
74
|
-
strategy: mode,
|
|
75
|
-
preserveStructure: true,
|
|
76
|
-
enableSummarization: mode === 'aggressive',
|
|
77
|
-
enableNormalization: true,
|
|
78
|
-
enableFiltering: mode !== 'minimal'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (result.success) {
|
|
82
|
-
const savedTokens = result.originalTokens - result.compressedTokens;
|
|
83
|
-
const ratio = result.compressionRatio.toFixed(1);
|
|
84
|
-
|
|
85
|
-
console.log(chalk.green(`✓ ${fileName} (${result.originalTokens.toLocaleString()} → ${result.compressedTokens.toLocaleString()} 토큰, -${ratio}%)`));
|
|
86
|
-
|
|
87
|
-
compressionResults.push({
|
|
88
|
-
filePath: file,
|
|
89
|
-
fileName,
|
|
90
|
-
...result
|
|
91
|
-
});
|
|
92
|
-
} else {
|
|
93
|
-
console.log(chalk.red(`✗ ${fileName} (압축 실패: ${result.error})`));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const processingTime = Date.now() - startTime;
|
|
98
|
-
|
|
99
|
-
// 압축 후 통계
|
|
100
|
-
const afterStats = calculateCompressedStats(compressionResults);
|
|
101
|
-
const totalSaved = beforeStats.totalTokens - afterStats.totalTokens;
|
|
102
|
-
const overallRatio = ((totalSaved / beforeStats.totalTokens) * 100).toFixed(1);
|
|
103
|
-
|
|
104
|
-
// 압축된 파일 저장
|
|
105
|
-
spinner.text = '압축된 파일 저장 중...';
|
|
106
|
-
const outputDir = await saveCompressedFiles(compressionResults, resolvedPath);
|
|
107
|
-
|
|
108
|
-
// 압축 보고서 생성
|
|
109
|
-
await generateReport(compressionResults, {
|
|
110
|
-
mode,
|
|
111
|
-
targetPath: resolvedPath,
|
|
112
|
-
outputDir,
|
|
113
|
-
beforeStats,
|
|
114
|
-
afterStats,
|
|
115
|
-
processingTime
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
spinner.stop();
|
|
119
|
-
|
|
120
|
-
// 결과 출력
|
|
121
|
-
console.log('');
|
|
122
|
-
console.log(chalk.cyan('📊 압축 후 통계:'));
|
|
123
|
-
console.log(`- 압축된 토큰: ${chalk.green(afterStats.totalTokens.toLocaleString())}`);
|
|
124
|
-
console.log(`- 절약된 토큰: ${chalk.green(totalSaved.toLocaleString())}`);
|
|
125
|
-
console.log(`- 토큰 절약률: ${chalk.green(overallRatio + '%')}`);
|
|
126
|
-
console.log(`- 압축 시간: ${(processingTime / 1000).toFixed(1)}초`);
|
|
127
|
-
|
|
128
|
-
// 평균 품질 점수 계산
|
|
129
|
-
const avgQuality = compressionResults.reduce((sum, r) => {
|
|
130
|
-
return sum + (r.metadata?.validation?.qualityScore || 0);
|
|
131
|
-
}, 0) / compressionResults.length;
|
|
132
|
-
|
|
133
|
-
console.log(`- 품질 점수: ${chalk.green(avgQuality.toFixed(0) + '/100')}`);
|
|
134
|
-
|
|
135
|
-
// 페르소나 정보 출력
|
|
136
|
-
if (usePersona && compressor.currentPersona) {
|
|
137
|
-
console.log(`- 활성 페르소나: ${chalk.cyan(compressor.currentPersona)}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log('');
|
|
141
|
-
|
|
142
|
-
console.log(chalk.green('✅ 압축 완료!'));
|
|
143
|
-
console.log(`압축된 파일 위치: ${chalk.yellow(outputDir)}`);
|
|
144
|
-
|
|
145
|
-
// 페르소나 인식 압축인 경우 백그라운드 품질 체크
|
|
146
|
-
if (usePersona && compressor.currentPersona) {
|
|
147
|
-
const monitor = getBackgroundMonitor();
|
|
148
|
-
|
|
149
|
-
// 압축된 내용의 샘플로 품질 체크 (비차단)
|
|
150
|
-
const sampleResult = compressionResults[0];
|
|
151
|
-
if (sampleResult) {
|
|
152
|
-
monitor.monitorResponse(
|
|
153
|
-
sampleResult.compressed.substring(0, 1000), // 샘플만
|
|
154
|
-
compressor.currentPersona,
|
|
155
|
-
{ silent: false }
|
|
156
|
-
).then(feedback => {
|
|
157
|
-
if (feedback && feedback.feedback) {
|
|
158
|
-
console.log('');
|
|
159
|
-
console.log(feedback.feedback);
|
|
160
|
-
}
|
|
161
|
-
}).catch(() => {}); // 에러 무시
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 목표 달성 여부 확인
|
|
166
|
-
const targetRange = getTargetRange(mode);
|
|
167
|
-
if (parseFloat(overallRatio) < targetRange.min) {
|
|
168
|
-
console.log('');
|
|
169
|
-
console.log(chalk.yellow(`⚠️ 경고: 압축률이 목표 범위(${targetRange.min}-${targetRange.max}%)에 미달했습니다.`));
|
|
170
|
-
console.log('더 높은 압축률이 필요하면 aggressive 모드를 시도해보세요.');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 리소스 정리
|
|
174
|
-
compressor.cleanup();
|
|
175
|
-
|
|
176
|
-
} catch (error) {
|
|
177
|
-
spinner.fail(`압축 실패: ${error.message}`);
|
|
178
|
-
console.error(error);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* 명령어 인자를 파싱합니다
|
|
184
|
-
* @param {Array<string>} args - 인자 배열
|
|
185
|
-
* @returns {Object} 파싱된 옵션
|
|
186
|
-
*/
|
|
187
|
-
function parseArguments(args) {
|
|
188
|
-
let mode = 'balanced';
|
|
189
|
-
let targetPath = null;
|
|
190
|
-
let usePersona = false;
|
|
191
|
-
|
|
192
|
-
for (const arg of args) {
|
|
193
|
-
if (['aggressive', 'balanced', 'minimal'].includes(arg)) {
|
|
194
|
-
mode = arg;
|
|
195
|
-
} else if (arg === '--persona' || arg === '-p') {
|
|
196
|
-
usePersona = true;
|
|
197
|
-
} else if (arg && !arg.startsWith('-')) {
|
|
198
|
-
targetPath = arg;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return { mode, targetPath, usePersona };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* 대상 경로를 결정합니다
|
|
207
|
-
* @param {string} targetPath - 사용자 지정 경로
|
|
208
|
-
* @returns {string} 결정된 경로
|
|
209
|
-
*/
|
|
210
|
-
async function resolveTargetPath(targetPath) {
|
|
211
|
-
if (targetPath) {
|
|
212
|
-
// 절대 경로가 아니면 현재 디렉토리 기준으로 변환
|
|
213
|
-
if (!path.isAbsolute(targetPath)) {
|
|
214
|
-
targetPath = path.join(process.cwd(), targetPath);
|
|
215
|
-
}
|
|
216
|
-
return targetPath;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// 기본값: 프로젝트의 .aiwf 디렉토리
|
|
220
|
-
const aiwfPath = path.join(process.cwd(), '.aiwf');
|
|
221
|
-
try {
|
|
222
|
-
await fs.access(aiwfPath);
|
|
223
|
-
return aiwfPath;
|
|
224
|
-
} catch {
|
|
225
|
-
// .aiwf가 없으면 현재 디렉토리
|
|
226
|
-
return process.cwd();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* 마크다운 파일들을 수집합니다
|
|
232
|
-
* @param {string} targetPath - 대상 경로
|
|
233
|
-
* @returns {Array<string>} 파일 경로 배열
|
|
234
|
-
*/
|
|
235
|
-
async function collectMarkdownFiles(targetPath) {
|
|
236
|
-
const files = [];
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const stats = await fs.stat(targetPath);
|
|
240
|
-
|
|
241
|
-
if (stats.isFile() && targetPath.endsWith('.md')) {
|
|
242
|
-
files.push(targetPath);
|
|
243
|
-
} else if (stats.isDirectory()) {
|
|
244
|
-
await collectFromDirectory(targetPath, files);
|
|
245
|
-
}
|
|
246
|
-
} catch (error) {
|
|
247
|
-
console.error(`경로 접근 오류: ${targetPath}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return files;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* 디렉토리에서 마크다운 파일을 재귀적으로 수집합니다
|
|
255
|
-
* @param {string} dirPath - 디렉토리 경로
|
|
256
|
-
* @param {Array<string>} files - 파일 배열
|
|
257
|
-
*/
|
|
258
|
-
async function collectFromDirectory(dirPath, files) {
|
|
259
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
260
|
-
|
|
261
|
-
for (const entry of entries) {
|
|
262
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
263
|
-
|
|
264
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
265
|
-
await collectFromDirectory(fullPath, files);
|
|
266
|
-
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
267
|
-
files.push(fullPath);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* 파일 통계를 계산합니다
|
|
274
|
-
* @param {Array<string>} files - 파일 경로 배열
|
|
275
|
-
* @returns {Object} 통계 정보
|
|
276
|
-
*/
|
|
277
|
-
async function calculateStats(files) {
|
|
278
|
-
const tokenCounter = new TokenCounter();
|
|
279
|
-
let totalTokens = 0;
|
|
280
|
-
let totalSize = 0;
|
|
281
|
-
|
|
282
|
-
for (const file of files) {
|
|
283
|
-
const content = await fs.readFile(file, 'utf8');
|
|
284
|
-
totalTokens += tokenCounter.countTokens(content);
|
|
285
|
-
totalSize += Buffer.byteLength(content, 'utf8');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
tokenCounter.cleanup();
|
|
289
|
-
|
|
290
|
-
return { totalTokens, totalSize };
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* 압축된 통계를 계산합니다
|
|
295
|
-
* @param {Array<Object>} results - 압축 결과 배열
|
|
296
|
-
* @returns {Object} 통계 정보
|
|
297
|
-
*/
|
|
298
|
-
function calculateCompressedStats(results) {
|
|
299
|
-
const totalTokens = results.reduce((sum, r) => sum + r.compressedTokens, 0);
|
|
300
|
-
const totalSize = results.reduce((sum, r) => sum + Buffer.byteLength(r.compressed, 'utf8'), 0);
|
|
301
|
-
|
|
302
|
-
return { totalTokens, totalSize };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* 압축된 파일들을 저장합니다
|
|
307
|
-
* @param {Array<Object>} results - 압축 결과 배열
|
|
308
|
-
* @param {string} targetPath - 원본 경로
|
|
309
|
-
* @returns {string} 출력 디렉토리
|
|
310
|
-
*/
|
|
311
|
-
async function saveCompressedFiles(results, targetPath) {
|
|
312
|
-
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
|
|
313
|
-
const outputDir = path.join(path.dirname(targetPath), `.aiwf_compressed`, timestamp);
|
|
314
|
-
|
|
315
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
316
|
-
|
|
317
|
-
for (const result of results) {
|
|
318
|
-
const outputPath = path.join(outputDir, result.fileName);
|
|
319
|
-
await fs.writeFile(outputPath, result.compressed);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return outputDir;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* 압축 보고서를 생성합니다
|
|
327
|
-
* @param {Array<Object>} results - 압축 결과 배열
|
|
328
|
-
* @param {Object} stats - 통계 정보
|
|
329
|
-
*/
|
|
330
|
-
async function generateReport(results, stats) {
|
|
331
|
-
const report = [];
|
|
332
|
-
|
|
333
|
-
report.push('# Context 압축 보고서');
|
|
334
|
-
report.push('');
|
|
335
|
-
report.push(`**생성 시간**: ${new Date().toISOString()}`);
|
|
336
|
-
report.push(`**압축 모드**: ${stats.mode}`);
|
|
337
|
-
report.push(`**대상 경로**: ${stats.targetPath}`);
|
|
338
|
-
report.push(`**출력 경로**: ${stats.outputDir}`);
|
|
339
|
-
report.push('');
|
|
340
|
-
|
|
341
|
-
report.push('## 전체 통계');
|
|
342
|
-
report.push(`- **파일 수**: ${results.length}`);
|
|
343
|
-
report.push(`- **원본 토큰**: ${stats.beforeStats.totalTokens.toLocaleString()}`);
|
|
344
|
-
report.push(`- **압축 토큰**: ${stats.afterStats.totalTokens.toLocaleString()}`);
|
|
345
|
-
report.push(`- **절약 토큰**: ${(stats.beforeStats.totalTokens - stats.afterStats.totalTokens).toLocaleString()}`);
|
|
346
|
-
report.push(`- **전체 압축률**: ${((stats.beforeStats.totalTokens - stats.afterStats.totalTokens) / stats.beforeStats.totalTokens * 100).toFixed(1)}%`);
|
|
347
|
-
report.push(`- **처리 시간**: ${(stats.processingTime / 1000).toFixed(1)}초`);
|
|
348
|
-
report.push('');
|
|
349
|
-
|
|
350
|
-
report.push('## 파일별 상세');
|
|
351
|
-
report.push('');
|
|
352
|
-
report.push('| 파일명 | 원본 토큰 | 압축 토큰 | 압축률 | 품질 점수 |');
|
|
353
|
-
report.push('|--------|-----------|-----------|--------|-----------|');
|
|
354
|
-
|
|
355
|
-
for (const result of results) {
|
|
356
|
-
const qualityScore = result.metadata?.validation?.qualityScore || 0;
|
|
357
|
-
report.push(
|
|
358
|
-
`| ${result.fileName} | ${result.originalTokens.toLocaleString()} | ` +
|
|
359
|
-
`${result.compressedTokens.toLocaleString()} | ${result.compressionRatio}% | ` +
|
|
360
|
-
`${qualityScore}/100 |`
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const reportPath = path.join(stats.outputDir, 'compression-report.md');
|
|
365
|
-
await fs.writeFile(reportPath, report.join('\n'));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* 압축 모드별 목표 범위를 반환합니다
|
|
370
|
-
* @param {string} mode - 압축 모드
|
|
371
|
-
* @returns {Object} 목표 범위
|
|
372
|
-
*/
|
|
373
|
-
function getTargetRange(mode) {
|
|
374
|
-
const ranges = {
|
|
375
|
-
aggressive: { min: 50, max: 70 },
|
|
376
|
-
balanced: { min: 30, max: 50 },
|
|
377
|
-
minimal: { min: 10, max: 30 }
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
return ranges[mode] || { min: 0, max: 100 };
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// CLI로 직접 실행하는 경우
|
|
384
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
385
|
-
const args = process.argv.slice(2);
|
|
386
|
-
executeCompressContext(args);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export default executeCompressContext;
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 경량화된 평가 명령어
|
|
5
|
-
* 선택적으로 사용할 수 있는 간단한 평가 도구
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { SimplifiedEvaluator } from '../utils/simplified-evaluator.js';
|
|
9
|
-
import { getBackgroundMonitor } from '../utils/background-monitor.js';
|
|
10
|
-
import fs from 'fs/promises';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import chalk from 'chalk';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = path.dirname(__filename);
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* evaluate 명령어 실행
|
|
20
|
-
*/
|
|
21
|
-
export async function executeEvaluate(args = []) {
|
|
22
|
-
const option = args[0];
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
switch (option) {
|
|
26
|
-
case '--detailed':
|
|
27
|
-
case '-d':
|
|
28
|
-
return await detailedEvaluation();
|
|
29
|
-
|
|
30
|
-
case '--history':
|
|
31
|
-
case '-h':
|
|
32
|
-
return await showHistory();
|
|
33
|
-
|
|
34
|
-
case '--stats':
|
|
35
|
-
case '-s':
|
|
36
|
-
return await showStats();
|
|
37
|
-
|
|
38
|
-
default:
|
|
39
|
-
return await quickEvaluation();
|
|
40
|
-
}
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(chalk.red('오류:'), error.message);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 빠른 평가 (기본)
|
|
48
|
-
*/
|
|
49
|
-
async function quickEvaluation() {
|
|
50
|
-
console.log(chalk.cyan('🔍 최근 응답 평가'));
|
|
51
|
-
console.log(chalk.gray('━'.repeat(50)));
|
|
52
|
-
|
|
53
|
-
// 현재 페르소나 확인
|
|
54
|
-
const currentPersona = await getCurrentPersona();
|
|
55
|
-
if (!currentPersona) {
|
|
56
|
-
console.log(chalk.yellow('활성화된 페르소나가 없습니다.'));
|
|
57
|
-
console.log('페르소나를 먼저 활성화하세요: aiwf persona [architect|security|frontend|backend|data_analyst]');
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
console.log(`현재 페르소나: ${chalk.yellow(currentPersona)}`);
|
|
62
|
-
|
|
63
|
-
// 최근 통계 표시
|
|
64
|
-
const monitor = getBackgroundMonitor();
|
|
65
|
-
const stats = monitor.getRecentStats(currentPersona);
|
|
66
|
-
|
|
67
|
-
if (stats.message) {
|
|
68
|
-
console.log(stats.message);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log(`평균 점수: ${chalk.green(stats.averageScore)}`);
|
|
73
|
-
console.log(`평가 횟수: ${stats.totalEvaluations}`);
|
|
74
|
-
console.log(`추세: ${getTrendEmoji(stats.trend)} ${getKoreanTrend(stats.trend)}`);
|
|
75
|
-
|
|
76
|
-
// 간단한 팁
|
|
77
|
-
if (parseFloat(stats.averageScore) < 0.6) {
|
|
78
|
-
console.log('');
|
|
79
|
-
console.log(chalk.yellow('💡 팁: 페르소나 특성을 더 활용해보세요!'));
|
|
80
|
-
} else if (parseFloat(stats.averageScore) > 0.8) {
|
|
81
|
-
console.log('');
|
|
82
|
-
console.log(chalk.green('🎉 훌륭해요! 페르소나를 잘 활용하고 있습니다.'));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 상세 평가
|
|
88
|
-
*/
|
|
89
|
-
async function detailedEvaluation() {
|
|
90
|
-
console.log(chalk.cyan('📊 상세 평가 분석'));
|
|
91
|
-
console.log(chalk.gray('━'.repeat(50)));
|
|
92
|
-
|
|
93
|
-
const monitor = getBackgroundMonitor();
|
|
94
|
-
const stats = monitor.getRecentStats();
|
|
95
|
-
|
|
96
|
-
if (stats.message) {
|
|
97
|
-
console.log(stats.message);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
console.log(chalk.white('전체 통계:'));
|
|
102
|
-
console.log(`- 총 평가: ${stats.totalEvaluations}회`);
|
|
103
|
-
console.log(`- 평균 점수: ${stats.averageScore}`);
|
|
104
|
-
console.log(`- 추세: ${getTrendEmoji(stats.trend)} ${getKoreanTrend(stats.trend)}`);
|
|
105
|
-
|
|
106
|
-
console.log('');
|
|
107
|
-
console.log(chalk.white('페르소나별 성과:'));
|
|
108
|
-
|
|
109
|
-
Object.entries(stats.personaStats).forEach(([persona, data]) => {
|
|
110
|
-
const score = parseFloat(data.avgScore);
|
|
111
|
-
const scoreColor = score >= 0.8 ? 'green' : score >= 0.6 ? 'yellow' : 'red';
|
|
112
|
-
|
|
113
|
-
console.log(`- ${getKoreanPersonaName(persona)}: ${chalk[scoreColor](data.avgScore)} (${data.count}회)`);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// 개선 제안
|
|
117
|
-
console.log('');
|
|
118
|
-
console.log(chalk.white('개선 제안:'));
|
|
119
|
-
|
|
120
|
-
Object.entries(stats.personaStats).forEach(([persona, data]) => {
|
|
121
|
-
if (parseFloat(data.avgScore) < 0.6) {
|
|
122
|
-
const evaluator = new SimplifiedEvaluator();
|
|
123
|
-
const feedback = evaluator.getGentleFeedback(persona, parseFloat(data.avgScore));
|
|
124
|
-
if (feedback) {
|
|
125
|
-
console.log(feedback);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* 평가 히스토리
|
|
133
|
-
*/
|
|
134
|
-
async function showHistory() {
|
|
135
|
-
console.log(chalk.cyan('📈 평가 히스토리'));
|
|
136
|
-
console.log(chalk.gray('━'.repeat(50)));
|
|
137
|
-
|
|
138
|
-
const monitor = getBackgroundMonitor();
|
|
139
|
-
const history = monitor.recentEvaluations.slice(-10); // 최근 10개
|
|
140
|
-
|
|
141
|
-
if (history.length === 0) {
|
|
142
|
-
console.log('평가 기록이 없습니다.');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
history.forEach((eval, index) => {
|
|
147
|
-
const date = new Date(eval.timestamp);
|
|
148
|
-
const timeStr = `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
149
|
-
const scoreColor = eval.score >= 0.8 ? 'green' : eval.score >= 0.6 ? 'yellow' : 'red';
|
|
150
|
-
|
|
151
|
-
console.log(`${timeStr} | ${getKoreanPersonaName(eval.persona)} | ${chalk[scoreColor](eval.score.toFixed(2))}`);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 주간 통계
|
|
157
|
-
*/
|
|
158
|
-
async function showStats() {
|
|
159
|
-
console.log(chalk.cyan('📊 주간 통계'));
|
|
160
|
-
console.log(chalk.gray('━'.repeat(50)));
|
|
161
|
-
|
|
162
|
-
const monitor = getBackgroundMonitor();
|
|
163
|
-
const summary = monitor.getWeeklySummary();
|
|
164
|
-
|
|
165
|
-
if (summary.message) {
|
|
166
|
-
console.log(summary.message);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
console.log(`평가 횟수: ${summary.evaluationCount}회`);
|
|
171
|
-
console.log(`평균 점수: ${summary.averageScore}`);
|
|
172
|
-
console.log(`추세: ${getTrendEmoji(summary.trend)} ${getKoreanTrend(summary.trend)}`);
|
|
173
|
-
|
|
174
|
-
if (summary.personaStats) {
|
|
175
|
-
console.log('');
|
|
176
|
-
console.log('페르소나별 활용도:');
|
|
177
|
-
|
|
178
|
-
const sorted = Object.entries(summary.personaStats)
|
|
179
|
-
.sort((a, b) => b[1].count - a[1].count);
|
|
180
|
-
|
|
181
|
-
sorted.forEach(([persona, data]) => {
|
|
182
|
-
const bar = '█'.repeat(Math.floor(data.count / 2));
|
|
183
|
-
console.log(`${getKoreanPersonaName(persona).padEnd(10)} ${bar} ${data.count}회`);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 현재 페르소나 가져오기
|
|
190
|
-
*/
|
|
191
|
-
async function getCurrentPersona() {
|
|
192
|
-
try {
|
|
193
|
-
const personaPath = path.join(process.cwd(), '.aiwf', 'current_persona.json');
|
|
194
|
-
const data = await fs.readFile(personaPath, 'utf8');
|
|
195
|
-
const personaData = JSON.parse(data);
|
|
196
|
-
return personaData.persona;
|
|
197
|
-
} catch {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* 페르소나 이름 한글화
|
|
204
|
-
*/
|
|
205
|
-
function getKoreanPersonaName(persona) {
|
|
206
|
-
const names = {
|
|
207
|
-
architect: '아키텍트',
|
|
208
|
-
security: '보안 전문가',
|
|
209
|
-
frontend: '프론트엔드',
|
|
210
|
-
backend: '백엔드',
|
|
211
|
-
data_analyst: '데이터 분석가'
|
|
212
|
-
};
|
|
213
|
-
return names[persona] || persona;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* 추세 이모지
|
|
218
|
-
*/
|
|
219
|
-
function getTrendEmoji(trend) {
|
|
220
|
-
const emojis = {
|
|
221
|
-
improving: '📈',
|
|
222
|
-
declining: '📉',
|
|
223
|
-
stable: '➡️'
|
|
224
|
-
};
|
|
225
|
-
return emojis[trend] || '❓';
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* 추세 한글화
|
|
230
|
-
*/
|
|
231
|
-
function getKoreanTrend(trend) {
|
|
232
|
-
const trends = {
|
|
233
|
-
improving: '개선 중',
|
|
234
|
-
declining: '하락 중',
|
|
235
|
-
stable: '안정적'
|
|
236
|
-
};
|
|
237
|
-
return trends[trend] || trend;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// CLI로 직접 실행하는 경우
|
|
241
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
242
|
-
const args = process.argv.slice(2);
|
|
243
|
-
executeEvaluate(args);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export default executeEvaluate;
|