Files
simonandCursor 1e1111c90f Qortino AI QDN assembly, share-to-Quitter, categorized saves, File shim fixes
Safety snapshot of mobile work before merging upstream chat-v2:
- Qortino AI: QDN pack discovery/download (Q-Share ids), external app
  storage, install validation, teach/report Q-Mail, PDF thumbs+zoom
- Android share target "Quitter" with native ShareReceiver plugin
- Categorized device saves (Images/Videos/Audio/Documents/Apps/GO state)
- createNamedFile helper: cordova-plugin-file clobbers global File

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 12:47:44 +00:00

111 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Evaluate hash-RAG hit rate against golden questions (no LLM required).
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
// Replicate hash embed + cosine from rag.ts (keep eval dependency-free).
const HASH_DIM = 384;
function hashEmbed(text, dim = HASH_DIM) {
const vec = new Array(dim).fill(0);
const tokens = text
.toLowerCase()
.replace(/[^a-z0-9\s]/g, ' ')
.split(/\s+/)
.filter(Boolean);
for (const token of tokens) {
let h = 2166136261;
for (let i = 0; i < token.length; i++) {
h ^= token.charCodeAt(i);
h = Math.imul(h, 16777619);
}
const idx = Math.abs(h) % dim;
const sign = h & 1 ? 1 : -1;
vec[idx] += sign;
}
let norm = 0;
for (const v of vec) norm += v * v;
norm = Math.sqrt(norm) || 1;
for (let i = 0; i < vec.length; i++) vec[i] /= norm;
return vec;
}
function cosine(a, b) {
const n = Math.min(a.length, b.length);
let dot = 0,
na = 0,
nb = 0;
for (let i = 0; i < n; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
const d = Math.sqrt(na) * Math.sqrt(nb);
return d === 0 ? 0 : dot / d;
}
function loadGolden() {
const dir = path.join(__dirname, 'golden');
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.jsonl'));
const rows = [];
for (const f of files) {
for (const line of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
if (!line.trim()) continue;
rows.push(JSON.parse(line));
}
}
return rows;
}
function main() {
const packPath = path.resolve(
__dirname,
'../../src/qortinoAgent/bundledKnowledge.json'
);
const pack = JSON.parse(fs.readFileSync(packPath, 'utf8'));
const chunks = pack.chunks || [];
const golden = loadGolden();
if (!golden.length) {
console.error('No golden JSONL found');
process.exit(1);
}
let hits = 0;
const misses = [];
for (const g of golden) {
const qv = hashEmbed(g.q);
let best = { score: -1, source: '' };
for (const c of chunks) {
const cv = c.embedding?.length ? c.embedding : hashEmbed(c.text);
const score = cosine(qv, cv);
if (score > best.score) best = { score, source: c.source || '' };
}
const ok =
best.source.includes('/faq/') ||
best.source.includes(g.category) ||
(g.category === 'life-skills' && best.source.includes('life-skills'));
if (ok && best.score >= 0.12) hits += 1;
else misses.push({ id: g.id, q: g.q, best: best.source, score: best.score });
}
console.log(
JSON.stringify(
{
golden: golden.length,
chunks: chunks.length,
categoryHitRate: Number((hits / golden.length).toFixed(3)),
missSample: misses.slice(0, 25),
},
null,
2
)
);
}
main();