From 99117e8e7a1104893eaffbbdcf37912cd36089cd Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Tue, 5 Aug 2025 13:03:33 +0200 Subject: [PATCH] first draft enhancement 2 --- .env.example | 21 +- check-unused-css.js | 102 ++++++++++ package.json | 6 +- src/components/AIQueryInterface.astro | 170 ++++++++++++++-- src/styles/global.css | 126 ++++++++---- src/utils/aiPipeline.ts | 267 +++++++++++++++++++++++++- unused-selectors.txt | 89 +++++++++ 7 files changed, 714 insertions(+), 67 deletions(-) create mode 100644 check-unused-css.js create mode 100644 unused-selectors.txt diff --git a/.env.example b/.env.example index 00b00bc..2ad295d 100644 --- a/.env.example +++ b/.env.example @@ -190,19 +190,22 @@ FORENSIC_AUDIT_RETENTION_HOURS=24 FORENSIC_AUDIT_MAX_ENTRIES=50 # ============================================================================ -# 10. QUALITY CONTROL & BIAS DETECTION (ADVANCED) +# 10. CONFIDENCE SCORING SYSTEM (Enhancement 2) # ============================================================================ -# Confidence scoring weights (must sum to 1.0) -CONFIDENCE_EMBEDDINGS_WEIGHT=0.3 -CONFIDENCE_CONSENSUS_WEIGHT=0.25 -CONFIDENCE_DOMAIN_MATCH_WEIGHT=0.25 -CONFIDENCE_FRESHNESS_WEIGHT=0.2 +# Confidence component weights (must sum to 1.0) +CONFIDENCE_EMBEDDINGS_WEIGHT=0.3 # Weight for vector similarity quality +CONFIDENCE_CONSENSUS_WEIGHT=0.25 # Weight for micro-task agreement +CONFIDENCE_DOMAIN_MATCH_WEIGHT=0.25 # Weight for domain alignment +CONFIDENCE_FRESHNESS_WEIGHT=0.2 # Weight for tool freshness/maintenance # Confidence thresholds (0-100) -CONFIDENCE_MINIMUM_THRESHOLD=40 -CONFIDENCE_MEDIUM_THRESHOLD=60 -CONFIDENCE_HIGH_THRESHOLD=80 +CONFIDENCE_MINIMUM_THRESHOLD=40 # Below this = weak recommendation +CONFIDENCE_MEDIUM_THRESHOLD=60 # 40-59 = weak, 60-79 = moderate +CONFIDENCE_HIGH_THRESHOLD=80 # 80+ = strong recommendation + +# Domain keywords for confidence scoring (domain:keyword1,keyword2|domain:keyword3,keyword4) +CONFIDENCE_DOMAIN_KEYWORDS="incident-response:incident,breach,attack,compromise,response|malware-analysis:malware,virus,trojan,reverse,analysis|network-forensics:network,traffic,packet,pcap,wireshark|mobile-forensics:mobile,android,ios,phone,app|cloud-forensics:cloud,aws,azure,saas,paas" # ============================================================================ # PERFORMANCE TUNING PRESETS diff --git a/check-unused-css.js b/check-unused-css.js new file mode 100644 index 0000000..c61aac7 --- /dev/null +++ b/check-unused-css.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node +// find-unused-css.js +// Usage: node find-unused-css [--verbose] + +import fs from 'fs/promises'; +import path from 'path'; +import fg from 'fast-glob'; +import pc from 'picocolors'; +import postcss from 'postcss'; +import safeParser from 'postcss-safe-parser'; + +const [,, cssPath, srcRoot = '.', ...rest] = process.argv; +const verbose = rest.includes('--verbose'); +if (!cssPath) { + console.error('Usage: node find-unused-css '); + process.exit(1); +} + +/* -------------------------------------------------- */ +/* 1. Parse the CSS and harvest class/id tokens */ +/* -------------------------------------------------- */ +const cssRaw = await fs.readFile(cssPath, 'utf8'); +const root = postcss().process(cssRaw, { parser: safeParser }).root; + +const selectorTokens = new Map(); // selector → Set('.foo', '#bar') +const CLASS = /\.([\w-]+)/g; +const ID = /#([\w-]+)/g; + +root.walkRules(rule => { + rule.selectors.forEach(sel => { + const tokens = new Set(); + sel.replace(CLASS, (_, c) => tokens.add('.'+c)); + sel.replace(ID, (_, i) => tokens.add('#'+i)); + if (tokens.size) selectorTokens.set(sel, tokens); + }); +}); + +/* -------------------------------------------------- */ +/* 2. Dynamic classes you add via JS (safe keep) */ +/* -------------------------------------------------- */ +const dynamicAllow = new Set([ + 'hidden', 'active', 'loading', 'open', 'closed' +]); + +/* -------------------------------------------------- */ +/* 3. Read every source file once */ +/* -------------------------------------------------- */ +const files = await fg([ + `${srcRoot}/**/*.{html,htm,js,jsx,ts,tsx,vue,svelte,astro}`, + `!${srcRoot}/**/node_modules/**` +]); +const sources = await Promise.all(files.map(f => fs.readFile(f, 'utf8'))); + +/* -------------------------------------------------- */ +/* 4. Fast search helpers */ +/* -------------------------------------------------- */ +const makeClassRE = cls => + new RegExp( + `(class|className)=['"][^'"]*\\b${cls}\\b[^'"]*['"]|['"\`]${cls}['"\`]`, + 'i' + ); +const makeIdRE = id => + new RegExp(`id=['"]${id}['"]|['"\`]${id}['"\`]`, 'i'); + +const tokenInSources = token => { + // dynamic allow-list + if (dynamicAllow.has(token)) return true; + + const re = token.startsWith('.') + ? makeClassRE(token.slice(1)) + : makeIdRE(token.slice(1)); + + return sources.some(txt => re.test(txt)); +}; + +/* -------------------------------------------------- */ +/* 5. Decide used vs unused */ +/* -------------------------------------------------- */ +const used = []; +const unused = []; + +for (const [selector, tokens] of selectorTokens.entries()) { + const isUsed = [...tokens].some(tokenInSources); // **ANY** token keeps rule + (isUsed ? used : unused).push(selector); + if (verbose) { + console.log(isUsed ? pc.green('✓ '+selector) : pc.red('✗ '+selector)); + } +} + +/* -------------------------------------------------- */ +/* 6. Report & write list */ +/* -------------------------------------------------- */ +console.log( + `\n${pc.bold(pc.blue('🎯 CSS usage summary'))}\n` + + ` selectors total : ${selectorTokens.size}\n` + + ` still used : ${pc.green(used.length)}\n` + + ` maybe unused : ${pc.red(unused.length)}\n` +); + +const outFile = path.resolve('unused-selectors.txt'); +await fs.writeFile(outFile, unused.join('\n')); +console.log(`📝 Unused selector list → ${pc.yellow(outFile)}\n`); diff --git a/package.json b/package.json index 12677b7..7367a40 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,11 @@ "zod": "^3.25.76" }, "devDependencies": { - "@types/js-yaml": "^4.0.9" + "@types/js-yaml": "^4.0.9", + "fast-glob": "^3.3.3", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-safe-parser": "^7.0.1" }, "engines": { "node": ">=18.0.0" diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro index 9b80036..076b322 100644 --- a/src/components/AIQueryInterface.astro +++ b/src/components/AIQueryInterface.astro @@ -1,5 +1,6 @@ --- import { getToolsData } from '../utils/dataService.js'; +import { isToolHosted } from '../utils/toolHelpers.js'; const data = await getToolsData(); const tools = data.tools; @@ -698,7 +699,10 @@ class AIQueryInterface { if (fullTool) { toolsByPhase[recTool.phase].push({ ...fullTool, - recommendation: recTool + recommendation: recTool, + confidence: recTool.confidence, + justification: recTool.justification, + priority: recTool.priority }); } } @@ -999,6 +1003,151 @@ class AIQueryInterface { return String(data); } + renderConfidenceIndicator(confidence) { + if (!confidence || typeof confidence.overall !== 'number') return ''; + + const confidenceColor = confidence.overall >= 80 ? 'var(--color-accent)' : + confidence.overall >= 60 ? 'var(--color-warning)' : 'var(--color-error)'; + + const strengthText = confidence.overall >= 80 ? 'HOCH' : + confidence.overall >= 60 ? 'MITTEL' : 'NIEDRIG'; + + return ` +
+
+
+
+
+ + ${confidence.overall}% + + +
+ +
+ `; + } + + renderWorkflowTool(tool) { + const hasValidProjectUrl = isToolHosted(tool); + const priorityColors = { + high: 'var(--color-error)', + medium: 'var(--color-warning)', + low: 'var(--color-accent)' + }; + + return ` +
+
+

