/**
* Pure vector / token-budget math, shared by every semantic-selection pass.
*
* This is infrastructure, not domain knowledge: `memory`, `world` and
* `characters` all rank candidates by cosine similarity and fit them under a
* token budget. Keeping the arithmetic here lets those modules stay decoupled
* from each other (they depend on this shared helper, never on one another) and
* removes the drift risk of three byte-identical copies.
*/
/** Cheap token estimate: ~4 characters per token. */
export function estimateTokens(text: string): number {
return Math.ceil(text.length / 4);
}
/**
* Cosine similarity of two equal-length vectors, in [-1, 1]. Defensive: returns
* 0 for missing, empty, mismatched-length, or zero-norm inputs (never throws,
* never NaN). Pure.
*/
export function cosineSimilarity(a: number[], b: number[]): number {
if (!a || !b || a.length === 0 || a.length !== b.length) return 0;
let dot = 0;
let na = 0;
let nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
if (na === 0 || nb === 0) return 0;
return dot / (Math.sqrt(na) * Math.sqrt(nb));
}