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>
81 lines
2.0 KiB
TypeScript
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;
|