← back to ClawCoder
src/lib/analyzer/matcher.ts
61 lines
import { PROFILES } from '../profiles/data'
import type { Profile } from '../profiles/types'
/**
* Extract meaningful keywords from a text block for comparison.
* Strips markdown syntax and returns lowercased unique words.
*/
function extractKeywords(text: string): Set<string> {
const cleaned = text
.toLowerCase()
.replace(/[#*`\[\](){}|>_~]/g, ' ')
.replace(/https?:\/\/\S+/g, '')
.replace(/[^a-z0-9\s-]/g, ' ')
const words = cleaned.split(/\s+/).filter(w => w.length > 2)
return new Set(words)
}
/**
* Calculate Jaccard similarity between two sets.
*/
function jaccardSimilarity(a: Set<string>, b: Set<string>): number {
let intersection = 0
for (const word of a) {
if (b.has(word)) intersection++
}
const union = new Set([...a, ...b]).size
return union > 0 ? intersection / union : 0
}
/**
* Match CLAUDE.md content against all 15 profiles and return
* the slug of the closest matching profile.
*/
export function detectProfile(content: string): string | undefined {
const contentKeywords = extractKeywords(content)
if (contentKeywords.size === 0) return undefined
let bestSlug: string | undefined
let bestScore = 0
for (const profile of PROFILES) {
const templateKeywords = extractKeywords(profile.template.content)
// Also include persona keywords for better matching
const personaKeywords = extractKeywords(profile.persona)
const combined = new Set([...templateKeywords, ...personaKeywords])
const score = jaccardSimilarity(contentKeywords, combined)
if (score > bestScore) {
bestScore = score
bestSlug = profile.slug
}
}
// Only return a match if similarity is above a minimum threshold
return bestScore > 0.05 ? bestSlug : undefined
}