Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 5036cb4985d34c0381074fb66f000393b7f2d466
parent 192a13e1aff217f861194186ad7f3e3795553bef
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri,  9 Oct 2026 17:53:36 -0400

mcp: get_report reads a report's timeline, newest first; `section` may name an entry

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mmcp/src/reports.test.ts | 32+++++++++++++++++++++++++++++++-
Mmcp/src/reports.ts | 28++++++++++++++++++++++++++--
Mmcp/src/server.ts | 11+++++++----
3 files changed, 64 insertions(+), 7 deletions(-)

diff --git a/mcp/src/reports.test.ts b/mcp/src/reports.test.ts @@ -14,7 +14,7 @@ import { import { CONTRACT } from "yt-dlp-transcript-common/lib/archive/contract"; import { LocalSource, type ShardSource } from "./source"; import { createServer } from "./server"; -import { renderReportIndex, reportsLine } from "./reports"; +import { renderReportIndex, renderReportPage, reportsLine } from "./reports"; // list_reports / get_report and the "cited-only site" line, over a composed // public dir on disk read by the real LocalSource. @@ -222,3 +222,33 @@ test("a source with no reports of its own (a hub) points at its members", () => assert.match(out, /reports are per site/); assert.equal(reportsLine({ supported: false, scope: "full", reports: [] }), null); }); + +test("get_report's text: the timeline newest first, dated, before the sections; `section` may name an entry", () => { + const view = buildReportPageView( + { + ...REPORT, + entries: [ + { id: "e-old", date: "2026-10-02", title: "The first update", body: "It began [here](cite:v1)." }, + { id: "e-new", date: "2026-10-05T09:30:00Z", updated: "2026-10-06", title: "The second update", body: "Then more." }, + ], + }, + { record: (c) => ({ channel: c.channel, id: c.id, title: `Record ${c.id}` }) }, + ); + const text = renderReportPage(view, ORIGIN); + const at = (s: string) => text.indexOf(s); + assert.ok(at("## Timeline (newest first)") > 0); + assert.ok(at("### 2026-10-05T09:30:00Z (updated 2026-10-06) — The second update (#e-new)") > at("## Timeline")); + assert.ok(at("### 2026-10-02 — The first update (#e-old)") > at("(#e-new)")); + assert.ok(at("## Section one (#one)") > at("(#e-old)")); + // An entry's citations, as a section body's. + assert.match(text, /\(#e-old\)\nIt began \[here\]\(cite:v1\)\.\n\[1\] video/); + // The header's update is the newest entry's. + assert.match(text, /updated 2026-10-06/); + assert.match(text, /\(sections: one, two; timeline entries: e-new, e-old — pass section:"<id>" for one\)$/); + + const one = renderReportPage(view, ORIGIN, "e-old"); + assert.match(one, /### 2026-10-02 — The first update \(#e-old\)/); + assert.doesNotMatch(one, /e-new|Section one/); + const section = renderReportPage(view, ORIGIN, "one"); + assert.doesNotMatch(section, /Timeline/); +}); diff --git a/mcp/src/reports.ts b/mcp/src/reports.ts @@ -21,6 +21,7 @@ import { verdictTally, type CitationView, type ClaimView, + type EntryView, type ReportIndexEntry, type ReportPageView, type SectionView, @@ -214,6 +215,20 @@ function renderSection(view: ReportPageView, s: SectionView, origin: string | nu return out.join("\n"); } +// A timeline entry: its date, title and anchor, its body, and the citations +// it cites. +function renderEntry(view: ReportPageView, e: EntryView, origin: string | null): string { + const out = [`### ${e.date}${e.updated ? ` (updated ${e.updated})` : ""} — ${e.title} (#${e.id})`]; + out.push(e.body.trim()); + for (const id of [...new Set(extractCiteRefs(e.body).map((r) => r.id))]) { + const c = view.citations[id]; + if (c) out.push(...citationLines(c, origin)); + } + return out.join("\n"); +} + +// One report as text. `sectionId` narrows it to one section — or one +// timeline entry, by its id. export function renderReportPage( view: ReportPageView, origin: string | null, @@ -241,10 +256,19 @@ export function renderReportPage( } if (view.summary && !sectionId) head.push("", view.summary.trim()); + // The timeline, newest first, before the sections (as the page has it). + const allEntries = view.entries ?? []; + const entries = sectionId ? allEntries.filter((e) => e.id === sectionId) : allEntries; + const body: string[] = []; + if (entries.length > 0) { + body.push([`## Timeline (newest first)`, ...entries.map((e) => renderEntry(view, e, origin))].join("\n\n")); + } const sections = sectionId ? view.sections.filter((s) => s.id === sectionId) : view.sections; - const body = sections.map((s) => renderSection(view, s, origin)); + body.push(...sections.map((s) => renderSection(view, s, origin))); const outline = sectionId ? "" - : `\n\n(sections: ${view.sections.map((s) => s.id).join(", ")} — pass section:"<id>" for one)`; + : `\n\n(sections: ${view.sections.map((s) => s.id).join(", ")}` + + (allEntries.length > 0 ? `; timeline entries: ${allEntries.map((e) => e.id).join(", ")}` : "") + + ` — pass section:"<id>" for one)`; return `${head.join("\n")}\n\n${body.join("\n\n")}${outline}`; } diff --git a/mcp/src/server.ts b/mcp/src/server.ts @@ -387,7 +387,8 @@ export const TOOLS: Tool[] = [ { name: "get_report", description: - "Read one published report: title, kind, verdict tally, then each " + + "Read one published report: title, kind, verdict tally, its timeline " + + "(dated entries, newest first) when it keeps one, then each " + "section's claims (verdict, findings) with every citation's verbatim " + "quote, original URL (the platform at the cited second, the post, the " + "document) and the site's moment page. Ids from list_reports.", @@ -398,7 +399,7 @@ export const TOOLS: Tool[] = [ report: { type: "string", description: "The report id." }, section: { type: "string", - description: "Optional: one section's id, to read just that section.", + description: "Optional: one section's (or timeline entry's) id, to read just that part.", }, }, required: ["report"], @@ -1495,9 +1496,11 @@ async function handleGetReport( ); } const section = typeof args.section === "string" && args.section.trim() ? args.section.trim() : undefined; - if (section && !view.sections.some((s) => s.id === section)) { + if (section && !view.sections.some((s) => s.id === section) && !(view.entries ?? []).some((e) => e.id === section)) { + const entries = (view.entries ?? []).map((e) => e.id); return errorText( - `report ${id} has no section ${section} — its sections: ${view.sections.map((s) => s.id).join(", ")}`, + `report ${id} has no section ${section} — its sections: ${view.sections.map((s) => s.id).join(", ")}` + + (entries.length > 0 ? `; its timeline entries: ${entries.join(", ")}` : ""), ); } return text(renderReportPage(view, await reportOrigin(source), section));