import { test } from "node:test"; import assert from "node:assert/strict"; import { DIGEST_MAX_CUES_PER_CHUNK, PROMPT_VERSION, buildChapterPrompt, buildTagPrompt, maxCuesForContext, promptOffsetSeconds, } from "./digestPrompt"; import { DEFAULT_DIGEST_MAX_CUES_PER_CHUNK, DEFAULT_DIGEST_NUM_CTX, DEFAULT_DIGEST_TIMESTAMP_MODE, } from "./digest"; // The chunk size MUST track the configured context. Lowering num_ctx without // lowering it feeds ollama more transcript than its window holds, and ollama // truncates SILENTLY — the model then summarizes a fragment and the result reads // as a bad model rather than as a misconfiguration. test("the two copies of the default chunk size agree", () => { // digestPrompt.ts re-exports digest.ts's constant; this pins them equal so the // identity helper (which compares against digest.ts's copy) and the chunker // (which uses this one) can never drift apart. assert.equal(DIGEST_MAX_CUES_PER_CHUNK, DEFAULT_DIGEST_MAX_CUES_PER_CHUNK); }); test("maxCuesForContext scales the slice with the window", () => { // Ratio-pinned to the 8192 -> 600 default pair. assert.equal(maxCuesForContext(8192), 600); assert.equal(maxCuesForContext(16384), 1200); assert.equal(maxCuesForContext(32768), 2400); assert.equal(maxCuesForContext(4096), 300); // Unset or nonsense falls back to the default window, never to zero cues. assert.equal(maxCuesForContext(undefined), DEFAULT_DIGEST_MAX_CUES_PER_CHUNK); assert.equal(maxCuesForContext(0), DEFAULT_DIGEST_MAX_CUES_PER_CHUNK); // Floored, so a tiny window still yields a usable slice rather than one cue. assert.equal(maxCuesForContext(256), 100); }); test("the shipped defaults are the configuration the bake-off measured best", () => { // Round 2 (plans/bakeoff/round2.md) scored qwen2.5:7b@8192/chunk-local best on // zero-yield rate, chapters/hour and worst coverage gap. The decision was made // and then not shipped for a while — the defaults stayed at the losing // 16384/absolute pair — so this pins the two constants that carry it. assert.equal(DEFAULT_DIGEST_NUM_CTX, 8192); assert.equal(DEFAULT_DIGEST_MAX_CUES_PER_CHUNK, 600); assert.equal(DEFAULT_DIGEST_TIMESTAMP_MODE, "chunk-local"); }); test("PROMPT_VERSION was bumped with the defaults change", () => { // The pairing is the whole point. digestPromptVariant derives its string // RELATIVE to the defaults, so a record written under the old defaults carries // promptVariant: undefined and would compare EQUAL under the new ones — // freezing bake-off-losing output into the corpus as permanently "fresh". // isSectionFresh compares promptVersion, so the bump is what invalidates them. assert.ok( PROMPT_VERSION >= 2, "changing DEFAULT_DIGEST_* without bumping PROMPT_VERSION silently skips every existing digest as fresh", ); }); test("chunk-local re-bases the range the chapter prompt states", () => { const base = { title: "T", channel: "C", startSeconds: 5503, // 01:31:43 — the measured third chunk endSeconds: 8169, // 02:16:09 transcript: "[00:00:00] hello", }; const absolute = buildChapterPrompt(base); assert.match(absolute, /This section covers 01:31:43 to 02:16:09/); assert.match(absolute, /between 01:31:43 and 02:16:09 inclusive/); const local = buildChapterPrompt({ ...base, timestampMode: "chunk-local" }); assert.match(local, /This section covers 00:00:00 to 00:44:26/); assert.match(local, /between 00:00:00 and 00:44:26 inclusive/); // The SPAN is unchanged, so the density target and the schema's minItems floor // are identical in both modes — only the numbering moves. assert.match(absolute, /44 minutes of material/); assert.match(local, /44 minutes of material/); }); test("the tag prompt is re-based the same way", () => { // It sees the same rendered transcript, so a range stated in the other // numbering would contradict the markers in front of it. const base = { title: "T", channel: "C", startSeconds: 5503, endSeconds: 8169, transcript: "[00:00:00] hello", }; assert.match(buildTagPrompt(base), /covering 01:31:43 to 02:16:09/); assert.match( buildTagPrompt({ ...base, timestampMode: "chunk-local" }), /covering 00:00:00 to 00:44:26/, ); }); test("absolute mode renders byte-identically to the pre-timestampMode prompt", () => { // The compatibility guarantee that let PROMPT_VERSION stay at 1: adding the // mode must not change what the default configuration sends, or every digest // on disk would have been invalidated. const base = { title: "T", channel: "C", startSeconds: 100, endSeconds: 700, transcript: "[00:01:40] hello", }; assert.equal( buildChapterPrompt(base), buildChapterPrompt({ ...base, timestampMode: "absolute" }), ); }); test("a prompt with no speakerRoster renders byte-identically to today", () => { // THE SAME COMPATIBILITY ARGUMENT AS absolute-mode ABOVE, and the reason // adding the roster field needed no PROMPT_VERSION bump: the shipped lane // never sets it, so every digest the sweep produces is byte-for-byte what it // produced before the field existed. It is also what makes the bake-off's // speakers-OFF arm a real control rather than a third variant. const base = { title: "T", channel: "C", startSeconds: 100, endSeconds: 700, transcript: "[00:01:40] hello", }; const shipped = buildChapterPrompt(base); assert.equal(buildChapterPrompt({ ...base, speakerRoster: undefined }), shipped); // Whitespace-only is treated as absent, so a caller that builds an empty // roster string cannot accidentally change the prompt. assert.equal(buildChapterPrompt({ ...base, speakerRoster: " \n" }), shipped); assert.equal(buildChapterPrompt({ ...base, speakerRoster: "" }), shipped); }); test("a speakerRoster is stated separately from the channel context note", () => { const base = { title: "T", channel: "C", startSeconds: 100, endSeconds: 700, transcript: "[00:01:40] hello", contextNote: "The host is Alice.", speakerRoster: "Speakers in this section:\n- Alice\n- Bob", }; const prompt = buildChapterPrompt(base); // Both present, and the roster is NOT filed under "context for this channel" // — it is per-chunk and derived, not hand-authored per channel. assert.match(prompt, /Context for this channel[^\n]*\nThe host is Alice\./); assert.match(prompt, /Speakers in this section:\n- Alice\n- Bob/); // The guard against titling chapters after whoever is speaking. assert.match(prompt, /A title still names the SUBJECT under discussion/); // The transcript still comes last, so the body is what the model ends on. assert.ok(prompt.indexOf("Speakers in this section") < prompt.indexOf("Transcript section:")); }); test("promptOffsetSeconds is zero in absolute mode by construction", () => { assert.equal(promptOffsetSeconds({ startSeconds: 5503 }), 0); assert.equal( promptOffsetSeconds({ startSeconds: 5503, timestampMode: "absolute" }), 0, ); assert.equal( promptOffsetSeconds({ startSeconds: 5503, timestampMode: "chunk-local" }), 5503, ); });