// ============================================================================ // Project : localvm-research // File : web/lib/content.js // Purpose : Content index over the synced research-repo snapshot (content/) // Author : Simon-Pierre Boucher // Contact : contact@spboucher.ai // Created : 2026-08-12 // Modified : 2026-08-12 // Platform : macOS / Apple Silicon (arm64) — Node.js // License : All rights reserved (research code) // ============================================================================ "use strict"; const fs = require("fs"); const path = require("path"); const matter = require("gray-matter"); const CONTENT_ROOT = path.join(__dirname, "..", "content"); const TEXT_EXT = new Set([ ".md", ".py", ".sh", ".toml", ".yaml", ".yml", ".cff", ".json", ".js", ".css", ".metal", ".cpp", ".h", ".hpp", ".swift", ".m", ".txt", ".log", ]); /** Resolve a repo-relative path safely inside content/, or return null. */ function safeResolve(rel) { const abs = path.resolve(CONTENT_ROOT, rel); if (!abs.startsWith(CONTENT_ROOT + path.sep) && abs !== CONTENT_ROOT) return null; return abs; } function exists(rel) { const abs = safeResolve(rel); return abs !== null && fs.existsSync(abs); } function readText(rel) { const abs = safeResolve(rel); if (!abs || !fs.existsSync(abs) || !fs.statSync(abs).isFile()) return null; return fs.readFileSync(abs, "utf8"); } /** Read a markdown file, returning { data (front matter), content }. */ function readMarkdown(rel) { const raw = readText(rel); if (raw === null) return null; try { return matter(raw); } catch { return { data: {}, content: raw }; } } /** List a directory inside content/: [{name, rel, dir, size}] */ function listDir(rel) { const abs = safeResolve(rel || "."); if (!abs || !fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) return null; return fs .readdirSync(abs) .filter((n) => !n.startsWith(".") && n !== "node_modules") .map((n) => { const st = fs.statSync(path.join(abs, n)); return { name: n, rel: path.posix.join(rel || "", n), dir: st.isDirectory(), size: st.size, }; }) .sort((a, b) => (a.dir !== b.dir ? (a.dir ? -1 : 1) : a.name.localeCompare(b.name))); } /** Recursively collect files under rel matching a predicate. */ function walk(rel, predicate, out = []) { const entries = listDir(rel); if (!entries) return out; for (const e of entries) { if (e.dir) walk(e.rel, predicate, out); else if (predicate(e)) out.push(e); } return out; } function isTextFile(rel) { return TEXT_EXT.has(path.extname(rel).toLowerCase()) || path.basename(rel) === "Makefile"; } /** Parse research/LOG.md into entries [{title, body}] (## headings). */ function parseLogEntries() { const md = readMarkdown("research/LOG.md"); if (!md) return []; const parts = md.content.split(/^## /m).slice(1); return parts.map((p) => { const nl = p.indexOf("\n"); return { title: p.slice(0, nl).trim(), body: p.slice(nl + 1).replace(/^---\s*$/m, "").trim() }; }); } /** Experiment cards: micro experiments + candidates. */ function listExperiments() { const out = []; for (const base of ["experiments/micro", "experiments"]) { const entries = listDir(base) || []; for (const e of entries) { if (!e.dir || e.name === "micro") continue; const readme = readMarkdown(path.posix.join(e.rel, "README.md")); const hasAnalysis = (readText(path.posix.join(e.rel, "analysis.md")) || "").length > 400; const runs = listResultRuns().filter((r) => r.experiment === e.name || r.experiment.startsWith(e.name + "_")); let purpose = ""; if (readme) { const line = readme.content.split("\n").find((l) => l.trim() && !l.startsWith("#")); purpose = line ? line.trim() : ""; } out.push({ id: e.name, rel: e.rel, purpose, status: runs.length > 0 && hasAnalysis ? "completed" : runs.length > 0 ? "has results" : "scaffolded", runs: runs.length, }); } } return out; } /** Result runs: results///*.json */ function listResultRuns() { const out = []; for (const exp of listDir("results") || []) { if (!exp.dir) continue; for (const run of listDir(exp.rel) || []) { if (!run.dir) continue; const files = (listDir(run.rel) || []).filter((f) => !f.dir); out.push({ experiment: exp.name, timestamp: run.name, rel: run.rel, files }); } } return out.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); } function readJson(rel) { const raw = readText(rel); if (raw === null) return null; try { return JSON.parse(raw); } catch { return null; } } /** Build info written by sync-content.sh (commit, counts, date). */ function buildInfo() { return readJson("build-info.json") || {}; } /** Site-wide stats for the home page. */ function stats() { const bib = readText("research/bibliography.md") || ""; const sources = bib.split("\n").filter((l) => l.trim().startsWith("- ")).length; const gapsDoc = readText("research/research_gaps.md") || ""; const gaps = (gapsDoc.match(/^#{2,3}\s+G\d{2}/gm) || []).length; const experiments = listExperiments(); const notes = (listDir("research/notes") || []).filter((f) => f.name.endsWith(".md") && f.name !== "README.md").length; return { sources, gaps, notes, experiments: experiments.length, experimentsDone: experiments.filter((e) => e.status === "completed").length, resultRuns: listResultRuns().length, logEntries: parseLogEntries().length, build: buildInfo(), }; } /** Latest expH results for the home-page chart: cold random cells. */ function expHChartData() { const runs = listResultRuns().filter((r) => r.experiment === "expH_ssd_feasibility"); if (!runs.length) return null; const data = readJson(path.posix.join(runs[0].rel, "results.json")); if (!data) return null; const cells = (data.cells || []).filter( (c) => c.nocache && !c.gpu_load && c.pattern === "random" && c.block_bytes <= 1 << 20 ); const series = {}; for (const c of cells) { (series[c.threads] ||= []).push({ block: c.block_bytes, mbps: c.mb_per_s_mean }); } for (const k of Object.keys(series)) series[k].sort((a, b) => a.block - b.block); return { series, run: runs[0].timestamp, manifest: data.manifest || {} }; } module.exports = { CONTENT_ROOT, safeResolve, exists, readText, readMarkdown, readJson, listDir, walk, isTextFile, parseLogEntries, listExperiments, listResultRuns, buildInfo, stats, expHChartData, };