← Files Vocabloot Deck KitARCHIVED FILE

scripts/lib/prompts.mjs

4.76 KB · Oct 2, 2026 · 00:34 UTC

↓ Download file

// Loads a prompt file, reads its promptVersion, fills {placeholders}.
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const HERE = path.dirname(fileURLToPath(import.meta.url));
export const PROMPTS_DIR = path.join(HERE, "..", "..", "prompts");

export function loadPrompt(name, { file = null } = {}) {
  const text = readFileSync(file ?? path.join(PROMPTS_DIR, `${name}.md`), "utf8");
  const version = /promptVersion:\s*([\w.-]+)/.exec(text)?.[1] ?? "unversioned";
  const body = text.replace(/<!--[\s\S]*?-->\s*/g, "").trim();
  return { version, body };
}

export function fill(body, values) {
  return body.replace(/\{(\w+)\}/g, (m, k) => (k in values ? String(values[k]) : m));
}

/** The grammar lines a language needs, from registry/languages.json facts. */
export function grammarRules(facts, learning) {
  const f = facts[learning] ?? {};
  const lines = [];
  if (f.hasArticles && f.hasGender)
    lines.push(
      `- For a noun: article (the definite article), gender (masculine, feminine or neuter) and plural are required; for anything else they are null.`,
    );
  else if (f.hasGender)
    lines.push(`- For a noun: gender (masculine, feminine or neuter) and plural are required; article is null.`);
  else
    lines.push(
      `- article and gender are null for this language; plural only where the word has a distinct plural form.`,
    );
  if (f.needsRomanization) lines.push(`- romanization is required: the standard Latin transcription of the headword.`);
  else lines.push(`- romanization is null.`);
  return lines.join("\n");
}

/** The strict JSON schema of one generated card (deck kit spec section 6). */
export const CARD_SCHEMA = {
  type: "object",
  additionalProperties: false,
  required: [
    "text",
    "article",
    "gender",
    "plural",
    "partOfSpeech",
    "phonetic",
    "romanization",
    "translation",
    "stickerMode",
    "stickerConcept",
    "examples",
    "confidence",
    "childSafe",
    "notes",
  ],
  properties: {
    text: { type: "string" },
    article: { type: ["string", "null"] },
    gender: { type: ["string", "null"], enum: ["masculine", "feminine", "neuter", null] },
    plural: { type: ["string", "null"] },
    partOfSpeech: {
      type: "string",
      enum: [
        "noun",
        "verb",
        "adjective",
        "adverb",
        "pronoun",
        "preposition",
        "conjunction",
        "numeral",
        "interjection",
        "phrase",
        "particle",
        "article",
        "determiner",
        "other",
      ],
    },
    phonetic: { type: "string" },
    romanization: { type: ["string", "null"] },
    translation: { type: "string" },
    stickerMode: { type: "string", enum: ["sticker", "symbolic", "contextual", "text-first"] },
    stickerConcept: { type: "string" },
    examples: {
      type: "array",
      minItems: 2,
      maxItems: 2,
      items: {
        type: "object",
        additionalProperties: false,
        required: ["source", "target", "tokens"],
        properties: {
          source: { type: "string" },
          target: { type: "string" },
          tokens: {
            type: "array",
            items: {
              type: "object",
              additionalProperties: false,
              required: ["text", "lemma", "partOfSpeech", "grammarCodes", "meanings", "article", "gender", "plural"],
              properties: {
                text: { type: "string" },
                lemma: { type: "string" },
                partOfSpeech: { type: "string" },
                grammarCodes: { type: "array", items: { type: "string" } },
                meanings: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 3 },
                article: { type: ["string", "null"] },
                gender: { type: ["string", "null"] },
                plural: { type: ["string", "null"] },
              },
            },
          },
        },
      },
    },
    confidence: { type: "string", enum: ["high", "medium", "low"] },
    childSafe: { type: "boolean" },
    notes: { type: "string" },
  },
};

export const WORDS_SCHEMA = {
  type: "object",
  additionalProperties: false,
  required: ["words"],
  properties: {
    words: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["word", "article", "hint"],
        properties: { word: { type: "string" }, article: { type: ["string", "null"] }, hint: { type: "string" } },
      },
    },
  },
};

export const VERIFY_SCHEMA = {
  type: "object",
  additionalProperties: false,
  required: ["natural", "levelOk", "correct", "safe", "issue"],
  properties: {
    natural: { type: "boolean" },
    levelOk: { type: "boolean" },
    correct: { type: "boolean" },
    safe: { type: "boolean" },
    issue: { type: "string" },
  },
};

SHA-256: b6c0fcd8b47effcc5191aa6c55ab00255760ffdf47a0b2c5d2e34e502fb06ca7