Files
Hankan/types/lib/lexicon.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

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;