import { getGlobalConfig, saveGlobalConfig } from '../config.js' const SKILL_USAGE_DEBOUNCE_MS = 60_000 // Process-lifetime debounce cache — avoids lock + read + parse on debounced // calls. Same pattern as lastConfigStatTime / globalConfigWriteCount in config.ts. const lastWriteBySkill = new Map() /** * Records a skill usage for ranking purposes. * Updates both usage count and last used timestamp. */ export function recordSkillUsage(skillName: string): void { const now = Date.now() const lastWrite = lastWriteBySkill.get(skillName) // The ranking algorithm uses a 7-day half-life, so sub-minute granularity // is irrelevant. Bail out before saveGlobalConfig to avoid lock + file I/O. if (lastWrite !== undefined && now - lastWrite < SKILL_USAGE_DEBOUNCE_MS) { return } lastWriteBySkill.set(skillName, now) saveGlobalConfig(current => { const existing = current.skillUsage?.[skillName] return { ...current, skillUsage: { ...current.skillUsage, [skillName]: { usageCount: (existing?.usageCount ?? 0) + 1, lastUsedAt: now, }, }, } }) } /** * Calculates a usage score for a skill based on frequency and recency. * Higher scores indicate more frequently and recently used skills. * * The score uses exponential decay with a half-life of 7 days, * meaning usage from 7 days ago is worth half as much as usage today. */ export function getSkillUsageScore(skillName: string): number { const config = getGlobalConfig() const usage = config.skillUsage?.[skillName] if (!usage) return 0 // Recency decay: halve score every 7 days const daysSinceUse = (Date.now() - usage.lastUsedAt) / (1000 * 60 * 60 * 24) const recencyFactor = Math.pow(0.5, daysSinceUse / 7) // Minimum recency factor of 0.1 to avoid completely dropping old but heavily used skills return usage.usageCount * Math.max(recencyFactor, 0.1) }