mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
39 lines
2.7 KiB
TypeScript
39 lines
2.7 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { readFileSync, readdirSync } from "node:fs";
|
|
import { parseDocument } from "yaml";
|
|
import type { ChatScenario } from "./chat-feature-cases.js";
|
|
|
|
export interface CatalogChatScenario extends ChatScenario { feature: string; sourceFile: string }
|
|
const directory = new URL("./chat-scenarios/", import.meta.url);
|
|
export const chatCatalogScenarios: readonly CatalogChatScenario[] = readdirSync(directory)
|
|
.filter(name => name.endsWith(".yaml") && name !== "defaults.yaml").sort().flatMap(name => {
|
|
const document = parseDocument(readFileSync(new URL(name, directory), "utf8"), { uniqueKeys: true });
|
|
if (document.errors.length) throw new Error(`Invalid chat catalog file ${name}`);
|
|
const catalog = document.toJS();
|
|
const feature = name.slice(0, -5);
|
|
if (catalog.schema_version !== 1 || catalog.feature !== feature || catalog.status !== "planned" || !Array.isArray(catalog.scenarios))
|
|
throw new Error(`Unsupported chat catalog file ${name}`);
|
|
return catalog.scenarios.map((scenario: ChatScenario) => {
|
|
if (!scenario.id.startsWith(`${feature}.`) || [scenario.context, scenario.query, scenario.expected_outcome].some(value => typeof value !== "string" || !value.trim())
|
|
|| !Array.isArray(scenario.capabilities) || scenario.capabilities.length === 0)
|
|
throw new Error(`Invalid scenario in ${name}`);
|
|
return { ...scenario, feature, sourceFile: `tests/runner-e2e/chat-scenarios/${name}` };
|
|
});
|
|
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
if (chatCatalogScenarios.length !== 244 || new Set(chatCatalogScenarios.map(row => row.id)).size !== 244)
|
|
throw new Error("Frozen chat catalog must contain exactly 244 unique scenarios");
|
|
/** Canonical semantic digest; whitespace changes alone do not alter scenario meaning. */
|
|
export const chatCatalogSemanticDigest = createHash("sha256").update(JSON.stringify(chatCatalogScenarios.map(
|
|
({ id, capabilities, context, query, expected_outcome }) => ({ id, capabilities, context, query, expected_outcome }),
|
|
))).digest("hex");
|
|
|
|
/** Campaign freeze also binds original file bytes and shared context defaults. */
|
|
export const chatCatalogInventoryDigest = createHash("sha256").update(JSON.stringify({
|
|
defaultsSha256: createHash("sha256").update(readFileSync(new URL("defaults.yaml", directory))).digest("hex"),
|
|
scenarios: chatCatalogScenarios.map(({ id, capabilities, context, query, expected_outcome, sourceFile }) => ({
|
|
id,
|
|
scenarioSha256: createHash("sha256").update(JSON.stringify({ id, capabilities, context, query, expected_outcome })).digest("hex"),
|
|
sourceSha256: createHash("sha256").update(readFileSync(new URL(`../../${sourceFile}`, import.meta.url))).digest("hex"),
|
|
})),
|
|
})).digest("hex");
|