@quantumai/quantum-cli-core
Version:
Quantum CLI Core - Multi-LLM Collaboration System
565 lines • 25.8 kB
JavaScript
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export class OnboardingSystem {
features = new Map();
userState;
historyManager;
preferenceEngine;
constructor(historyManager, preferenceEngine, initialState) {
this.historyManager = historyManager;
this.preferenceEngine = preferenceEngine;
this.userState = {
completedFeatures: new Set(),
dismissedFeatures: new Set(),
currentlyLearning: null,
learningStartTime: null,
skillLevel: 'beginner',
preferredLearningStyle: 'guided',
lastOnboardingTime: 0,
onboardingBudget: 10, // Default 10 minutes per session
...initialState
};
this.initializeFeatures();
}
initializeFeatures() {
// Multi-LLM Collaboration Features
this.addFeature({
id: 'multi_llm_verification',
name: 'AI 응답 검증',
description: '중요한 작업에서 여러 AI 모델의 의견을 비교하여 더 정확한 결과를 얻습니다',
category: 'collaboration',
prerequisites: [],
estimatedTimeToValue: 5,
complexity: 'beginner',
triggers: [
{ type: 'usage_pattern', condition: 'high_complexity_queries > 3', weight: 0.8 },
{ type: 'context_based', condition: 'security_related_query', weight: 0.9 },
{ type: 'explicit_interest', condition: 'quality_concern_expressed', weight: 1.0 }
],
tutorial: {
steps: [
{
title: '검증 모드 활성화',
description: '/verify 명령어로 다중 AI 검증을 활성화할 수 있습니다',
action: '/verify auto',
example: '/verify auto "보안 관련 코드 검토해줘"',
hint: '보안, 배포, 중요한 결정이 필요한 작업에 특히 유용합니다'
},
{
title: '자동 트리거 설정',
description: '특정 조건에서 자동으로 검증 모드를 활성화하도록 설정할 수 있습니다',
action: '/settings collaboration.autoVerifyThreshold 0.7',
hint: '불확실성이 70% 이상일 때 자동으로 검증 모드가 활성화됩니다'
}
],
estimatedDuration: 5,
interactiveDemo: true
}
});
this.addFeature({
id: 'uncertainty_detection',
name: '불확실성 감지',
description: 'AI가 응답에 확신이 없을 때를 자동으로 감지하고 추가 검증을 제안합니다',
category: 'collaboration',
prerequisites: ['multi_llm_verification'],
estimatedTimeToValue: 3,
complexity: 'intermediate',
triggers: [
{ type: 'usage_pattern', condition: 'verification_mode_used > 2', weight: 0.7 },
{ type: 'context_based', condition: 'complex_technical_query', weight: 0.8 }
],
tutorial: {
steps: [
{
title: '불확실성 점수 확인',
description: 'AI 응답의 신뢰도를 실시간으로 확인할 수 있습니다',
hint: '신뢰도가 낮을 때는 자동으로 다른 모델의 의견을 요청합니다'
},
{
title: '임계값 조정',
description: '개인의 요구에 맞게 불확실성 감지 임계값을 조정할 수 있습니다',
action: '/settings uncertainty.threshold 0.6'
}
],
estimatedDuration: 3,
interactiveDemo: false
}
});
// Advanced Productivity Features
this.addFeature({
id: 'smart_context_management',
name: '스마트 컨텍스트 관리',
description: '프로젝트 컨텍스트를 자동으로 분석하고 관련 파일을 지능적으로 포함합니다',
category: 'productivity',
prerequisites: [],
estimatedTimeToValue: 8,
complexity: 'intermediate',
triggers: [
{ type: 'usage_pattern', condition: 'large_project_detected', weight: 0.9 },
{ type: 'usage_pattern', condition: 'frequent_file_operations > 10', weight: 0.7 }
],
tutorial: {
steps: [
{
title: 'QUANTUM.md 설정',
description: '프로젝트 루트에 QUANTUM.md 파일을 생성하여 컨텍스트를 정의합니다',
example: '# 프로젝트 개요\n이 프로젝트는 CLI 도구입니다...',
hint: '프로젝트 구조, 코딩 스타일, 중요한 파일들을 명시하세요'
},
{
title: '자동 파일 발견',
description: '@로 시작하는 경로를 사용하여 관련 파일을 자동으로 포함시킵니다',
action: '@src/components',
example: '@src/components "Button 컴포넌트 수정해줘"'
}
],
estimatedDuration: 8,
interactiveDemo: true
}
});
this.addFeature({
id: 'automatic_testing',
name: '자동 테스트 생성',
description: '코드 변경 시 자동으로 테스트를 생성하고 실행합니다',
category: 'productivity',
prerequisites: ['smart_context_management'],
estimatedTimeToValue: 12,
complexity: 'advanced',
triggers: [
{ type: 'usage_pattern', condition: 'code_modification_frequency > 5', weight: 0.8 },
{ type: 'context_based', condition: 'test_directory_exists', weight: 0.9 }
],
tutorial: {
steps: [
{
title: '테스트 자동 생성',
description: '코드 수정 후 /test generate 명령어로 테스트를 자동 생성합니다',
action: '/test generate',
hint: '기존 테스트 패턴을 분석하여 일관된 테스트를 생성합니다'
},
{
title: '지속적 검증',
description: '변경사항에 대해 자동으로 테스트를 실행하고 결과를 보고합니다',
action: '/test watch'
}
],
estimatedDuration: 12,
interactiveDemo: true
}
});
// Customization Features
this.addFeature({
id: 'theme_customization',
name: '테마 커스터마이징',
description: '개인 취향에 맞는 색상과 레이아웃으로 CLI 인터페이스를 커스터마이징합니다',
category: 'customization',
prerequisites: [],
estimatedTimeToValue: 3,
complexity: 'beginner',
triggers: [
{ type: 'time_based', condition: 'sessions > 5', weight: 0.6 },
{ type: 'explicit_interest', condition: 'ui_customization_mentioned', weight: 0.9 }
],
tutorial: {
steps: [
{
title: '테마 변경',
description: '/theme 명령어로 다양한 색상 테마를 적용할 수 있습니다',
action: '/theme',
example: '/theme dracula 또는 /theme github-light'
},
{
title: '사용자 정의 테마',
description: '개인적인 색상 조합으로 나만의 테마를 만들 수 있습니다',
hint: '~/.quantum/themes/ 폴더에 사용자 정의 테마를 추가할 수 있습니다'
}
],
estimatedDuration: 3,
interactiveDemo: true
}
});
this.addFeature({
id: 'custom_commands',
name: '사용자 정의 명령어',
description: '자주 사용하는 작업을 단축 명령어로 만들어 생산성을 향상시킵니다',
category: 'customization',
prerequisites: [],
estimatedTimeToValue: 10,
complexity: 'intermediate',
triggers: [
{ type: 'usage_pattern', condition: 'repeated_command_patterns > 3', weight: 0.8 },
{ type: 'explicit_interest', condition: 'automation_interest_expressed', weight: 0.9 }
],
tutorial: {
steps: [
{
title: '매크로 생성',
description: '반복되는 명령어 시퀀스를 매크로로 저장합니다',
action: '/macro create deploy',
example: '/macro create deploy "npm run build && npm run test && git push"'
},
{
title: '조건부 명령어',
description: '프로젝트나 상황에 따라 다르게 동작하는 스마트 명령어를 만듭니다',
hint: '프로젝트 타입, 환경 변수 등을 기반으로 동적으로 실행됩니다'
}
],
estimatedDuration: 10,
interactiveDemo: true
}
});
}
addFeature(feature) {
this.features.set(feature.id, feature);
}
/**
* 현재 사용자 상태와 패턴을 기반으로 온보딩 추천을 생성합니다
*/
getPersonalizedRecommendations() {
const userPattern = this.historyManager.getUserPattern();
const preferences = this.preferenceEngine.getPreferences();
const recommendations = [];
for (const [featureId, feature] of this.features) {
// Skip already completed or dismissed features
if (this.userState.completedFeatures.has(featureId) ||
this.userState.dismissedFeatures.has(featureId)) {
continue;
}
// Check prerequisites
if (!this.arePrerequisitesMet(feature.prerequisites)) {
continue;
}
// Calculate recommendation priority
const priority = this.calculateFeaturePriority(feature, userPattern, preferences);
if (priority > 0.5) { // Threshold for recommendation
const recommendation = {
feature,
priority,
reason: this.generateRecommendationReason(feature, userPattern),
personalizedMessage: this.generatePersonalizedMessage(feature, userPattern, preferences),
optimalTiming: this.determineOptimalTiming(feature, userPattern)
};
recommendations.push(recommendation);
}
}
// Sort by priority and return top recommendations
return recommendations
.sort((a, b) => b.priority - a.priority)
.slice(0, 3); // Limit to top 3 recommendations
}
/**
* 현재 컨텍스트에서 즉시 도움이 될 수 있는 기능을 추천합니다
*/
getContextualSuggestion(context) {
const userPattern = this.historyManager.getUserPattern();
for (const [featureId, feature] of this.features) {
if (this.userState.completedFeatures.has(featureId) ||
this.userState.dismissedFeatures.has(featureId)) {
continue;
}
// Check if feature is relevant to current context
for (const trigger of feature.triggers) {
if (trigger.type === 'context_based' && this.evaluateTrigger(trigger, context, userPattern)) {
return {
feature,
priority: trigger.weight,
reason: `현재 작업과 관련된 기능입니다: ${context.queryType}`,
personalizedMessage: this.generatePersonalizedMessage(feature, userPattern),
optimalTiming: 'immediate'
};
}
}
}
return null;
}
/**
* 사용자의 스킬 레벨에 맞는 학습 경로를 추천합니다
*/
getLearningPath() {
const path = [];
const userPattern = this.historyManager.getUserPattern();
// Determine appropriate skill level progression
const targetComplexity = this.getTargetComplexity();
// Find features that match the user's learning progression
const availableFeatures = Array.from(this.features.values())
.filter(feature => !this.userState.completedFeatures.has(feature.id) &&
!this.userState.dismissedFeatures.has(feature.id) &&
this.isFeatureAppropriate(feature, targetComplexity, userPattern))
.sort((a, b) => {
// Sort by complexity, then by estimated time to value
const complexityOrder = { 'beginner': 0, 'intermediate': 1, 'advanced': 2 };
const aDiff = complexityOrder[a.complexity] - complexityOrder[targetComplexity];
const bDiff = complexityOrder[b.complexity] - complexityOrder[targetComplexity];
if (aDiff !== bDiff)
return Math.abs(aDiff) - Math.abs(bDiff);
return a.estimatedTimeToValue - b.estimatedTimeToValue;
});
return availableFeatures.slice(0, 5); // Return top 5 features for learning path
}
/**
* 온보딩 기능 완료를 기록합니다
*/
markFeatureCompleted(featureId, completionTime) {
this.userState.completedFeatures.add(featureId);
this.userState.currentlyLearning = null;
this.userState.learningStartTime = null;
// Update skill level based on completed features
this.updateSkillLevel();
// Log completion for learning
this.preferenceEngine.recordInteraction({
type: 'feature_completion',
featureId,
timestamp: completionTime,
context: { skillLevel: this.userState.skillLevel }
});
}
/**
* 사용자가 기능을 거부했을 때 기록합니다
*/
markFeatureDismissed(featureId, reason) {
this.userState.dismissedFeatures.add(featureId);
// Learn from dismissal
this.preferenceEngine.recordInteraction({
type: 'feature_dismissal',
featureId,
timestamp: Date.now(),
context: { reason, skillLevel: this.userState.skillLevel }
});
}
/**
* 현재 학습 중인 기능을 설정합니다
*/
startLearningFeature(featureId) {
this.userState.currentlyLearning = featureId;
this.userState.learningStartTime = Date.now();
}
/**
* 사용자의 온보딩 상태를 반환합니다
*/
getOnboardingState() {
return { ...this.userState };
}
arePrerequisitesMet(prerequisites) {
return prerequisites.every(prereq => this.userState.completedFeatures.has(prereq));
}
calculateFeaturePriority(feature, userPattern, preferences) {
let priority = 0;
// Calculate priority based on triggers
for (const trigger of feature.triggers) {
if (this.evaluateTrigger(trigger, null, userPattern)) {
priority += trigger.weight;
}
}
// Adjust based on user preferences
if (preferences.learningStyle === 'exploratory' && feature.complexity === 'advanced') {
priority *= 1.2;
}
else if (preferences.learningStyle === 'guided' && feature.complexity === 'beginner') {
priority *= 1.1;
}
// Consider time constraints
if (feature.estimatedTimeToValue <= this.userState.onboardingBudget) {
priority *= 1.3;
}
else {
priority *= 0.7;
}
// Boost features in user's preferred categories
if (preferences.interestedAreas.includes(feature.category)) {
priority *= 1.4;
}
return Math.min(priority, 1.0);
}
evaluateTrigger(trigger, context, userPattern) {
switch (trigger.type) {
case 'usage_pattern':
return this.evaluateUsagePattern(trigger.condition, userPattern);
case 'time_based':
return this.evaluateTimeCondition(trigger.condition, userPattern);
case 'context_based':
return context ? this.evaluateContextCondition(trigger.condition, context) : false;
case 'explicit_interest':
return this.evaluateInterestCondition(trigger.condition, userPattern);
default:
return false;
}
}
evaluateUsagePattern(condition, userPattern) {
// Parse condition like "high_complexity_queries > 3"
const parts = condition.split(/\s*(>|<|=|>=|<=)\s*/);
if (parts.length !== 3)
return false;
const [metric, operator, valueStr] = parts;
const value = parseInt(valueStr, 10);
let actualValue = 0;
switch (metric) {
case 'high_complexity_queries':
actualValue = userPattern.queryComplexityDistribution.filter(c => c.complexity > 0.7).length;
break;
case 'large_project_detected':
actualValue = userPattern.contextMetrics.avgFilesPerQuery > 10 ? 1 : 0;
break;
case 'frequent_file_operations':
actualValue = userPattern.toolUsage.fileOperations || 0;
break;
case 'verification_mode_used':
actualValue = userPattern.toolUsage.collaborationFeatures || 0;
break;
case 'code_modification_frequency':
actualValue = userPattern.toolUsage.codeModification || 0;
break;
case 'repeated_command_patterns':
actualValue = userPattern.behaviorMetrics.commandRepetition;
break;
case 'sessions':
actualValue = userPattern.sessionMetrics.totalSessions;
break;
default:
return false;
}
switch (operator) {
case '>': return actualValue > value;
case '<': return actualValue < value;
case '=': return actualValue === value;
case '>=': return actualValue >= value;
case '<=': return actualValue <= value;
default: return false;
}
}
evaluateTimeCondition(condition, userPattern) {
const parts = condition.split(/\s*(>|<|=|>=|<=)\s*/);
if (parts.length !== 3)
return false;
const [metric, operator, valueStr] = parts;
const value = parseInt(valueStr, 10);
let actualValue = 0;
switch (metric) {
case 'sessions':
actualValue = userPattern.sessionMetrics.totalSessions;
break;
case 'days_since_start':
actualValue = Math.floor((Date.now() - userPattern.sessionMetrics.firstSessionTime) / (1000 * 60 * 60 * 24));
break;
default:
return false;
}
switch (operator) {
case '>': return actualValue > value;
case '<': return actualValue < value;
case '=': return actualValue === value;
case '>=': return actualValue >= value;
case '<=': return actualValue <= value;
default: return false;
}
}
evaluateContextCondition(condition, context) {
switch (condition) {
case 'security_related_query':
return context.queryType === 'security' ||
!!context.keywords?.some(k => ['security', 'auth', 'password', 'token'].includes(k.toLowerCase()));
case 'complex_technical_query':
return context.complexity != null && context.complexity > 0.7;
case 'test_directory_exists':
return !!context.projectStructure?.some(path => path.includes('test') || path.includes('spec'));
case 'ui_customization_mentioned':
return !!context.keywords?.some(k => ['theme', 'color', 'ui', 'interface', 'style'].includes(k.toLowerCase()));
default:
return false;
}
}
evaluateInterestCondition(condition, userPattern) {
const recentQueries = userPattern.recentQueries.slice(-10);
switch (condition) {
case 'quality_concern_expressed':
return recentQueries.some(q => q.text.toLowerCase().includes('correct') ||
q.text.toLowerCase().includes('sure') ||
q.text.toLowerCase().includes('double check'));
case 'automation_interest_expressed':
return recentQueries.some(q => q.text.toLowerCase().includes('automate') ||
q.text.toLowerCase().includes('script') ||
q.text.toLowerCase().includes('repeat'));
default:
return false;
}
}
generateRecommendationReason(feature, userPattern) {
const reasons = [];
if (userPattern.sessionMetrics.totalSessions > 5) {
reasons.push('경험이 쌓였으니 고급 기능을 사용해볼 시간입니다');
}
if (feature.category === 'productivity' && userPattern.behaviorMetrics.commandRepetition > 3) {
reasons.push('반복되는 작업 패턴이 감지되어 생산성 향상이 필요해 보입니다');
}
if (feature.category === 'collaboration' && userPattern.sessionMetrics.avgSessionDuration > 30) {
reasons.push('복잡한 작업을 많이 하시는 것 같아 품질 검증 기능이 도움이 될 것 같습니다');
}
return reasons.length > 0 ? reasons[0] : `${feature.category} 영역에서 도움이 될 수 있는 기능입니다`;
}
generatePersonalizedMessage(feature, userPattern, preferences) {
const skillLevel = this.userState.skillLevel;
const timeToValue = feature.estimatedTimeToValue;
let message = `💡 **${feature.name}**\n${feature.description}\n\n`;
if (skillLevel === 'beginner') {
message += `초보자에게 친화적인 기능으로, 약 ${timeToValue}분이면 익힐 수 있습니다. `;
}
else if (skillLevel === 'advanced') {
message += `고급 사용자를 위한 강력한 기능으로, ${timeToValue}분 투자로 큰 효율성 향상을 얻을 수 있습니다. `;
}
if (userPattern.behaviorMetrics.errorRate > 0.1) {
message += '현재 작업 중 발생하는 오류를 줄이는데 특히 도움이 될 것 같습니다.';
}
else if (userPattern.sessionMetrics.avgSessionDuration > 45) {
message += '긴 작업 시간을 단축하는데 큰 도움이 될 것 같습니다.';
}
return message;
}
determineOptimalTiming(feature, userPattern) {
if (feature.complexity === 'beginner') {
return 'immediate';
}
if (userPattern.sessionMetrics.avgSessionDuration < 15) {
return 'next_session';
}
if (feature.estimatedTimeToValue > this.userState.onboardingBudget) {
return 'when_idle';
}
return 'contextual';
}
getTargetComplexity() {
const completedCount = this.userState.completedFeatures.size;
const sessionCount = this.historyManager.getUserPattern().sessionMetrics.totalSessions;
if (completedCount >= 5 || sessionCount >= 20) {
return 'advanced';
}
else if (completedCount >= 2 || sessionCount >= 10) {
return 'intermediate';
}
else {
return 'beginner';
}
}
isFeatureAppropriate(feature, targetComplexity, userPattern) {
const complexityOrder = { 'beginner': 0, 'intermediate': 1, 'advanced': 2 };
const featureLevel = complexityOrder[feature.complexity];
const targetLevel = complexityOrder[targetComplexity];
// Allow features up to one level above target
return featureLevel <= targetLevel + 1;
}
updateSkillLevel() {
const completedCount = this.userState.completedFeatures.size;
const sessionCount = this.historyManager.getUserPattern().sessionMetrics.totalSessions;
if (completedCount >= 8 || sessionCount >= 30) {
this.userState.skillLevel = 'advanced';
}
else if (completedCount >= 4 || sessionCount >= 15) {
this.userState.skillLevel = 'intermediate';
}
else {
this.userState.skillLevel = 'beginner';
}
}
}
//# sourceMappingURL=onboarding-system.js.map