Files
Hankan/types/lib/gate.d.ts
MechaCat02 e72b77d6c2 chore: take in the 16 Sep bundle — lib, curriculum v5, prompt, gate audit
The artifact was reworked after real incidents: a week of lost data, a
student taught out of order, and spelling diagnoses the model invented.
This takes the new export in verbatim; the port catches up in the
commits that follow.

Copied byte-identical from the bundle:
  lib/        lexicon.js and sync.js are new; gate.js gains enforcement,
              hangul.js letter-level marking, srs.js recall evidence,
              conjugation.js deconjugate(); blocks.js now takes the last
              block, closes gloss at "=", and parses recall, ::result and
              ::confirmed
  data/       curriculum.json v5 — six 다지기 phase reviews; the 371
              roadmap words are unchanged and no band moves
  prompt/     English-only rule, recall, LETTER-LEVEL CHECK, marking
  audit-gate.mjs, run-checks.sh, fixtures/  — the word gate measured
              against 54 real tutor messages

CI runs run-checks.sh in place of validate.mjs alone, and `npm run check`
gains the audit. Baselines: validate PASS 0/0; audit 7 of 41 and 2 of 13.

types/lib/ declares the new API, and test/lib/ pins it: letterCheck on
the prompt's own 짧다/빫다 case, deconjugation, the roadmap-first order
that keeps 마셔 out of Phase 1, sync's three gates, and recall evidence —
including the two ways lib's evidence is looser than PORT.md, pinned as
they are so the call site that tightens them is visibly needed.

TaskHost gains a plain recall renderer so the tree typechecks against the
wider Task union.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 19:49:52 +02:00

129 lines
3.9 KiB
TypeScript

/* Declarations for lib/gate.js — the module itself ships unchanged.
The gate is what stops material being taught out of order. buildGate()
computes it from curriculum + progress; renderGate() turns it into the
{{GATE}} section of prompt/tutor-system.md. The second half —
scanTask(), proseIsKorean(), rejectionNote() — checks whether the tutor
actually listened, and its scope was measured against real messages
(audit-gate.mjs), not reasoned out. */
import type { ParsedMessage, Task } from "./blocks.js";
export interface Unit {
id: string;
ko: string;
name: string;
goal: string;
vocabUnit: boolean;
/** A 다지기 phase review: introduces nothing, confirms the whole phase. */
review?: boolean;
teaches: string[];
avoid?: string[];
/** Strictly NEW vocabulary. Deliberate repeats live in revisits[]. */
words: string[];
revisits?: { word: string; from: string }[];
}
/** A Unit with its phase stamped on, as flatten() returns it. */
export interface FlatUnit extends Unit {
phase: number;
phaseKo: string;
phaseName: string;
}
export interface Phase {
phase: number;
ko: string;
name: string;
units: Unit[];
}
export interface Curriculum {
version: number;
note?: string;
phases: Phase[];
}
export interface ProgressState {
current: string;
done: Record<string, boolean>;
confidence?: Record<string, number>;
}
/**
* Replaces the hand-listed word set with a dictionary query. Returning a
* frequency band here is what turns 371 hand-typed words into something
* that scales.
*/
export type VocabQuery = (unit: FlatUnit, done: FlatUnit[]) => string[];
export interface GateOptions {
vocabQuery?: VocabQuery;
}
export interface Gate {
unit: FlatUnit;
phase: { n: number; ko: string; name: string };
taught: string[];
forbidden: { near: string[]; tailUnit: FlatUnit | null; count: number };
vocabulary: string[];
newWords: string[];
/** Words met earlier that this unit should deliberately bring back. */
revisits: string[];
confidence: number | null;
next: FlatUnit | null;
finished: string[];
}
export function flatten(curriculum: Curriculum): FlatUnit[];
export function buildGate(
curriculum: Curriculum,
progress: ProgressState,
opts?: GateOptions,
): Gate;
/** Render the gate into the system prompt section. Keep the headings. */
export function renderGate(g: Gate): string;
/* ── enforcing the gate ─────────────────────────────────────────────── */
/** Unit and phase names plus the course's metalanguage (받침, 비음화, …):
Hangul a teacher may write without it being taught vocabulary. */
export function buildScaffold(curriculum: Curriculum): Set<string>;
/** Only the side of an exercise the student must decode. Recall prompts
are English, so a recall task contributes nothing. */
export function taskMaterial(task: Task | null | undefined): string[];
export interface GateFinding {
word: string;
/** The unit that introduces it, or "" when no unit does. */
unit: string;
/** false: no gloss exists anywhere — a typo or an invented word. */
known: boolean;
}
export interface ScanContext {
allowed: Set<string>;
scaffold: Set<string>;
/** Every dictionary word this surface could be, best first. */
heads: (token: string) => string[];
/** The unit that introduces a word, or "". */
unitOf: (word: string) => string;
}
export function scanTask(
parsed: Pick<ParsedMessage, "task" | "words"> | null | undefined,
ctx: ScanContext,
): GateFinding[];
/** ≥40 Hangul syllables in the prose outside blocks, and ≥50% of letters. */
export function proseIsKorean(text: string): boolean;
/** What to tell the tutor when a message is sent back. */
export function rejectionNote(findings: GateFinding[]): string;
/** Retries before a message is shown anyway, with its words flagged. */
export const GATE_TRIES: number;