Files
Hankan/types/lib/gate.d.ts
MechaCat02 966043ab3f feat(lib): type declarations and golden tests for the exported modules
lib/ ships unchanged, so its types live in types/lib/ and are wired up by a
tsconfig path mapping. Adding types costs nothing; reimplementing the logic
would cost the two things that make this port possible.

The tests exist so a later refactor cannot silently drift them:

  hangul       the nine Composer cases named in the export README —
               먹어 · 왔어 · 읽어 · 괜찮아 · 값 · 의사 · 뭐야 and backspace
  conjugation  all seven irregular classes, and every form in
               IRREGULAR_FORMS reachable through haeche()
  srs          the SM-2 transitions, ease and interval clamps
  blocks       parse → answerText round-trip for all four task types

101 tests.

One is a pinned defect rather than a guarantee. blocks.parse() never closes
a ::gloss block on its "=" line, so a multi-sentence gloss — which the tutor
prompt explicitly invites — collapses into one run-on line keeping only the
last translation. lib/ ships unchanged, so the test records the real
behaviour and the app works around it at the call site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 19:12:19 +02:00

81 lines
2.0 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. */
export interface Unit {
id: string;
ko: string;
name: string;
goal: string;
vocabUnit: 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;