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>
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
/* Declarations for lib/lexicon.js — the module itself ships unchanged.
|
|
|
|
One resolver shared by word lookup and the gate, so the two cannot drift.
|
|
heads() returns EVERY route from a surface form to a dictionary word; a
|
|
single "best" answer was the bug. */
|
|
|
|
export const PARTICLES: string[];
|
|
|
|
export interface LexEntry {
|
|
ko: string;
|
|
gloss: string;
|
|
note: string;
|
|
/** roadmap | deck | form | gloss | sentence | sfx */
|
|
src: string;
|
|
/** The dictionary word this entry is a form of, or "". */
|
|
base: string;
|
|
}
|
|
|
|
export class Lexicon {
|
|
map: Map<string, LexEntry>;
|
|
verbs: Set<string>;
|
|
/** First writer wins: an existing entry is never replaced. */
|
|
add(ko: string, gloss: string, note?: string, src?: string, base?: string): void;
|
|
addVerb(dictionaryForm: string): void;
|
|
get(ko: string): LexEntry | null;
|
|
isVerb(ko: string): boolean;
|
|
/** Every dictionary word this surface form could be, best first. */
|
|
heads(token: string): string[];
|
|
/** What to show when the student taps a word. */
|
|
lookup(token: string): LexEntry | null;
|
|
}
|
|
|
|
/** A deck row: [한글, romanization, English, POS], or the same as an object. */
|
|
export type DeckWord =
|
|
| { ko: string; en: string; pos: string }
|
|
| readonly [string, string, string, string];
|
|
|
|
export interface LexiconSources {
|
|
/** Entered FIRST, with no base — see the ordering warning in lexicon.js. */
|
|
roadmapWords?: string[];
|
|
deck?: DeckWord[];
|
|
/** [한글, English, note?] */
|
|
glossExtra?: (readonly [string, string, string?])[];
|
|
sentences?: { parts?: (readonly [string, string, ...unknown[]])[] }[];
|
|
/** [한글, English] */
|
|
sfx?: (readonly [string, string])[];
|
|
}
|
|
|
|
export function buildLexicon(
|
|
sources: LexiconSources,
|
|
conjugation: {
|
|
haeche: (dict: string) => string | null;
|
|
past: (present: string | null) => string | null;
|
|
},
|
|
): Lexicon;
|