Files
zeekay 25011a2085 brain: 100% fortemi parity β€” every algorithm shipped across all 4 runtimes
Closed every remaining 🟑 / πŸ”΅ row in PARITY.md. Each algorithm FortΓ©mi
ships now has a real implementation in @hanzo/bot-memory v2.0.0 with
matching ports in hanzo-memory (Python), bot-go (Go), and hanzoai/mcp
(Rust). Cross-runtime tests: 121 TS + 53 Python + 58 Go + 38 Rust = 270
passing.

Landed under packages/memory/

  Retrieval & ranking
    fusion       RRF (k=20 BEIR) + RSF (Weaviate v1.24) + adaptive k +
                 adaptive FTS/semantic weights
    rerank       MMR (Carbonell & Goldstein 1998), cosine
    dedup        chunk-aware deduplication
    script       Unicode script detection (Latin/CJK/emoji/Cyrillic/
                 Arabic/Hebrew/Greek/Devanagari)
    fts          feature-flag matrix, CJK bigrams, emoji trigrams,
                 websearch_to_tsquery parser, SQLite FTS5 MATCH renderer
    embed        embedding registry, asymmetric E5 prefixes, Matryoshka
                 truncation, coarse-dim picker
    two-stage    coarse β†’ fine retrieval via MRL
    federated    cross-archive search with RRF fusion
    multi-memory X-Hanzo-Memory routing (X-Fortemi-Memory alias)
    filters      composable tag/temporal/collection/scope predicates
    temporal     UUIDv7 floor/ceiling + named ranges
    spatial      Haversine + bbox + in-box (PostGIS fallback)

  Knowledge & graph
    graph        normalize β†’ SNN β†’ PFNET-∞ β†’ Louvain maintenance pipeline
    skos         W3C SKOS concept tree (broader/narrower closures,
                 Turtle export)
    fair         Dublin Core XML + JSON-LD
    doc-types    document type registry + auto-detect (filename/MIME/
                 content sniff)
    captions     WebVTT / SRT / RTTM renderers
    sprite       thumbnail sprite-sheet WebVTT cue builder
    code-ast     language-aware syntactic chunker
    email        RFC 2822 / MIME parser (multipart, qp, base64, HTML strip)
    exif         EXIF parser (camera / time / GPS)

  Operational
    events       EventBus + SSE renderer + webhook HMAC signer
    tokenizer    BPE-style estimator + truncate-to-budget
    eval         MRR / recall@k / precision@k / NDCG@k / benchmark
    hardware     tier autodetect + per-tier model recommender
    circuit-breaker  closed / open / half-open
    retry        exponential backoff with full jitter
    range        HTTP Range parser + Content-Range formatter
    tus          TUS v1.0.0 (Create / Patch / metadata / checksum)

  Crypto
    address      wallet-style content-addressable ids (hanzo: / mm:)
    mmpke01      multi-recipient envelope parser/builder + magic-byte sniff

  Inference (packages/memory/inference/)
    index        InferenceBackend trait + registry
    slug         provider-qualified routing (provider:model:tag)
    capabilities capability flags + task-based selector
    runtime-config hot-swap config (db_override β†’ env β†’ default)
    mock         deterministic backend (for tests)
    few-shot     ICL prompt builder (3-5 examples capped)
    refinement   Self-Refine / ReAct / Reflexion patterns
    link-types   rule + LLM link classifier (11 canonical types)
    vision       VisionBackend trait + Ollama default
    transcription Whisper-compatible HTTP client
    diarization  pyannote-compatible HTTP client + alignSpeakers / relabel
    gliner       GLiNER zero-shot NER HTTP client

  Extraction (packages/memory/extract/)
    index        registry + dispatcher
    text         native UTF-8 passthrough
    email        RFC 2822 adapter
    spreadsheet  CSV/TSV pure-JS; xlsx via registerXlsxDecoder
    archive      ZIP central-directory pure-JS reader
    exif-adapter EXIF for images
    code         tree-sitter-style syntactic chunker
    pdf          PdfSidecar HTTP adapter factory
    image        VisionBackend-backed adapter factory
    audio        TranscriptionBackend-backed adapter factory
    video        keyframe + scene + transcript adapter factory
    3d           multi-view 3D model adapter factory
    media-optimize ffmpeg variants (faststart / web-compat / 720p)
                   adapter factory
    structured   JSON-schema structured-extract adapter factory
    summarizer   fast/standard cascade adapter factory
    sprite-adapter thumbnail sprite-sheet adapter factory

Cross-runtime ports
  hanzo-memory (Python)      53 tests, ports of fusion / rerank / dedup /
                             script / fts / embed / temporal / captions /
                             tokenizer / eval / spatial / range / address /
                             graph / doc-types / circuit / retry / slug /
                             runtime-config / link-types
  bot-go (Go)                58 tests, same algorithm set ported with
                             standard-library-only primitives
  hanzoai/mcp::brain (Rust)  38 tests, same algorithm set; sha2 added as
                             dependency (blake3 stand-in)

PARITY.md is now 100% green (βœ… in-monorepo + 🟒 sibling-repo). No πŸ”΅
placeholders remain.
2026-05-11 11:44:08 -07:00

165 lines
5.4 KiB
TypeScript

