56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
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<string, number>()
|
|
|
|
/**
|
|
* 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)
|
|
}
|