diff --git a/README.md b/README.md
index d338cf68..450f344b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,6 @@
# deepevents.ai
deepevents.ai main codebase
+
+## Research Assistant Bounty Slices
+
+- [Literature freshness review assistant](literature-freshness-review-assistant/README.md)
diff --git a/literature-freshness-review-assistant/README.md b/literature-freshness-review-assistant/README.md
new file mode 100644
index 00000000..33fcbbe2
--- /dev/null
+++ b/literature-freshness-review-assistant/README.md
@@ -0,0 +1,33 @@
+# Literature Freshness Review Assistant
+
+This is a focused AI-Powered Research Assistant Suite slice for SCIBASE issue #16. It checks whether reviewer-facing manuscript claims rely on current evidence before the assistant marks a packet ready.
+
+## Scope
+
+- Flags stale support citations outside a configurable freshness window.
+- Detects newer systematic reviews, reporting guidelines, dataset releases, and benchmark updates that are missing from a claim.
+- Escalates newer contradictory evidence as a review hold.
+- Detects dataset and benchmark version drift for claims using "current" or "state-of-the-art" wording.
+- Emits reviewer actions, research-gap prompts, stable audit digests, and deterministic JSON/Markdown/SVG/MP4 artifacts.
+
+This is intentionally separate from broad assistant suites, citation-context reconciliation, retraction notices, statistics review, benchmark-leakage checks, reporting-guideline compliance, figure/table checks, prompt-safety guards, and assay control/calibration completeness.
+
+## Files
+
+- `index.js` - freshness evaluator and report renderers
+- `sample-data.js` - synthetic evidence ledger and manuscript packets
+- `test.js` - dependency-free assertion tests
+- `demo.js` - deterministic JSON/Markdown/SVG report generator
+- `scripts/render-demo-video.js` - optional ffmpeg MP4 renderer
+- `reports/` - generated reviewer artifacts
+
+## Validate
+
+```bash
+npm run check
+npm test
+npm run demo
+npm run demo:video
+```
+
+Synthetic data only. The module does not call external APIs, scan private manuscripts, use credentials, or contact payment services.
diff --git a/literature-freshness-review-assistant/demo.js b/literature-freshness-review-assistant/demo.js
new file mode 100644
index 00000000..4c437104
--- /dev/null
+++ b/literature-freshness-review-assistant/demo.js
@@ -0,0 +1,28 @@
+const fs = require("fs");
+const path = require("path");
+
+const {
+ evaluateFreshnessReview,
+ renderMarkdown,
+ renderSvg
+} = require("./index");
+const {
+ freshnessPolicy,
+ evidenceLedger,
+ manuscriptPackets
+} = require("./sample-data");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const report = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy);
+fs.writeFileSync(path.join(reportsDir, "demo.json"), `${JSON.stringify(report, null, 2)}\n`);
+fs.writeFileSync(path.join(reportsDir, "demo.md"), renderMarkdown(report));
+fs.writeFileSync(path.join(reportsDir, "demo.svg"), renderSvg(report));
+
+console.log(`status=${report.manuscripts[0].summary.status}`);
+console.log(`reviewed=${report.aggregate.reviewedManuscripts}`);
+console.log(`held=${report.aggregate.heldManuscripts}`);
+console.log(`critical=${report.aggregate.criticalFindings}`);
+console.log(`major=${report.aggregate.majorFindings}`);
+console.log(`digest=${report.auditDigest}`);
diff --git a/literature-freshness-review-assistant/index.js b/literature-freshness-review-assistant/index.js
new file mode 100644
index 00000000..c0f3d6b4
--- /dev/null
+++ b/literature-freshness-review-assistant/index.js
@@ -0,0 +1,322 @@
+const crypto = require("crypto");
+
+const SEVERITY_RANK = {
+ info: 0,
+ warning: 1,
+ major: 2,
+ critical: 3
+};
+
+function yearsBetween(olderDate, newerDate) {
+ const older = new Date(`${olderDate}T00:00:00Z`);
+ const newer = new Date(`${newerDate}T00:00:00Z`);
+ return (newer.getTime() - older.getTime()) / (365.25 * 24 * 60 * 60 * 1000);
+}
+
+function latestDate(dates) {
+ const validDates = dates.filter(Boolean).sort();
+ return validDates[validDates.length - 1] || null;
+}
+
+function evidenceForTopic(ledger, topic) {
+ return ledger.signals.filter((signal) => signal.topic === topic);
+}
+
+function addFinding(findings, finding) {
+ findings.push({
+ severity: finding.severity,
+ code: finding.code,
+ claimId: finding.claimId,
+ topic: finding.topic,
+ detail: finding.detail,
+ requiredAction: finding.requiredAction,
+ evidenceSignalId: finding.evidenceSignalId || null
+ });
+}
+
+function evaluateClaimFreshness(claim, ledger, policy) {
+ const findings = [];
+ const signals = evidenceForTopic(ledger, claim.topic);
+ const latestCitation = latestDate(claim.citationDates || []);
+ const reviewDate = policy.reviewDate;
+
+ if (!latestCitation) {
+ addFinding(findings, {
+ severity: "critical",
+ code: "NO_SUPPORT_DATES",
+ claimId: claim.id,
+ topic: claim.topic,
+ detail: "Claim has no dated supporting citations for freshness review.",
+ requiredAction: "Attach dated evidence before the assistant marks this claim reviewer-ready."
+ });
+ } else if (yearsBetween(latestCitation, reviewDate) > policy.staleAfterYears) {
+ addFinding(findings, {
+ severity: "major",
+ code: "STALE_SUPPORT_WINDOW",
+ claimId: claim.id,
+ topic: claim.topic,
+ detail: `Latest support is ${latestCitation}, older than the ${policy.staleAfterYears}-year freshness window.`,
+ requiredAction: "Add recent evidence or downgrade the claim wording."
+ });
+ }
+
+ for (const signal of signals) {
+ const cited = (claim.citedCurrentSignalIds || []).includes(signal.id);
+ const signalIsNewer = !latestCitation || signal.published > latestCitation;
+
+ if (!cited && signalIsNewer) {
+ const severity = signal.relation === "contradicts" ? policy.contradictionSeverity : "major";
+ addFinding(findings, {
+ severity,
+ code: signal.relation === "contradicts" ? "NEWER_CONTRADICTORY_EVIDENCE" : "MISSING_NEWER_EVIDENCE",
+ claimId: claim.id,
+ topic: claim.topic,
+ detail: `${signal.title} (${signal.published}) ${signal.relation} older support but is not cited.`,
+ requiredAction: signal.requirement,
+ evidenceSignalId: signal.id
+ });
+ }
+
+ if (signal.currentVersion && claim.datasetVersion && claim.datasetVersion !== signal.currentVersion) {
+ addFinding(findings, {
+ severity: "major",
+ code: "DATASET_VERSION_DRIFT",
+ claimId: claim.id,
+ topic: claim.topic,
+ detail: `Claim uses dataset ${claim.datasetVersion}; current ledger version is ${signal.currentVersion}.`,
+ requiredAction: signal.requirement,
+ evidenceSignalId: signal.id
+ });
+ }
+
+ if (signal.currentVersion && claim.benchmarkVersion && claim.benchmarkVersion !== signal.currentVersion) {
+ addFinding(findings, {
+ severity: "major",
+ code: "BENCHMARK_VERSION_DRIFT",
+ claimId: claim.id,
+ topic: claim.topic,
+ detail: `Claim uses benchmark ${claim.benchmarkVersion}; current ledger version is ${signal.currentVersion}.`,
+ requiredAction: signal.requirement,
+ evidenceSignalId: signal.id
+ });
+ }
+ }
+
+ if (findings.length === 0) {
+ addFinding(findings, {
+ severity: "info",
+ code: "FRESHNESS_READY",
+ claimId: claim.id,
+ topic: claim.topic,
+ detail: "Claim cites the current evidence signal for its topic.",
+ requiredAction: "No freshness hold required."
+ });
+ }
+
+ return findings;
+}
+
+function summarizeFindings(findings, policy) {
+ const actionable = findings.filter((finding) => finding.severity !== "info");
+ const critical = findings.filter((finding) => finding.severity === "critical");
+ const major = findings.filter((finding) => finding.severity === "major");
+ const warnings = findings.filter((finding) => finding.severity === "warning");
+ const maxSeverity = findings.reduce((max, finding) => {
+ return SEVERITY_RANK[finding.severity] > SEVERITY_RANK[max] ? finding.severity : max;
+ }, "info");
+ const status = SEVERITY_RANK[maxSeverity] >= SEVERITY_RANK[policy.holdSeverityCutoff]
+ ? "hold_freshness_review"
+ : actionable.length > 0
+ ? "revise_freshness_context"
+ : "ready_for_review";
+
+ return {
+ status,
+ totalFindings: findings.length,
+ actionableFindings: actionable.length,
+ criticalFindings: critical.length,
+ majorFindings: major.length,
+ warningFindings: warnings.length,
+ readyFindings: findings.length - actionable.length
+ };
+}
+
+function buildReviewerActions(findings) {
+ return findings
+ .filter((finding) => finding.severity !== "info")
+ .map((finding) => ({
+ claimId: finding.claimId,
+ severity: finding.severity,
+ action: finding.requiredAction,
+ reason: finding.detail
+ }));
+}
+
+function buildResearchGapPrompts(findings) {
+ const topics = new Map();
+ for (const finding of findings) {
+ if (finding.severity === "info") {
+ continue;
+ }
+ if (!topics.has(finding.topic)) {
+ topics.set(
+ finding.topic,
+ `Freshness gap: update the evidence base for ${finding.topic} before treating the claim as current.`
+ );
+ }
+ }
+ return [...topics.values()];
+}
+
+function stableDigest(value) {
+ return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
+}
+
+function evaluateManuscript(packet, ledger, policy) {
+ const findings = packet.claims.flatMap((claim) => evaluateClaimFreshness(claim, ledger, policy));
+ const summary = summarizeFindings(findings, policy);
+ const reviewerActions = buildReviewerActions(findings);
+ const researchGapPrompts = buildResearchGapPrompts(findings);
+ const result = {
+ packetId: packet.id,
+ title: packet.title,
+ domain: packet.domain,
+ reviewDate: policy.reviewDate,
+ summary,
+ findings,
+ reviewerActions,
+ researchGapPrompts
+ };
+ return {
+ ...result,
+ auditDigest: stableDigest(result)
+ };
+}
+
+function evaluateFreshnessReview(packets, ledger, policy) {
+ const manuscripts = packets.map((packet) => evaluateManuscript(packet, ledger, policy));
+ const aggregate = {
+ reviewedManuscripts: manuscripts.length,
+ heldManuscripts: manuscripts.filter((entry) => entry.summary.status === "hold_freshness_review").length,
+ revisionManuscripts: manuscripts.filter((entry) => entry.summary.status === "revise_freshness_context").length,
+ readyManuscripts: manuscripts.filter((entry) => entry.summary.status === "ready_for_review").length,
+ criticalFindings: manuscripts.reduce((sum, entry) => sum + entry.summary.criticalFindings, 0),
+ majorFindings: manuscripts.reduce((sum, entry) => sum + entry.summary.majorFindings, 0),
+ actionableFindings: manuscripts.reduce((sum, entry) => sum + entry.summary.actionableFindings, 0)
+ };
+ const result = {
+ assistant: "literature-freshness-review-assistant",
+ policy,
+ aggregate,
+ manuscripts
+ };
+ return {
+ ...result,
+ auditDigest: stableDigest(result)
+ };
+}
+
+function renderMarkdown(report) {
+ const lines = [
+ "# Literature Freshness Review Assistant",
+ "",
+ `Review date: ${report.policy.reviewDate}`,
+ `Overall digest: ${report.auditDigest}`,
+ "",
+ "## Aggregate",
+ "",
+ `- Reviewed manuscripts: ${report.aggregate.reviewedManuscripts}`,
+ `- Held manuscripts: ${report.aggregate.heldManuscripts}`,
+ `- Revision manuscripts: ${report.aggregate.revisionManuscripts}`,
+ `- Ready manuscripts: ${report.aggregate.readyManuscripts}`,
+ `- Critical findings: ${report.aggregate.criticalFindings}`,
+ `- Major findings: ${report.aggregate.majorFindings}`,
+ `- Actionable findings: ${report.aggregate.actionableFindings}`,
+ ""
+ ];
+
+ for (const manuscript of report.manuscripts) {
+ lines.push(`## ${manuscript.title}`);
+ lines.push("");
+ lines.push(`Status: ${manuscript.summary.status}`);
+ lines.push(`Digest: ${manuscript.auditDigest}`);
+ lines.push("");
+ lines.push("| Severity | Code | Claim | Detail | Required action |");
+ lines.push("| --- | --- | --- | --- | --- |");
+ for (const finding of manuscript.findings) {
+ lines.push(
+ `| ${finding.severity} | ${finding.code} | ${finding.claimId} | ${finding.detail} | ${finding.requiredAction} |`
+ );
+ }
+ lines.push("");
+ if (manuscript.researchGapPrompts.length > 0) {
+ lines.push("Research gap prompts:");
+ for (const prompt of manuscript.researchGapPrompts) {
+ lines.push(`- ${prompt}`);
+ }
+ lines.push("");
+ }
+ }
+
+ return `${lines.join("\n").trimEnd()}\n`;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """);
+}
+
+function renderSvg(report) {
+ const held = report.aggregate.heldManuscripts;
+ const ready = report.aggregate.readyManuscripts;
+ const critical = report.aggregate.criticalFindings;
+ const major = report.aggregate.majorFindings;
+ const bars = [
+ { label: "Held", value: held, color: "#b91c1c" },
+ { label: "Ready", value: ready, color: "#047857" },
+ { label: "Critical", value: critical, color: "#dc2626" },
+ { label: "Major", value: major, color: "#d97706" }
+ ];
+ const maxValue = Math.max(1, ...bars.map((bar) => bar.value));
+ const rows = bars.map((bar, index) => {
+ const y = 170 + index * 70;
+ const width = Math.round((bar.value / maxValue) * 560);
+ return ` ${escapeXml(bar.label)}
+
+
+ ${bar.value}`;
+ }).join("\n");
+
+ return `
+`;
+}
+
+module.exports = {
+ evaluateClaimFreshness,
+ evaluateManuscript,
+ evaluateFreshnessReview,
+ renderMarkdown,
+ renderSvg,
+ stableDigest
+};
diff --git a/literature-freshness-review-assistant/package.json b/literature-freshness-review-assistant/package.json
new file mode 100644
index 00000000..c867f4ce
--- /dev/null
+++ b/literature-freshness-review-assistant/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "literature-freshness-review-assistant",
+ "version": "1.0.0",
+ "description": "Freshness-focused AI research assistant review slice for SCIBASE issue #16.",
+ "main": "index.js",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js && node --check scripts/render-demo-video.js",
+ "test": "node test.js",
+ "demo": "node demo.js",
+ "demo:video": "node scripts/render-demo-video.js"
+ },
+ "license": "MIT"
+}
diff --git a/literature-freshness-review-assistant/reports/demo.json b/literature-freshness-review-assistant/reports/demo.json
new file mode 100644
index 00000000..917be5be
--- /dev/null
+++ b/literature-freshness-review-assistant/reports/demo.json
@@ -0,0 +1,217 @@
+{
+ "assistant": "literature-freshness-review-assistant",
+ "policy": {
+ "reviewDate": "2026-05-23",
+ "staleAfterYears": 4,
+ "contradictionSeverity": "critical",
+ "requiredEvidenceTypes": [
+ "systematic_review",
+ "guideline",
+ "dataset_release",
+ "benchmark_update"
+ ],
+ "holdSeverityCutoff": "critical"
+ },
+ "aggregate": {
+ "reviewedManuscripts": 2,
+ "heldManuscripts": 1,
+ "revisionManuscripts": 0,
+ "readyManuscripts": 1,
+ "criticalFindings": 1,
+ "majorFindings": 8,
+ "actionableFindings": 9
+ },
+ "manuscripts": [
+ {
+ "packetId": "ms-hold-senolytic-review",
+ "title": "Senolytic cardiac safety manuscript",
+ "domain": "translational cardiometabolic biology",
+ "reviewDate": "2026-05-23",
+ "summary": {
+ "status": "hold_freshness_review",
+ "totalFindings": 9,
+ "actionableFindings": 9,
+ "criticalFindings": 1,
+ "majorFindings": 8,
+ "warningFindings": 0,
+ "readyFindings": 0
+ },
+ "findings": [
+ {
+ "severity": "major",
+ "code": "STALE_SUPPORT_WINDOW",
+ "claimId": "claim-tolerability",
+ "topic": "senolytic cardiometabolic safety",
+ "detail": "Latest support is 2021-06-01, older than the 4-year freshness window.",
+ "requiredAction": "Add recent evidence or downgrade the claim wording.",
+ "evidenceSignalId": null
+ },
+ {
+ "severity": "critical",
+ "code": "NEWER_CONTRADICTORY_EVIDENCE",
+ "claimId": "claim-tolerability",
+ "topic": "senolytic cardiometabolic safety",
+ "detail": "2025 systematic review of senolytic cardiometabolic safety (2025-11-18) contradicts older support but is not cited.",
+ "requiredAction": "Discuss the larger safety signal before claiming broad tolerability.",
+ "evidenceSignalId": "ev-sr-2025-senolytic-cardiometabolic"
+ },
+ {
+ "severity": "major",
+ "code": "STALE_SUPPORT_WINDOW",
+ "claimId": "claim-organoid-methods",
+ "topic": "organoid assay reporting",
+ "detail": "Latest support is 2022-02-10, older than the 4-year freshness window.",
+ "requiredAction": "Add recent evidence or downgrade the claim wording.",
+ "evidenceSignalId": null
+ },
+ {
+ "severity": "major",
+ "code": "MISSING_NEWER_EVIDENCE",
+ "claimId": "claim-organoid-methods",
+ "topic": "organoid assay reporting",
+ "detail": "SCIBASE organoid reporting guidance 2026.1 (2026-02-04) updates older support but is not cited.",
+ "requiredAction": "Use the 2026.1 organoid reporting checklist for reviewer packets.",
+ "evidenceSignalId": "ev-guide-2026-organoid-reporting"
+ },
+ {
+ "severity": "major",
+ "code": "STALE_SUPPORT_WINDOW",
+ "claimId": "claim-atlas-normalization",
+ "topic": "single-cell atlas normalization",
+ "detail": "Latest support is 2021-12-12, older than the 4-year freshness window.",
+ "requiredAction": "Add recent evidence or downgrade the claim wording.",
+ "evidenceSignalId": null
+ },
+ {
+ "severity": "major",
+ "code": "MISSING_NEWER_EVIDENCE",
+ "claimId": "claim-atlas-normalization",
+ "topic": "single-cell atlas normalization",
+ "detail": "Single Cell Atlas v4.0 release notes (2026-03-14) supersedes older support but is not cited.",
+ "requiredAction": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.",
+ "evidenceSignalId": "ev-data-2026-single-cell-atlas-v4"
+ },
+ {
+ "severity": "major",
+ "code": "DATASET_VERSION_DRIFT",
+ "claimId": "claim-atlas-normalization",
+ "topic": "single-cell atlas normalization",
+ "detail": "Claim uses dataset v2.1; current ledger version is v4.0.",
+ "requiredAction": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.",
+ "evidenceSignalId": "ev-data-2026-single-cell-atlas-v4"
+ },
+ {
+ "severity": "major",
+ "code": "MISSING_NEWER_EVIDENCE",
+ "claimId": "claim-protein-benchmark",
+ "topic": "protein structure benchmark",
+ "detail": "Protein structure benchmark leaderboard recalibration (2026-01-22) updates older support but is not cited.",
+ "requiredAction": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.",
+ "evidenceSignalId": "ev-bench-2026-protein-leaderboard"
+ },
+ {
+ "severity": "major",
+ "code": "BENCHMARK_VERSION_DRIFT",
+ "claimId": "claim-protein-benchmark",
+ "topic": "protein structure benchmark",
+ "detail": "Claim uses benchmark 2024.10; current ledger version is 2026.01.",
+ "requiredAction": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.",
+ "evidenceSignalId": "ev-bench-2026-protein-leaderboard"
+ }
+ ],
+ "reviewerActions": [
+ {
+ "claimId": "claim-tolerability",
+ "severity": "major",
+ "action": "Add recent evidence or downgrade the claim wording.",
+ "reason": "Latest support is 2021-06-01, older than the 4-year freshness window."
+ },
+ {
+ "claimId": "claim-tolerability",
+ "severity": "critical",
+ "action": "Discuss the larger safety signal before claiming broad tolerability.",
+ "reason": "2025 systematic review of senolytic cardiometabolic safety (2025-11-18) contradicts older support but is not cited."
+ },
+ {
+ "claimId": "claim-organoid-methods",
+ "severity": "major",
+ "action": "Add recent evidence or downgrade the claim wording.",
+ "reason": "Latest support is 2022-02-10, older than the 4-year freshness window."
+ },
+ {
+ "claimId": "claim-organoid-methods",
+ "severity": "major",
+ "action": "Use the 2026.1 organoid reporting checklist for reviewer packets.",
+ "reason": "SCIBASE organoid reporting guidance 2026.1 (2026-02-04) updates older support but is not cited."
+ },
+ {
+ "claimId": "claim-atlas-normalization",
+ "severity": "major",
+ "action": "Add recent evidence or downgrade the claim wording.",
+ "reason": "Latest support is 2021-12-12, older than the 4-year freshness window."
+ },
+ {
+ "claimId": "claim-atlas-normalization",
+ "severity": "major",
+ "action": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.",
+ "reason": "Single Cell Atlas v4.0 release notes (2026-03-14) supersedes older support but is not cited."
+ },
+ {
+ "claimId": "claim-atlas-normalization",
+ "severity": "major",
+ "action": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.",
+ "reason": "Claim uses dataset v2.1; current ledger version is v4.0."
+ },
+ {
+ "claimId": "claim-protein-benchmark",
+ "severity": "major",
+ "action": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.",
+ "reason": "Protein structure benchmark leaderboard recalibration (2026-01-22) updates older support but is not cited."
+ },
+ {
+ "claimId": "claim-protein-benchmark",
+ "severity": "major",
+ "action": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.",
+ "reason": "Claim uses benchmark 2024.10; current ledger version is 2026.01."
+ }
+ ],
+ "researchGapPrompts": [
+ "Freshness gap: update the evidence base for senolytic cardiometabolic safety before treating the claim as current.",
+ "Freshness gap: update the evidence base for organoid assay reporting before treating the claim as current.",
+ "Freshness gap: update the evidence base for single-cell atlas normalization before treating the claim as current.",
+ "Freshness gap: update the evidence base for protein structure benchmark before treating the claim as current."
+ ],
+ "auditDigest": "2b28dfa1b6fc17166e45aa06df339015e0dec9da568f3a185015dab564cad637"
+ },
+ {
+ "packetId": "ms-ready-telemetry-review",
+ "title": "Telemetry implant arrhythmia replication note",
+ "domain": "clinical devices",
+ "reviewDate": "2026-05-23",
+ "summary": {
+ "status": "ready_for_review",
+ "totalFindings": 1,
+ "actionableFindings": 0,
+ "criticalFindings": 0,
+ "majorFindings": 0,
+ "warningFindings": 0,
+ "readyFindings": 1
+ },
+ "findings": [
+ {
+ "severity": "info",
+ "code": "FRESHNESS_READY",
+ "claimId": "claim-telemetry-risk",
+ "topic": "telemetry implant arrhythmia",
+ "detail": "Claim cites the current evidence signal for its topic.",
+ "requiredAction": "No freshness hold required.",
+ "evidenceSignalId": null
+ }
+ ],
+ "reviewerActions": [],
+ "researchGapPrompts": [],
+ "auditDigest": "30b9621c84e89262d698c368e40fcd6fd32bde83bb05cf9be0e70ad5823c4287"
+ }
+ ],
+ "auditDigest": "45a5a7683b859d96101b72cc8442e889cd630ad2736fcebe4dd6b85302cd0cd8"
+}
diff --git a/literature-freshness-review-assistant/reports/demo.md b/literature-freshness-review-assistant/reports/demo.md
new file mode 100644
index 00000000..3e2ac752
--- /dev/null
+++ b/literature-freshness-review-assistant/reports/demo.md
@@ -0,0 +1,46 @@
+# Literature Freshness Review Assistant
+
+Review date: 2026-05-23
+Overall digest: 45a5a7683b859d96101b72cc8442e889cd630ad2736fcebe4dd6b85302cd0cd8
+
+## Aggregate
+
+- Reviewed manuscripts: 2
+- Held manuscripts: 1
+- Revision manuscripts: 0
+- Ready manuscripts: 1
+- Critical findings: 1
+- Major findings: 8
+- Actionable findings: 9
+
+## Senolytic cardiac safety manuscript
+
+Status: hold_freshness_review
+Digest: 2b28dfa1b6fc17166e45aa06df339015e0dec9da568f3a185015dab564cad637
+
+| Severity | Code | Claim | Detail | Required action |
+| --- | --- | --- | --- | --- |
+| major | STALE_SUPPORT_WINDOW | claim-tolerability | Latest support is 2021-06-01, older than the 4-year freshness window. | Add recent evidence or downgrade the claim wording. |
+| critical | NEWER_CONTRADICTORY_EVIDENCE | claim-tolerability | 2025 systematic review of senolytic cardiometabolic safety (2025-11-18) contradicts older support but is not cited. | Discuss the larger safety signal before claiming broad tolerability. |
+| major | STALE_SUPPORT_WINDOW | claim-organoid-methods | Latest support is 2022-02-10, older than the 4-year freshness window. | Add recent evidence or downgrade the claim wording. |
+| major | MISSING_NEWER_EVIDENCE | claim-organoid-methods | SCIBASE organoid reporting guidance 2026.1 (2026-02-04) updates older support but is not cited. | Use the 2026.1 organoid reporting checklist for reviewer packets. |
+| major | STALE_SUPPORT_WINDOW | claim-atlas-normalization | Latest support is 2021-12-12, older than the 4-year freshness window. | Add recent evidence or downgrade the claim wording. |
+| major | MISSING_NEWER_EVIDENCE | claim-atlas-normalization | Single Cell Atlas v4.0 release notes (2026-03-14) supersedes older support but is not cited. | Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations. |
+| major | DATASET_VERSION_DRIFT | claim-atlas-normalization | Claim uses dataset v2.1; current ledger version is v4.0. | Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations. |
+| major | MISSING_NEWER_EVIDENCE | claim-protein-benchmark | Protein structure benchmark leaderboard recalibration (2026-01-22) updates older support but is not cited. | Refresh benchmark claims against the recalibrated 2026.01 leaderboard. |
+| major | BENCHMARK_VERSION_DRIFT | claim-protein-benchmark | Claim uses benchmark 2024.10; current ledger version is 2026.01. | Refresh benchmark claims against the recalibrated 2026.01 leaderboard. |
+
+Research gap prompts:
+- Freshness gap: update the evidence base for senolytic cardiometabolic safety before treating the claim as current.
+- Freshness gap: update the evidence base for organoid assay reporting before treating the claim as current.
+- Freshness gap: update the evidence base for single-cell atlas normalization before treating the claim as current.
+- Freshness gap: update the evidence base for protein structure benchmark before treating the claim as current.
+
+## Telemetry implant arrhythmia replication note
+
+Status: ready_for_review
+Digest: 30b9621c84e89262d698c368e40fcd6fd32bde83bb05cf9be0e70ad5823c4287
+
+| Severity | Code | Claim | Detail | Required action |
+| --- | --- | --- | --- | --- |
+| info | FRESHNESS_READY | claim-telemetry-risk | Claim cites the current evidence signal for its topic. | No freshness hold required. |
diff --git a/literature-freshness-review-assistant/reports/demo.mp4 b/literature-freshness-review-assistant/reports/demo.mp4
new file mode 100644
index 00000000..31227d38
Binary files /dev/null and b/literature-freshness-review-assistant/reports/demo.mp4 differ
diff --git a/literature-freshness-review-assistant/reports/demo.svg b/literature-freshness-review-assistant/reports/demo.svg
new file mode 100644
index 00000000..5081e22b
--- /dev/null
+++ b/literature-freshness-review-assistant/reports/demo.svg
@@ -0,0 +1,34 @@
+
diff --git a/literature-freshness-review-assistant/sample-data.js b/literature-freshness-review-assistant/sample-data.js
new file mode 100644
index 00000000..1506269c
--- /dev/null
+++ b/literature-freshness-review-assistant/sample-data.js
@@ -0,0 +1,144 @@
+const freshnessPolicy = {
+ reviewDate: "2026-05-23",
+ staleAfterYears: 4,
+ contradictionSeverity: "critical",
+ requiredEvidenceTypes: [
+ "systematic_review",
+ "guideline",
+ "dataset_release",
+ "benchmark_update"
+ ],
+ holdSeverityCutoff: "critical"
+};
+
+const evidenceLedger = {
+ signals: [
+ {
+ id: "ev-sr-2025-senolytic-cardiometabolic",
+ topic: "senolytic cardiometabolic safety",
+ evidenceType: "systematic_review",
+ title: "2025 systematic review of senolytic cardiometabolic safety",
+ published: "2025-11-18",
+ relation: "contradicts",
+ requirement: "Discuss the larger safety signal before claiming broad tolerability.",
+ minCitationDate: "2025-11-18"
+ },
+ {
+ id: "ev-guide-2026-organoid-reporting",
+ topic: "organoid assay reporting",
+ evidenceType: "guideline",
+ title: "SCIBASE organoid reporting guidance 2026.1",
+ published: "2026-02-04",
+ relation: "updates",
+ requirement: "Use the 2026.1 organoid reporting checklist for reviewer packets.",
+ minCitationDate: "2026-02-04"
+ },
+ {
+ id: "ev-data-2026-single-cell-atlas-v4",
+ topic: "single-cell atlas normalization",
+ evidenceType: "dataset_release",
+ title: "Single Cell Atlas v4.0 release notes",
+ published: "2026-03-14",
+ relation: "supersedes",
+ currentVersion: "v4.0",
+ requirement: "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.",
+ minCitationDate: "2026-03-14"
+ },
+ {
+ id: "ev-bench-2026-protein-leaderboard",
+ topic: "protein structure benchmark",
+ evidenceType: "benchmark_update",
+ title: "Protein structure benchmark leaderboard recalibration",
+ published: "2026-01-22",
+ relation: "updates",
+ currentVersion: "2026.01",
+ requirement: "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.",
+ minCitationDate: "2026-01-22"
+ },
+ {
+ id: "ev-sr-2024-telemetry-implant",
+ topic: "telemetry implant arrhythmia",
+ evidenceType: "systematic_review",
+ title: "2024 telemetry implant arrhythmia meta-analysis",
+ published: "2024-09-20",
+ relation: "supports",
+ requirement: "Current support is acceptable if cited with the 2024 meta-analysis.",
+ minCitationDate: "2024-09-20"
+ }
+ ]
+};
+
+const manuscriptPackets = [
+ {
+ id: "ms-hold-senolytic-review",
+ title: "Senolytic cardiac safety manuscript",
+ domain: "translational cardiometabolic biology",
+ claims: [
+ {
+ id: "claim-tolerability",
+ text: "The senolytic cocktail is broadly well tolerated across cardiometabolic cohorts.",
+ topic: "senolytic cardiometabolic safety",
+ supportCitationIds: ["trial-2019-small", "review-2021-safety"],
+ citationYears: [2019, 2021],
+ citationDates: ["2019-08-15", "2021-06-01"],
+ citedCurrentSignalIds: [],
+ tone: "broad"
+ },
+ {
+ id: "claim-organoid-methods",
+ text: "The organoid assay follows current field reporting guidance.",
+ topic: "organoid assay reporting",
+ supportCitationIds: ["organoid-checklist-2022"],
+ citationYears: [2022],
+ citationDates: ["2022-02-10"],
+ citedCurrentSignalIds: [],
+ tone: "current"
+ },
+ {
+ id: "claim-atlas-normalization",
+ text: "The single-cell normalization pipeline reflects current atlas annotations.",
+ topic: "single-cell atlas normalization",
+ supportCitationIds: ["atlas-v2-release"],
+ citationYears: [2021],
+ citationDates: ["2021-12-12"],
+ citedCurrentSignalIds: [],
+ datasetVersion: "v2.1",
+ tone: "current"
+ },
+ {
+ id: "claim-protein-benchmark",
+ text: "The model remains state-of-the-art on the protein structure benchmark.",
+ topic: "protein structure benchmark",
+ supportCitationIds: ["leaderboard-2024-10"],
+ citationYears: [2024],
+ citationDates: ["2024-10-05"],
+ citedCurrentSignalIds: [],
+ benchmarkVersion: "2024.10",
+ tone: "state-of-the-art"
+ }
+ ]
+ },
+ {
+ id: "ms-ready-telemetry-review",
+ title: "Telemetry implant arrhythmia replication note",
+ domain: "clinical devices",
+ claims: [
+ {
+ id: "claim-telemetry-risk",
+ text: "Telemetry implant arrhythmia risk is consistent with the latest meta-analysis.",
+ topic: "telemetry implant arrhythmia",
+ supportCitationIds: ["telemetry-meta-2024"],
+ citationYears: [2024],
+ citationDates: ["2024-09-20"],
+ citedCurrentSignalIds: ["ev-sr-2024-telemetry-implant"],
+ tone: "calibrated"
+ }
+ ]
+ }
+];
+
+module.exports = {
+ freshnessPolicy,
+ evidenceLedger,
+ manuscriptPackets
+};
diff --git a/literature-freshness-review-assistant/scripts/render-demo-video.js b/literature-freshness-review-assistant/scripts/render-demo-video.js
new file mode 100644
index 00000000..c53297c4
--- /dev/null
+++ b/literature-freshness-review-assistant/scripts/render-demo-video.js
@@ -0,0 +1,89 @@
+const fs = require("fs");
+const os = require("os");
+const path = require("path");
+const { spawnSync } = require("child_process");
+
+const {
+ evaluateFreshnessReview
+} = require("../index");
+const {
+ freshnessPolicy,
+ evidenceLedger,
+ manuscriptPackets
+} = require("../sample-data");
+
+const moduleDir = path.join(__dirname, "..");
+const reportsDir = path.join(moduleDir, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+function candidateFonts() {
+ return [
+ "C:\\Windows\\Fonts\\arial.ttf",
+ "C:\\Windows\\Fonts\\segoeui.ttf",
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
+ "/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf",
+ "/System/Library/Fonts/Supplemental/Arial.ttf",
+ "/Library/Fonts/Arial.ttf"
+ ];
+}
+
+function resolveFontFile() {
+ const font = candidateFonts().find((candidate) => fs.existsSync(candidate));
+ if (!font) {
+ throw new Error("No supported TrueType font file found for ffmpeg drawtext rendering.");
+ }
+ return font;
+}
+
+function escapeDrawtext(value) {
+ return String(value)
+ .replaceAll("\\", "\\\\")
+ .replaceAll(":", "\\:")
+ .replaceAll("'", "\\'")
+ .replaceAll(",", "\\,");
+}
+
+function drawText(fontFile, text, x, y, size, color) {
+ return `drawtext=fontfile='${escapeDrawtext(fontFile)}':text='${escapeDrawtext(text)}':x=${x}:y=${y}:fontsize=${size}:fontcolor=${color}`;
+}
+
+const ffmpegPath = process.env.FFMPEG_PATH || "ffmpeg";
+const fontFile = resolveFontFile();
+const report = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy);
+const output = path.join(reportsDir, "demo.mp4");
+const filter = [
+ "color=c=0xf8fafc:s=1280x720:d=5",
+ drawText(fontFile, "Literature Freshness Review Assistant", 70, 70, 42, "0x0f172a"),
+ drawText(fontFile, `Status: ${report.manuscripts[0].summary.status}`, 70, 145, 32, "0xb91c1c"),
+ drawText(fontFile, `Reviewed manuscripts: ${report.aggregate.reviewedManuscripts}`, 70, 220, 28, "0x0f172a"),
+ drawText(fontFile, `Held: ${report.aggregate.heldManuscripts} Ready: ${report.aggregate.readyManuscripts}`, 70, 270, 28, "0x0f172a"),
+ drawText(fontFile, `Critical freshness findings: ${report.aggregate.criticalFindings}`, 70, 340, 30, "0xdc2626"),
+ drawText(fontFile, `Major freshness findings: ${report.aggregate.majorFindings}`, 70, 390, 30, "0xd97706"),
+ drawText(fontFile, "Checks stale support, missing newer evidence, dataset drift, and benchmark drift.", 70, 475, 25, "0x334155"),
+ drawText(fontFile, `Audit ${report.auditDigest.slice(0, 24)}`, 70, 635, 22, "0x475569")
+].join(",");
+
+const args = [
+ "-y",
+ "-f", "lavfi",
+ "-i", filter,
+ "-t", "5",
+ "-pix_fmt", "yuv420p",
+ "-c:v", "libx264",
+ "-movflags", "+faststart",
+ output
+];
+
+const result = spawnSync(ffmpegPath, args, {
+ encoding: "utf8",
+ stdio: "pipe",
+ windowsHide: true
+});
+
+if (result.status !== 0) {
+ process.stderr.write(result.stderr || result.stdout);
+ throw new Error(`ffmpeg failed with status ${result.status}`);
+}
+
+const stats = fs.statSync(output);
+console.log(`wrote ${path.relative(process.cwd(), output)} (${stats.size} bytes) using ${path.basename(ffmpegPath)} on ${os.platform()}`);
diff --git a/literature-freshness-review-assistant/test.js b/literature-freshness-review-assistant/test.js
new file mode 100644
index 00000000..05d879bc
--- /dev/null
+++ b/literature-freshness-review-assistant/test.js
@@ -0,0 +1,47 @@
+const assert = require("assert");
+
+const {
+ evaluateFreshnessReview,
+ renderMarkdown,
+ renderSvg
+} = require("./index");
+const {
+ freshnessPolicy,
+ evidenceLedger,
+ manuscriptPackets
+} = require("./sample-data");
+
+const report = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy);
+
+assert.strictEqual(report.aggregate.reviewedManuscripts, 2);
+assert.strictEqual(report.aggregate.heldManuscripts, 1);
+assert.strictEqual(report.aggregate.readyManuscripts, 1);
+assert.strictEqual(report.aggregate.criticalFindings, 1);
+assert.ok(report.aggregate.majorFindings >= 4);
+
+const held = report.manuscripts.find((entry) => entry.packetId === "ms-hold-senolytic-review");
+assert.ok(held);
+assert.strictEqual(held.summary.status, "hold_freshness_review");
+assert.ok(held.findings.some((finding) => finding.code === "NEWER_CONTRADICTORY_EVIDENCE"));
+assert.ok(held.findings.some((finding) => finding.code === "MISSING_NEWER_EVIDENCE"));
+assert.ok(held.findings.some((finding) => finding.code === "DATASET_VERSION_DRIFT"));
+assert.ok(held.findings.some((finding) => finding.code === "BENCHMARK_VERSION_DRIFT"));
+assert.ok(held.reviewerActions.length >= 4);
+
+const ready = report.manuscripts.find((entry) => entry.packetId === "ms-ready-telemetry-review");
+assert.ok(ready);
+assert.strictEqual(ready.summary.status, "ready_for_review");
+assert.deepStrictEqual(ready.findings.map((finding) => finding.code), ["FRESHNESS_READY"]);
+
+const secondPass = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy);
+assert.strictEqual(secondPass.auditDigest, report.auditDigest);
+
+const markdown = renderMarkdown(report);
+assert.ok(markdown.includes("Literature Freshness Review Assistant"));
+assert.ok(markdown.includes("NEWER_CONTRADICTORY_EVIDENCE"));
+
+const svg = renderSvg(report);
+assert.ok(svg.includes("