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>
79 lines
2.4 KiB
JavaScript
79 lines
2.4 KiB
JavaScript
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
|
|
/* The clock guard.
|
|
|
|
The artifact's sync bug was a fresh device stamping its own empty defaults
|
|
as newer than the server's real data. db/writes.ts owns the distinction
|
|
between a seed write (updated_at stays 0) and a user edit (stamped), so it
|
|
is the only file under src/db/ allowed to read the clock. Anywhere else,
|
|
reaching for Date.now() means someone is about to stamp a row without
|
|
deciding which kind of write it is. */
|
|
const CLOCK_BAN = [
|
|
{
|
|
selector: "CallExpression[callee.object.name='Date'][callee.property.name='now']",
|
|
message:
|
|
"Only db/writes.ts may read the clock. Use a seedX() helper (leaves updated_at 0) " +
|
|
"or an editX() helper (stamps it) — see the timestamp rule in db/writes.ts.",
|
|
},
|
|
{
|
|
selector: "NewExpression[callee.name='Date']",
|
|
message:
|
|
"Only db/writes.ts may read the clock. Use a seedX() or editX() helper from db/writes.ts.",
|
|
},
|
|
];
|
|
|
|
export default tseslint.config(
|
|
// lib/, data/, fixtures/, validate.mjs and audit-gate.mjs are copied in
|
|
// from the export bundle unchanged and must stay byte-identical — they are
|
|
// not ours to restyle.
|
|
{
|
|
ignores: [
|
|
"**/dist/**",
|
|
"**/node_modules/**",
|
|
"export/**",
|
|
"vendor/**",
|
|
"app/android/**",
|
|
"lib/**",
|
|
"data/**",
|
|
"fixtures/**",
|
|
"validate.mjs",
|
|
"audit-gate.mjs",
|
|
],
|
|
},
|
|
js.configs.recommended,
|
|
...tseslint.configs.recommended,
|
|
// Build scripts and the shared modules are plain Node ESM.
|
|
{
|
|
files: ["tools/**/*.mjs", "shared/**/*.mjs", "*.config.js"],
|
|
languageOptions: {
|
|
globals: {
|
|
console: "readonly",
|
|
process: "readonly",
|
|
URL: "readonly",
|
|
fetch: "readonly",
|
|
Buffer: "readonly",
|
|
},
|
|
},
|
|
},
|
|
/* The rules of hooks, and exhaustive deps in particular. A stale closure
|
|
in an effect is what let the tutor seed its opening turn three times. */
|
|
{
|
|
files: ["app/src/**/*.{ts,tsx}"],
|
|
plugins: { "react-hooks": reactHooks },
|
|
rules: {
|
|
"react-hooks/rules-of-hooks": "error",
|
|
"react-hooks/exhaustive-deps": "warn",
|
|
},
|
|
},
|
|
{
|
|
files: ["app/src/db/**/*.ts"],
|
|
rules: { "no-restricted-syntax": ["error", ...CLOCK_BAN] },
|
|
},
|
|
{
|
|
files: ["app/src/db/writes.ts"],
|
|
rules: { "no-restricted-syntax": "off" },
|
|
},
|
|
);
|