+ ${tool.icon ? `${tool.icon}` : ''} + ${tool.name} +

+
+ + ${tool.recommendation ? tool.recommendation.priority : tool.priority} + +
+
+ + ${tool.confidence ? this.renderConfidenceIndicator(tool.confidence) : ''} + +
+ "${this.sanitizeText(tool.justification || (tool.recommendation && tool.recommendation.justification) || `Empfohlen für ${tool.phase}`)}" +
+ +
+ ${this.renderToolBadges(tool)} +
+ ${tool.type === 'method' ? 'Methode' : tool.platforms.join(', ') + ' • ' + tool.license} +
+
+
+ `; + } + + renderDetailedTool(tool, recommendation, rank) { + const rankColors = { 1: 'var(--color-accent)', 2: 'var(--color-primary)', 3: 'var(--color-warning)' }; + const suitabilityColors = { high: 'var(--color-accent)', medium: 'var(--color-warning)', low: 'var(--color-text-secondary)' }; + + return ` +
+
+ ${rank} +
+ +
+

${tool.name}

+
+ + ${this.getSuitabilityText(recommendation.suitability_score)} + + ${this.renderToolBadges(tool)} +
+ ${recommendation.confidence ? this.renderConfidenceIndicator(recommendation.confidence) : ''} +
+ +
+