/**
* Pure-JS RFC 2822 / 5322 email message parser.
*
* Extracts headers, the primary text/plain body, and attachment metadata.
* For full multipart binary extraction, hand the message to
* `hanzo-tools-fs` β€” this module is the brain-side parser that powers
* search and link extraction.
*/
export interface EmailHeader {
name: string;
value: string;
}
export interface EmailAttachment {
filename?: string;
mimeType: string;
size: number;
/** Raw body slice β€” caller can persist. */
body: Uint8Array;
}
export interface ParsedEmail {
headers: EmailHeader[];
from?: string;
to: string[];
cc: string[];
bcc: string[];
subject?: string;
date?: string;
messageId?: string;
/** Decoded text/plain body (first part if multipart). */
textBody: string;
/** HTML body if present. */
htmlBody?: string;
attachments: EmailAttachment[];
}
export function parseEmail(raw: string | Uint8Array): ParsedEmail {
const text = typeof raw === "string" ? raw : new TextDecoder("utf-8").decode(raw);
const split = splitHeadersAndBody(text);
const headers = parseHeaders(split.headers);
const get = (name: string): string | undefined => {
const h = headers.find((x) => x.name.toLowerCase() === name.toLowerCase());
return h?.value;
};
const out: ParsedEmail = {
headers,
from: get("From"),
to: parseAddresses(get("To")),
cc: parseAddresses(get("Cc")),
bcc: parseAddresses(get("Bcc")),
subject: get("Subject"),
date: get("Date"),
messageId: get("Message-ID"),
textBody: "",
attachments: [],
};
const contentType = get("Content-Type") ?? "text/plain";
if (contentType.toLowerCase().startsWith("multipart/")) {
const boundary = contentType.match(/boundary="?([^"]+)"?/i)?.[1];
if (boundary) parseMultipart(split.body, boundary, out);
else out.textBody = split.body;
} else if (contentType.toLowerCase().startsWith("text/html")) {
out.htmlBody = split.body;
out.textBody = stripHtml(split.body);
} else {
out.textBody = split.body;
}
return out;
}
function splitHeadersAndBody(text: string): { headers: string; body: string } {
const idx = text.indexOf("\r\n\r\n");
if (idx !== -1) return { headers: text.slice(0, idx), body: text.slice(idx + 4) };
const idx2 = text.indexOf("\n\n");
if (idx2 !== -1) return { headers: text.slice(0, idx2), body: text.slice(idx2 + 2) };
return { headers: text, body: "" };
}
function parseHeaders(text: string): EmailHeader[] {
const out: EmailHeader[] = [];
const lines = text.split(/\r?\n/);
let cur: EmailHeader | null = null;
for (const line of lines) {
if (/^[\s\t]/.test(line) && cur) {
cur.value += " " + line.trim();
continue;
}
const m = line.match(/^([!-9;-~]+):\s*(.*)$/);
if (m) {
if (cur) out.push(cur);
cur = { name: m[1], value: m[2] };
}
}
if (cur) out.push(cur);
return out;
}
function parseAddresses(value: string | undefined): string[] {
if (!value) return [];
return value.split(",").map((s) => s.trim()).filter(Boolean);
}
function parseMultipart(body: string, boundary: string, out: ParsedEmail): void {
const sep = `--${boundary}`;
const end = `--${boundary}--`;
const parts: string[] = [];
let i = body.indexOf(sep);
while (i !== -1) {
const next = body.indexOf(sep, i + sep.length);
const finish = body.indexOf(end, i + sep.length);
const stop = finish !== -1 && (next === -1 || finish < next) ? finish : next;
if (stop === -1) break;
parts.push(body.slice(i + sep.length, stop).replace(/^\r?\n/, ""));
if (stop === finish) break;
i = next;
}
for (const p of parts) {
const split = splitHeadersAndBody(p);
const partHeaders = parseHeaders(split.headers);
const ct = (partHeaders.find((h) => h.name.toLowerCase() === "content-type")?.value ?? "text/plain").toLowerCase();
const cd = partHeaders.find((h) => h.name.toLowerCase() === "content-disposition")?.value ?? "";
if (cd.toLowerCase().startsWith("attachment")) {
const filename = cd.match(/filename="?([^";]+)"?/i)?.[1];
const enc = partHeaders.find((h) => h.name.toLowerCase() === "content-transfer-encoding")?.value?.toLowerCase();
const decoded = decodeBody(split.body, enc);
out.attachments.push({
filename,
mimeType: ct.split(";")[0],
size: decoded.length,
body: decoded,
});
} else if (ct.startsWith("text/html") && !out.htmlBody) {
out.htmlBody = split.body;
if (!out.textBody) out.textBody = stripHtml(split.body);
} else if (ct.startsWith("text/plain") && !out.textBody) {
out.textBody = split.body;
}
}
}
function decodeBody(body: string, enc?: string): Uint8Array {
if (enc === "base64") {
return Uint8Array.from(Buffer.from(body.replace(/\s+/g, ""), "base64"));
}
if (enc === "quoted-printable") {
return new TextEncoder().encode(decodeQuotedPrintable(body));
}
return new TextEncoder().encode(body);
}
function decodeQuotedPrintable(s: string): string {
return s
.replace(/=\r?\n/g, "")
.replace(/=([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
}
function stripHtml(html: string): string {
return html.replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\s+/g, " ").trim();
}