Generate via cassette
# grade this stage (from your course directory)
npx theinference test --stage cassette-generate --watchNow generate — without ever calling a live LLM. The prompt's sha256 hash keys into cassette.json, which returns the recorded answer for that exact prompt (and throws on anything else). This is the determinism boundary: same prompt → same answer, forever, offline.
What to build
Implement generate + parseCitations
Implement generate(prompt, cassette): hash the prompt with the provided promptHash, look it up, return the recorded answer (throw on a miss). Implement parseCitations(answer, usedChunkIds): parse [doc i] markers and map each to its chunk id via usedChunkIds[i-1]. The generate <q> <k> <budget> command assembles, prompts, generates, and prints {answer, citations}.
export function generate(prompt: string, cassette: Cassette): string {
const entry = cassette[promptHash(prompt)];
if (!entry) throw new Error("cassette miss");
return entry.answer;
}
export function parseCitations(answer: string, usedChunkIds: string[]): string[] {
// match /\[doc (\d+)\]/g; idx = n-1; if in range -> usedChunkIds[idx]
}
./your_program.sh generate 0 5 200
# -> { answer: "1887 [doc 1].", citations: ["eiffel-tower#p1::0-31"] }
process mode, stdout_json_match. The answer text and the parsed citation ids must match the reference exactly. A cassette miss throws (non-zero exit) — which fails the step — proving you assembled the exact prompt the cassette was recorded against.