Warum diese Methode?

+
${this.sanitizeText(recommendation.detailed_explanation)}
+ + ${recommendation.implementation_approach ? ` +

Anwendungsansatz

+
${this.sanitizeText(recommendation.implementation_approach)}
+ ` : ''} +
+ + ${this.renderProsAndCons(recommendation.pros, recommendation.cons)} + ${this.renderToolMetadata(tool)} + ${recommendation.alternatives ? this.renderAlternatives(recommendation.alternatives) : ''} +
+ `; + } + renderHeader(title, query) { return `
@@ -1099,7 +1248,7 @@ class AIQueryInterface { } renderWorkflowTool(tool) { - const hasValidProjectUrl = this.isToolHosted(tool); + const hasValidProjectUrl = isToolHosted(tool); const priorityColors = { high: 'var(--color-error)', medium: 'var(--color-warning)', @@ -1142,7 +1291,10 @@ class AIQueryInterface { const fullTool = tools.find(t => t.name === toolRec.name); if (!fullTool) return ''; - return this.renderDetailedTool(fullTool, toolRec, index + 1); + return this.renderDetailedTool(fullTool, { + ...toolRec, + confidence: toolRec.confidence + }, index + 1); }).join('')}
`; @@ -1256,7 +1408,7 @@ class AIQueryInterface { renderToolBadges(tool) { const isMethod = tool.type === 'method'; - const hasValidProjectUrl = this.isToolHosted(tool); + const hasValidProjectUrl = isToolHosted(tool); let badges = ''; if (isMethod) { @@ -1300,16 +1452,9 @@ class AIQueryInterface { .trim(); } - isToolHosted(tool) { - return tool.projectUrl !== undefined && - tool.projectUrl !== null && - tool.projectUrl !== "" && - tool.projectUrl.trim() !== ""; - } - getToolClass(tool, context = 'card') { const isMethod = tool.type === 'method'; - const hasValidProjectUrl = this.isToolHosted(tool); + const hasValidProjectUrl = isToolHosted(tool); if (context === 'recommendation') { if (isMethod) return 'method'; @@ -1402,5 +1547,6 @@ document.addEventListener('DOMContentLoaded', () => { aiInterface.hideError(); } }; + window.isToolHosted = window.isToolHosted || isToolHosted }); \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css index b3540f5..823d7b3 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -144,20 +144,11 @@ a:hover { margin: 0 auto; } -.container-wide { - max-width: 1400px; - margin: 0 auto; -} - /* Section Utilities */ .section { padding: 2rem 0; } -.section-sm { - padding: 1rem 0; -} - .section-lg { padding: 3rem 0; } @@ -2218,6 +2209,10 @@ input[type="checkbox"] { border-color: var(--color-method); } +.tool-recommendation:hover .confidence-bar-fill { + box-shadow: 0 0 8px rgba(0,0,0,0.1); +} + .tool-rec-header { display: flex; justify-content: space-between; @@ -2264,6 +2259,57 @@ input[type="checkbox"] { color: var(--color-text-secondary); } +/* =================================================================== + CONFIDENCE INTEGRATION STYLES + ================================================================= */ + +.confidence-indicator-integrated { + position: relative; +} + +.confidence-bar-wrapper { + position: relative; + background-color: var(--color-bg-tertiary); + border-radius: 2px; + overflow: hidden; + border: 1px solid var(--color-border); +} + +.confidence-bar-fill { + transition: width 0.6s ease-out; + position: relative; +} + +.confidence-bar-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(45deg, transparent 25%, rgba(255,255,255,0.1) 25%, rgba(255,255,255,0.1) 50%, transparent 50%, transparent 75%, rgba(255,255,255,0.1) 75%); + background-size: 8px 8px; + animation: confidence-shimmer 2s linear infinite; +} + +.confidence-badge-compact .btn-icon { + transition: var(--transition-fast); +} + +.confidence-badge-compact .btn-icon:hover { + background-color: var(--color-bg-secondary); + transform: scale(1.1); +} + +.confidence-details-popup { + animation: confidence-popup-in 0.2s ease-out; +} + +/* Hover effect for confidence bars */ +.confidence-indicator-integrated:hover .confidence-bar-fill { + filter: brightness(1.1); +} + /* =================================================================== 18. APPROACH SELECTION (CONSOLIDATED) ================================================================= */ @@ -2665,11 +2711,26 @@ footer { animation: ai-spotlight-pulse 0.4s ease-out; } +@keyframes confidence-shimmer { + 0% { transform: translateX(-8px); } + 100% { transform: translateX(8px); } +} + +@keyframes confidence-popup-in { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + /* =================================================================== 21. SMART PROMPTING INTERFACE (MISSING STYLES ADDED BACK) ================================================================= */ -/* Smart Prompting Container */ .smart-prompting-container { height: 100%; animation: smartPromptSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); @@ -3599,6 +3660,26 @@ footer { width: 14px; height: 14px; } + + .confidence-details-popup { + position: fixed !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; + margin: 0 !important; + width: 90vw !important; + max-width: 300px !important; + z-index: 1000 !important; + } + + .confidence-indicator-integrated { + margin-bottom: 0.5rem; + } + + .confidence-badge-compact .btn-icon { + width: 18px; + height: 18px; + } } @media (width <= 640px) { @@ -3924,28 +4005,6 @@ footer { background: linear-gradient(to bottom, var(--color-border) 0%, transparent 100%); } -.confidence-indicator { - position: relative; - overflow: hidden; -} - -.confidence-indicator::after { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient(45deg, transparent 25%, rgba(255,255,255,0.1) 25%, rgba(255,255,255,0.1) 50%, transparent 50%, transparent 75%, rgba(255,255,255,0.1) 75%); - background-size: 4px 4px; - animation: confidence-shimmer 2s linear infinite; -} - -@keyframes confidence-shimmer { - 0% { transform: translateX(-4px); } - 100% { transform: translateX(4px); } -} - .toggle-icon { transition: transform 0.2s ease; } @@ -4000,7 +4059,4 @@ footer { gap: 0.5rem; } - .confidence-indicator { - width: 100%; - } } \ No newline at end of file diff --git a/src/utils/aiPipeline.ts b/src/utils/aiPipeline.ts index 5b14b4d..284b1a5 100644 --- a/src/utils/aiPipeline.ts +++ b/src/utils/aiPipeline.ts @@ -3,6 +3,7 @@ import { getCompressedToolsDataForAI } from './dataService.js'; import { embeddingsService, type EmbeddingData } from './embeddings.js'; import { AI_PROMPTS, getPrompt } from '../config/prompts.js'; +import { isToolHosted } from './toolHelpers.js'; interface AIConfig { endpoint: string; @@ -67,6 +68,16 @@ interface SimilarityResult extends EmbeddingData { similarity: number; } +interface ConfidenceMetrics { + overall: number; // 0-100: Combined confidence score + embeddingsQuality: number; // How well embeddings matched + domainAlignment: number; // How well tools match scenario domain + consensus: number; // How much micro-tasks agree + freshness: number; // How recent/up-to-date the selection is + uncertaintyFactors: string[]; // What could make this wrong + strengthIndicators: string[]; // What makes this recommendation strong +} + class ImprovedMicroTaskAIPipeline { private config: AIConfig; @@ -92,6 +103,16 @@ class ImprovedMicroTaskAIPipeline { detailLevel: 'minimal' | 'standard' | 'verbose'; retentionHours: number; }; + + private confidenceConfig: { + embeddingsWeight: number; + consensusWeight: number; + domainMatchWeight: number; + freshnessWeight: number; + minimumThreshold: number; + mediumThreshold: number; + highThreshold: number; + }; private tempAuditEntries: AuditEntry[] = []; @@ -131,6 +152,21 @@ class ImprovedMicroTaskAIPipeline { noEmbeddingsLimits: `${this.noEmbeddingsToolLimit || 'unlimited'} tools, ${this.noEmbeddingsConceptLimit || 'unlimited'} concepts`, auditEnabled: this.auditConfig.enabled }); + + this.confidenceConfig = { + embeddingsWeight: parseFloat(process.env.CONFIDENCE_EMBEDDINGS_WEIGHT || '0.3'), + consensusWeight: parseFloat(process.env.CONFIDENCE_CONSENSUS_WEIGHT || '0.25'), + domainMatchWeight: parseFloat(process.env.CONFIDENCE_DOMAIN_MATCH_WEIGHT || '0.25'), + freshnessWeight: parseFloat(process.env.CONFIDENCE_FRESHNESS_WEIGHT || '0.2'), + minimumThreshold: parseInt(process.env.CONFIDENCE_MINIMUM_THRESHOLD || '40', 10), + mediumThreshold: parseInt(process.env.CONFIDENCE_MEDIUM_THRESHOLD || '60', 10), + highThreshold: parseInt(process.env.CONFIDENCE_HIGH_THRESHOLD || '80', 10) + }; + + console.log('[AI PIPELINE] Confidence scoring enabled:', { + weights: `E:${this.confidenceConfig.embeddingsWeight} C:${this.confidenceConfig.consensusWeight} D:${this.confidenceConfig.domainMatchWeight} F:${this.confidenceConfig.freshnessWeight}`, + thresholds: `${this.confidenceConfig.minimumThreshold}/${this.confidenceConfig.mediumThreshold}/${this.confidenceConfig.highThreshold}` + }); } private getEnv(key: string): string { @@ -662,6 +698,40 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; } } + private calculateRecommendationConfidence( + tool: any, + embeddingsSimilarity: number, + domainMatch: boolean, + microTaskAgreement: number, + context: AnalysisContext + ): ConfidenceMetrics { + + const embeddingsQuality = Math.min(100, embeddingsSimilarity * 100 * 2); // Scale 0.5 similarity to 100% + const domainAlignment = domainMatch ? 90 : (tool.domains?.length > 0 ? 60 : 30); + const consensus = Math.min(100, microTaskAgreement * 100); + const freshness = this.calculateToolFreshness(tool); + + const overall = ( + embeddingsQuality * this.confidenceConfig.embeddingsWeight + + domainAlignment * this.confidenceConfig.domainMatchWeight + + consensus * this.confidenceConfig.consensusWeight + + freshness * this.confidenceConfig.freshnessWeight + ); + + const uncertaintyFactors = this.identifyUncertaintyFactors(tool, context, overall); + const strengthIndicators = this.identifyStrengthIndicators(tool, context, overall); + + return { + overall: Math.round(overall), + embeddingsQuality: Math.round(embeddingsQuality), + domainAlignment: Math.round(domainAlignment), + consensus: Math.round(consensus), + freshness: Math.round(freshness), + uncertaintyFactors, + strengthIndicators + }; + } + private async analyzeScenario(context: AnalysisContext): Promise { const isWorkflow = context.mode === 'workflow'; const prompt = getPrompt('scenarioAnalysis', isWorkflow, context.userQuery); @@ -1010,6 +1080,124 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; } } + private calculateToolFreshness(tool: any): number { + // Base freshness score + let freshness = 70; // Default for tools without specific freshness data + + // Boost for tools with knowledge base (more maintained) + if (tool.knowledgebase === true) freshness += 20; + + // Boost for hosted tools (actively maintained) + if (isToolHosted(tool)) freshness += 15; + + // Slight boost for open source (community maintained) + if (tool.license && tool.license !== 'Proprietary') freshness += 5; + + return Math.min(100, freshness); + } + + private checkDomainMatch(tool: any, userQuery: string): boolean { + if (!tool.domains || tool.domains.length === 0) return false; + + const queryLower = userQuery.toLowerCase(); + + const domainKeywordsEnv = process.env.CONFIDENCE_DOMAIN_KEYWORDS || ''; + + const domainKeywords = domainKeywordsEnv.split('|').reduce((acc, pair) => { + const [domain, keywords] = pair.split(':'); + acc[domain] = keywords.split(','); + return acc; + }, {}); + + return tool.domains.some(domain => { + const keywords = domainKeywords[domain] || [domain.replace('-', ' ')]; + return keywords.some(keyword => queryLower.includes(keyword)); + }); + } + + private getMicroTaskAgreement(toolName: string, context: AnalysisContext): number { + // Check how many micro-tasks selected this tool + const microTaskEntries = context.auditTrail.filter(entry => + entry.phase === 'micro-task' && + entry.action.includes('selection') && + entry.output && + typeof entry.output === 'object' && + Array.isArray(entry.output.selectedTools) && + entry.output.selectedTools.includes(toolName) + ); + + const totalMicroTasks = context.auditTrail.filter(entry => + entry.phase === 'micro-task' && entry.action.includes('selection') + ).length; + + return totalMicroTasks > 0 ? microTaskEntries.length / totalMicroTasks : 0.8; // Default high agreement + } + + private getEmbeddingsSimilarity(toolName: string, context: AnalysisContext): number { + // Extract similarity from audit trail embeddings entry + const embeddingsEntry = context.auditTrail.find(entry => + entry.phase === 'retrieval' && entry.action === 'embeddings-search' + ); + + if (!embeddingsEntry || !embeddingsEntry.output) return 0.5; // Default medium similarity + + // Look for similarity data in the output (implementation specific) + // This would need to be populated during embeddings search + return 0.7; // Placeholder - would need actual similarity data from embeddings + } + + private identifyUncertaintyFactors(tool: any, context: AnalysisContext, confidence: number): string[] { + const factors: string[] = []; + + if (confidence < this.confidenceConfig.mediumThreshold) { + factors.push('Low overall confidence - consider manual validation'); + } + + if (!this.checkDomainMatch(tool, context.userQuery)) { + factors.push('Domain mismatch detected - tool may not be specifically designed for this scenario'); + } + + if (tool.skillLevel === 'expert' && /rapid|quick|urgent|triage/i.test(context.userQuery)) { + factors.push('Expert-level tool for rapid scenario - may be overcomplicated'); + } + + if (tool.type === 'software' && !isToolHosted(tool) && !tool.url) { + factors.push('Limited access information - availability uncertain'); + } + + if (tool.skillLevel === 'novice' && /complex|advanced|deep/i.test(context.userQuery)) { + factors.push('Novice-level tool for complex scenario - may lack required capabilities'); + } + + return factors; + } + + private identifyStrengthIndicators(tool: any, context: AnalysisContext, confidence: number): string[] { + const indicators: string[] = []; + + if (confidence >= this.confidenceConfig.highThreshold) { + indicators.push('High confidence recommendation based on multiple factors'); + } + + if (this.checkDomainMatch(tool, context.userQuery)) { + indicators.push('Strong domain alignment with scenario requirements'); + } + + if (tool.knowledgebase === true) { + indicators.push('Documentation and knowledge base available for guidance'); + } + + if (isToolHosted(tool)) { + indicators.push('Hosted solution available for immediate access'); + } + + if (tool.type === 'method' && /methodology|approach|process/i.test(context.userQuery)) { + indicators.push('Methodological approach matches procedural inquiry'); + } + + return indicators; + } + private buildRecommendation(context: AnalysisContext, mode: string, finalContent: string): any { const isWorkflow = mode === 'workflow'; @@ -1025,20 +1213,71 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; }; if (isWorkflow) { - return { - ...base, - recommended_tools: context.selectedTools?.map(st => ({ + const recommendedToolsWithConfidence = context.selectedTools?.map(st => { + // Calculate confidence for each tool + const confidence = this.calculateRecommendationConfidence( + st.tool, + this.getEmbeddingsSimilarity(st.tool.name, context), + this.checkDomainMatch(st.tool, context.userQuery), + this.getMicroTaskAgreement(st.tool.name, context), + context + ); + + // Add audit entry for confidence calculation + this.addAuditEntry(context, 'validation', 'confidence-scoring', + { toolName: st.tool.name, phase: st.phase }, + { + overall: confidence.overall, + components: { + embeddings: confidence.embeddingsQuality, + domain: confidence.domainAlignment, + consensus: confidence.consensus, + freshness: confidence.freshness + } + }, + confidence.overall, + Date.now(), + { uncertaintyCount: confidence.uncertaintyFactors.length } + ); + + return { name: st.tool.name, phase: st.phase, priority: st.priority, - justification: st.justification || `Empfohlen für ${st.phase}` - })) || [], + justification: st.justification || `Empfohlen für ${st.phase}`, + confidence: confidence, + recommendationStrength: confidence.overall >= this.confidenceConfig.highThreshold ? 'strong' : + confidence.overall >= this.confidenceConfig.mediumThreshold ? 'moderate' : 'weak' + }; + }) || []; + + return { + ...base, + recommended_tools: recommendedToolsWithConfidence, workflow_suggestion: finalContent }; } else { - return { - ...base, - recommended_tools: context.selectedTools?.map(st => ({ + const recommendedToolsWithConfidence = context.selectedTools?.map(st => { + const confidence = this.calculateRecommendationConfidence( + st.tool, + this.getEmbeddingsSimilarity(st.tool.name, context), + this.checkDomainMatch(st.tool, context.userQuery), + this.getMicroTaskAgreement(st.tool.name, context), + context + ); + + this.addAuditEntry(context, 'validation', 'confidence-scoring', + { toolName: st.tool.name, rank: st.tool.evaluation?.rank || 1 }, + { + overall: confidence.overall, + suitabilityAlignment: st.priority === 'high' && confidence.overall >= this.confidenceConfig.highThreshold + }, + confidence.overall, + Date.now(), + { strengthCount: confidence.strengthIndicators.length } + ); + + return { name: st.tool.name, rank: st.tool.evaluation?.rank || 1, suitability_score: st.priority, @@ -1046,8 +1285,16 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; implementation_approach: st.tool.evaluation?.implementation_approach || '', pros: st.tool.evaluation?.pros || [], cons: st.tool.evaluation?.cons || [], - alternatives: st.tool.evaluation?.alternatives || '' - })) || [], + alternatives: st.tool.evaluation?.alternatives || '', + confidence: confidence, + recommendationStrength: confidence.overall >= this.confidenceConfig.highThreshold ? 'strong' : + confidence.overall >= this.confidenceConfig.mediumThreshold ? 'moderate' : 'weak' + }; + }) || []; + + return { + ...base, + recommended_tools: recommendedToolsWithConfidence, additional_considerations: finalContent }; } diff --git a/unused-selectors.txt b/unused-selectors.txt new file mode 100644 index 0000000..ba12c40 --- /dev/null +++ b/unused-selectors.txt @@ -0,0 +1,89 @@ +.container-wide +.section-sm +.section-lg +.flex-center +.flex-start +.flex-end +.grid-3 +.grid-auto-fit-sm +.grid-auto-fit-lg +.gap-6 +.m-1 +.m-2 +.m-3 +.m-4 +.m-6 +.m-8 +.mt-0 +.mt-6 +.mt-8 +.mr-1 +.mr-4 +.ml-1 +.ml-3 +.ml-4 +.p-0 +.p-1 +.px-1 +.px-2 +.py-1 +.py-3 +.py-4 +.py-6 +.pb-2 +.content-narrow +.content-wide +.card-info-md +.card-info-lg +.info-text +.info-text-center +.grid-auto-300 +.grid-auto-400 +.grid-auto-500 +.pros-cons-section +.tool-metadata +.text-left +.font-normal +.font-bold +.text-error +.inline-block +.relative +.absolute +.sticky +.top-0 +.left-0 +.z-10 +.z-100 +.overflow-auto +.rounded-md +.rounded-2xl +.bg-tertiary +.h-12 +.nav-link +.nav-link:hover +.btn-xs +.card-sm +.card-lg +.card-gradient +.card-hero +.card-concept +.badge-accent +.tool-chip +.tool-chip:hover +.phase-grid +.phase-chip +.phase-chip:hover +.filter-row +.phase-card +.phase-card::before +.phase-card:hover +.tool-rec-metadata +.stat-unit +.share-btn--small +.collaboration-tool-compact +.collaboration-tool-compact:hover +.domain-phase-container +.card-info-xl +.text-3xl +.audit-progress-step +.audit-progress-step::before \ No newline at end of file