@bendyline/gilde 0.1.91 → 0.1.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +6 -4
  2. package/data/craftbook-templates/fr/freeze-scope/versions/1.4.0/craftbook.json +94 -0
  3. package/data/craftbook-templates/fr/freeze-scope/versions/1.4.0/test.json +131 -0
  4. package/data/craftbook-templates/index.json +1 -1
  5. package/data/craftbook-templates/na/narrated-slideshow/versions/1.1.7/craftbook.json +315 -0
  6. package/data/craftbook-templates/na/narrated-slideshow/versions/1.1.7/test.json +89 -0
  7. package/data/craftbook-templates/ph/photo-library-nightly/manifest.json +22 -0
  8. package/data/craftbook-templates/ph/photo-library-nightly/versions/1.0.0/craftbook.json +186 -0
  9. package/data/craftbook-templates/ph/photo-library-nightly/versions/1.0.0/test.json +69 -0
  10. package/data/craftbook-templates/po/powerpoint-deck/versions/1.7.17/craftbook.json +486 -0
  11. package/data/craftbook-templates/po/powerpoint-deck/versions/1.7.17/test.json +265 -0
  12. package/data/craftbook-templates/raw-index.json +1 -1
  13. package/data/craftbook-templates/re/report-pdf/versions/1.1.7/craftbook.json +316 -0
  14. package/data/craftbook-templates/re/report-pdf/versions/1.1.7/test.json +101 -0
  15. package/data/craftbook-templates/re/research-to-document/versions/1.2.7/craftbook.json +387 -0
  16. package/data/craftbook-templates/re/research-to-document/versions/1.2.7/test.json +242 -0
  17. package/data/gezel-templates/cu/curator/manifest.json +20 -0
  18. package/data/gezel-templates/cu/curator/versions/1.0.0/about.md +22 -0
  19. package/data/gezel-templates/cu/curator/versions/1.0.0/manifest.json +23 -0
  20. package/data/gezel-templates/index.json +1 -1
  21. package/data/gezel-templates/pu/puzzelaar/manifest.json +19 -0
  22. package/data/gezel-templates/pu/puzzelaar/versions/1.0.0/about.md +18 -0
  23. package/data/gezel-templates/pu/puzzelaar/versions/1.0.0/manifest.json +16 -0
  24. package/data/gezel-templates/raw-index.json +1 -1
  25. package/data/gezel-templates/wo/woordsmid/manifest.json +19 -0
  26. package/data/gezel-templates/wo/woordsmid/versions/1.0.0/about.md +18 -0
  27. package/data/gezel-templates/wo/woordsmid/versions/1.0.0/manifest.json +16 -0
  28. package/data/knowledge-catalogs/be/bendyline-how-to-books/manifest.json +26 -0
  29. package/data/knowledge-catalogs/be/bendyline-how-to-books/versions/2026.10.8/manifest.json +8950 -0
  30. package/data/knowledge-catalogs/ca/caselaw-washington-part-0001/manifest.json +24 -0
  31. package/data/knowledge-catalogs/ca/caselaw-washington-part-0001/versions/2026.10.1/manifest.json +318 -0
  32. package/data/knowledge-catalogs/ca/caselaw-washington-part-0002/manifest.json +24 -0
  33. package/data/knowledge-catalogs/ca/caselaw-washington-part-0002/versions/2026.10.1/manifest.json +266 -0
  34. package/data/knowledge-catalogs/ca/caselaw-washington-part-0003/manifest.json +24 -0
  35. package/data/knowledge-catalogs/ca/caselaw-washington-part-0003/versions/2026.10.1/manifest.json +186 -0
  36. package/data/knowledge-catalogs/ca/caselaw-washington-part-0004/manifest.json +24 -0
  37. package/data/knowledge-catalogs/ca/caselaw-washington-part-0004/versions/2026.10.1/manifest.json +138 -0
  38. package/data/knowledge-catalogs/ca/caselaw-washington-part-0005/manifest.json +24 -0
  39. package/data/knowledge-catalogs/ca/caselaw-washington-part-0005/versions/2026.10.1/manifest.json +138 -0
  40. package/data/knowledge-catalogs/index.json +1 -1
  41. package/data/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/manifest.json +22 -0
  42. package/data/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/versions/2026.10.16/manifest.json +18270 -0
  43. package/data/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/manifest.json +23 -0
  44. package/data/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/versions/2026.10.16/manifest.json +1098 -0
  45. package/data/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/manifest.json +23 -0
  46. package/data/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/versions/2026.10.16/manifest.json +722 -0
  47. package/data/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/manifest.json +23 -0
  48. package/data/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/versions/2026.10.16/manifest.json +530 -0
  49. package/data/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/manifest.json +23 -0
  50. package/data/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/versions/2026.10.16/manifest.json +602 -0
  51. package/data/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/manifest.json +23 -0
  52. package/data/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/versions/2026.10.16/manifest.json +250 -0
  53. package/data/knowledge-catalogs/mi/microsoftdocs-dotnet-en/manifest.json +22 -0
  54. package/data/knowledge-catalogs/mi/microsoftdocs-dotnet-en/versions/2026.10.16/manifest.json +16794 -0
  55. package/data/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/manifest.json +22 -0
  56. package/data/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/versions/2026.10.16/manifest.json +1106 -0
  57. package/data/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/manifest.json +22 -0
  58. package/data/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/versions/2026.10.16/manifest.json +234 -0
  59. package/data/knowledge-catalogs/mi/microsoftdocs-powershell-en/manifest.json +22 -0
  60. package/data/knowledge-catalogs/mi/microsoftdocs-powershell-en/versions/2026.10.16/manifest.json +522 -0
  61. package/data/knowledge-catalogs/mi/microsoftdocs-sql-server-en/manifest.json +22 -0
  62. package/data/knowledge-catalogs/mi/microsoftdocs-sql-server-en/versions/2026.10.16/manifest.json +1850 -0
  63. package/data/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/manifest.json +22 -0
  64. package/data/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/versions/2026.10.16/manifest.json +1734 -0
  65. package/data/knowledge-catalogs/mi/microsoftdocs-windows-server-en/manifest.json +22 -0
  66. package/data/knowledge-catalogs/mi/microsoftdocs-windows-server-en/versions/2026.10.16/manifest.json +774 -0
  67. package/data/knowledge-catalogs/raw-index.json +1 -1
  68. package/data/project-types/ch/checkers/versions/1.2.3/about.md +9 -0
  69. package/data/project-types/ch/checkers/versions/1.2.3/game.json +77 -0
  70. package/data/project-types/ch/checkers/versions/1.2.3/manifest.json +182 -0
  71. package/data/project-types/ch/checkers/versions/1.2.3/mission.md +9 -0
  72. package/data/project-types/ch/checkers/versions/1.2.3/pages/board/index.html +1158 -0
  73. package/data/project-types/ch/chess/versions/1.0.6/about.md +9 -0
  74. package/data/project-types/ch/chess/versions/1.0.6/game.json +127 -0
  75. package/data/project-types/ch/chess/versions/1.0.6/manifest.json +196 -0
  76. package/data/project-types/ch/chess/versions/1.0.6/mission.md +8 -0
  77. package/data/project-types/ch/chess/versions/1.0.6/pages/board/index.html +851 -0
  78. package/data/project-types/fi/fitness-coach/versions/1.1.0/about.md +8 -0
  79. package/data/project-types/fi/fitness-coach/versions/1.1.0/craftbooks/training-recap.json +34 -0
  80. package/data/project-types/fi/fitness-coach/versions/1.1.0/manifest.json +229 -0
  81. package/data/project-types/fi/fitness-coach/versions/1.1.0/mission.md +7 -0
  82. package/data/project-types/fi/fitness-coach/versions/1.1.0/pages/dashboard/index.html +383 -0
  83. package/data/project-types/fi/fitness-coach/versions/1.1.0/training.json +6 -0
  84. package/data/project-types/fl/flashcards/versions/1.1.2/about.md +8 -0
  85. package/data/project-types/fl/flashcards/versions/1.1.2/deck.json +7 -0
  86. package/data/project-types/fl/flashcards/versions/1.1.2/manifest.json +169 -0
  87. package/data/project-types/fl/flashcards/versions/1.1.2/mission.md +6 -0
  88. package/data/project-types/fl/flashcards/versions/1.1.2/pages/review/index.html +477 -0
  89. package/data/project-types/fl/flashcards/versions/1.2.0/about.md +8 -0
  90. package/data/project-types/fl/flashcards/versions/1.2.0/deck.json +7 -0
  91. package/data/project-types/fl/flashcards/versions/1.2.0/manifest.json +207 -0
  92. package/data/project-types/fl/flashcards/versions/1.2.0/mission.md +6 -0
  93. package/data/project-types/fl/flashcards/versions/1.2.0/pages/review/index.html +549 -0
  94. package/data/project-types/go/go/versions/1.0.4/about.md +9 -0
  95. package/data/project-types/go/go/versions/1.0.4/game.json +103 -0
  96. package/data/project-types/go/go/versions/1.0.4/manifest.json +182 -0
  97. package/data/project-types/go/go/versions/1.0.4/mission.md +8 -0
  98. package/data/project-types/go/go/versions/1.0.4/pages/board/index.html +855 -0
  99. package/data/project-types/index.json +1 -1
  100. package/data/project-types/la/language-trainer/versions/1.1.0/about.md +8 -0
  101. package/data/project-types/la/language-trainer/versions/1.1.0/manifest.json +245 -0
  102. package/data/project-types/la/language-trainer/versions/1.1.0/mission.md +4 -0
  103. package/data/project-types/la/language-trainer/versions/1.1.0/pages/dashboard/index.html +468 -0
  104. package/data/project-types/la/language-trainer/versions/1.1.0/progress.json +10 -0
  105. package/data/project-types/la/language-trainer/versions/1.1.0/reviews.json +3 -0
  106. package/data/project-types/la/language-trainer/versions/1.1.0/scenarios.json +84 -0
  107. package/data/project-types/raw-index.json +1 -1
  108. package/data/project-types/wo/word-clues/manifest.json +20 -0
  109. package/data/project-types/wo/word-clues/versions/1.0.0/about.md +7 -0
  110. package/data/project-types/wo/word-clues/versions/1.0.0/game.json +14 -0
  111. package/data/project-types/wo/word-clues/versions/1.0.0/manifest.json +205 -0
  112. package/data/project-types/wo/word-clues/versions/1.0.0/mission.md +3 -0
  113. package/data/project-types/wo/word-clues/versions/1.0.0/pages/board/index.html +399 -0
  114. package/data/project-types/wo/word-clues/versions/1.0.0/words.json +216 -0
  115. package/package.json +5 -3
  116. package/schemas/craftbook-doc.schema.json +14 -0
  117. package/schemas/craftbook-template-version.schema.json +14 -0
  118. package/schemas/craftbook-test.schema.json +2 -0
  119. package/schemas/project-type-version.schema.json +131 -0
@@ -0,0 +1,8 @@
1
+ A project for learning **{{language}}** by actually speaking it. Pick a scene on the
2
+ **Output** tab (ordering at a café, lost luggage, a pharmacy visit), and your tutor plays
3
+ the other person while you practice in {{language}}.
4
+
5
+ Every mistake you make becomes a correction under the tutor's reply and a card in your
6
+ review deck. The deck brings each correction back a day later, then after three days,
7
+ then after a week, until it sticks. The Output tab shows your level, your day streak,
8
+ the reviews due, and the scenes you can play.
@@ -0,0 +1,245 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.1.0",
4
+ "releasedAt": "2026-10-07T00:00:00Z",
5
+ "extends": "content-writing",
6
+ "meesterManaged": false,
7
+ "leanProfile": true,
8
+ "params": {
9
+ "type": "object",
10
+ "properties": {
11
+ "language": {
12
+ "type": "string",
13
+ "title": "Which language?",
14
+ "description": "The language you want to practice, e.g. Spanish, Japanese, French.",
15
+ "default": "Spanish"
16
+ }
17
+ },
18
+ "required": [
19
+ "language"
20
+ ]
21
+ },
22
+ "nameTemplate": "{{language}} Language Trainer",
23
+ "aboutTemplate": "about.md",
24
+ "missionTemplate": "mission.md",
25
+ "gezels": [
26
+ {
27
+ "templateId": "language-trainer",
28
+ "voorman": true
29
+ }
30
+ ],
31
+ "scripts": {
32
+ "progress-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'progress-store',\n description:\n \"The language trainer's state: progress, the role-play scene in play, and the review deck of the student's corrections. action:'state' reads it; 'begin' starts a scene; 'reply' records the tutor's line and the errors it found in the student's last message; 'review' grades a review card; 'end' closes the scene; 'advance' moves up a level.\",\n inputs: {\n action: {\n type: 'string',\n description: \"One of 'state', 'begin', 'reply', 'review', 'end', 'advance' (or the older 'status', 'record').\",\n required: true,\n },\n scenario: { type: 'string', description: \"For begin: a scenario id or title, e.g. 'coffee'.\" },\n say: { type: 'string', description: \"For reply: the tutor's next line, in the language being practiced.\" },\n errors: {\n type: 'json',\n description: \"For reply: mistakes in the student's last message, [{ wrong, right, note? }], at most 3.\",\n },\n done: { type: 'boolean', description: \"For reply: true when the scene's goal is reached.\" },\n id: { type: 'string', description: \"For review: the review card id, e.g. 'r-3'.\" },\n verdict: { type: 'string', description: \"For review: 'got-it' or 'missed'.\" },\n note: { type: 'string', description: 'For advance or record: a short note.' },\n },\n outputs: {\n status: { type: 'string', description: \"'active' while a scene is in play, else 'idle' (or 'started' / 'done').\" },\n display: { type: 'string', description: \"For reply: the tutor's line plus the corrections, as shown.\" },\n level: { type: 'number', description: 'Current level (1+).' },\n streak: { type: 'number', description: 'Days in a row with practice.' },\n dueCount: { type: 'number', description: 'Review cards due now.' },\n message: { type: 'string', description: 'A short summary of the state.' },\n scene: { type: 'json', description: 'The scene in play: title, the part the tutor plays, setting, goal.' },\n dueReviews: { type: 'json', description: 'Up to three corrections due for review, { wrong, right }.' },\n scenarios: { type: 'json', description: 'Scenario ids open at this level (when no scene is in play).' },\n opening: { type: 'boolean', description: 'True right after begin: the tutor opens the scene.' },\n },\n requires: ['workspace.read', 'workspace.write', 'memory.write', 'reminders'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── review-rules-start ──\n// Corrections become review cards on three Leitner boxes: box 1 comes back\n// the next day, box 2 after three days, box 3 after a week. A card the\n// student gets right moves up; a miss, or the same mistake made again in\n// conversation, sends it back to box 1. Kept self-contained between these\n// sentinels so unit tests execute the shipped bytes.\nvar REVIEW_INTERVAL_DAYS = { 1: 1, 2: 3, 3: 7 };\nvar REVIEW_DAY_MS = 24 * 60 * 60 * 1000;\n\nfunction reviewDueAt(card) {\n if (!card.lastSeenAt) return 0;\n return Date.parse(card.lastSeenAt) + (REVIEW_INTERVAL_DAYS[card.box] || 1) * REVIEW_DAY_MS;\n}\n\nfunction dueReviews(cards, nowMs) {\n return cards\n .filter(function (c) {\n return reviewDueAt(c) <= nowMs;\n })\n .sort(function (a, b) {\n return a.box - b.box || reviewDueAt(a) - reviewDueAt(b);\n });\n}\n\nfunction nextReviewAt(cards, nowMs) {\n var next = Infinity;\n for (var i = 0; i < cards.length; i += 1) {\n var at = reviewDueAt(cards[i]);\n if (at > nowMs && at < next) next = at;\n }\n return next;\n}\n\nfunction cleanText(value, max) {\n return typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim().slice(0, max) : '';\n}\n\n// The grading the model returned, made safe to store: at most three\n// mistakes, each a real change, without duplicates.\nfunction normalizeErrors(raw) {\n var list = Array.isArray(raw) ? raw : [];\n var out = [];\n for (var i = 0; i < list.length && out.length < 3; i += 1) {\n var e = list[i] || {};\n var wrong = cleanText(e.wrong, 80);\n var right = cleanText(e.right, 80);\n if (!wrong || !right || wrong.toLowerCase() === right.toLowerCase()) continue;\n if (out.some(function (o) { return o.wrong.toLowerCase() === wrong.toLowerCase(); })) continue;\n var note = cleanText(e.note, 80);\n out.push(note ? { wrong: wrong, right: right, note: note } : { wrong: wrong, right: right });\n }\n return out;\n}\n\n// Each mistake joins the deck, or sends its card back to box 1.\nfunction addCorrections(cards, errors, nowIso) {\n var max = 0;\n for (var i = 0; i < cards.length; i += 1) {\n var m = /^r-(\\d+)$/.exec(cards[i].id);\n if (m) max = Math.max(max, Number(m[1]));\n }\n for (var j = 0; j < errors.length; j += 1) {\n var e = errors[j];\n var found = cards.find(function (c) {\n return c.wrong.toLowerCase() === e.wrong.toLowerCase() && c.right.toLowerCase() === e.right.toLowerCase();\n });\n if (found) {\n found.box = 1;\n found.lastSeenAt = nowIso;\n found.repeats = (found.repeats || 0) + 1;\n } else {\n max += 1;\n cards.push({\n id: 'r-' + max,\n wrong: e.wrong,\n right: e.right,\n note: e.note || '',\n box: 1,\n lastSeenAt: nowIso,\n createdAt: nowIso,\n repeats: 0,\n });\n }\n }\n return cards;\n}\n\nfunction gradeReview(card, verdict, nowIso) {\n card.box = verdict === 'got-it' ? Math.min(3, card.box + 1) : 1;\n card.lastSeenAt = nowIso;\n return card;\n}\n\n// What the student sees: the tutor's line, then the corrections the app\n// recorded, so feedback never depends on the model writing it out.\nfunction displayLine(say, errors, done) {\n var text = say;\n if (errors.length) {\n text +=\n ' — Corrections: ' +\n errors\n .map(function (e) {\n return '\"' + e.wrong + '\" → \"' + e.right + '\"' + (e.note ? ' (' + e.note + ')' : '');\n })\n .join('; ') +\n '.';\n }\n if (done) text += ' Scene complete.';\n return text;\n}\n\n// The streak counts calendar days with practice, in the device's time zone.\nfunction practiceDay(nowMs) {\n var d = new Date(nowMs);\n return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');\n}\n\nfunction nextStreak(streak, lastDay, nowMs) {\n var today = practiceDay(nowMs);\n if (lastDay === today) return streak || 1;\n return lastDay === practiceDay(nowMs - REVIEW_DAY_MS) ? (streak || 0) + 1 : 1;\n}\n// ── review-rules-end ──\n\ninterface Scene {\n id: string;\n title: string;\n role: string;\n setting: string;\n goal: string;\n level: number;\n status: 'active' | 'done';\n turns: number;\n mistakes: number;\n startedAt: string;\n}\n\ninterface Progress {\n language: string;\n level: number;\n sessions: number;\n streak: number;\n lastPracticeDay: string | null;\n scene: Scene | null;\n history: Array<{ day: string; scene: string; turns: number; mistakes: number }>;\n notes: string[];\n}\n\ninterface Scenario {\n id: string;\n title: string;\n level: number;\n role: string;\n setting: string;\n goal: string;\n}\n\ninterface ReviewCard {\n id: string;\n wrong: string;\n right: string;\n note: string;\n box: number;\n lastSeenAt: string | null;\n createdAt: string;\n repeats: number;\n}\n\nasync function readJson<T>(path: string, fallback: T): Promise<T> {\n try {\n return JSON.parse(await gezel.fs.read(path)) as T;\n } catch {\n return fallback;\n }\n}\n\nasync function writeJson(path: string, value: unknown): Promise<void> {\n await gezel.fs.write(path, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nconst nowMs = Date.now();\nconst nowIso = new Date(nowMs).toISOString();\nconst raw = await readJson<Partial<Progress>>('progress.json', {});\nconst progress: Progress = {\n language: raw.language ?? 'your language',\n level: raw.level ?? 1,\n sessions: raw.sessions ?? 0,\n streak: raw.streak ?? 0,\n lastPracticeDay: raw.lastPracticeDay ?? null,\n scene: raw.scene ?? null,\n history: Array.isArray(raw.history) ? raw.history : [],\n notes: Array.isArray(raw.notes) ? raw.notes : [],\n};\nconst scenarios = (await readJson<{ scenarios?: Scenario[] }>('scenarios.json', {})).scenarios ?? [];\nconst deck = await readJson<{ items?: ReviewCard[] }>('reviews.json', {});\nconst cards: ReviewCard[] = Array.isArray(deck.items) ? deck.items : [];\n\nlet status: string = progress.scene?.status === 'active' ? 'active' : 'idle';\nlet display = '';\nlet progressChanged = false;\nlet cardsChanged = false;\n\nfunction findScenario(query: string): Scenario | undefined {\n const q = query.trim().toLowerCase();\n return (\n scenarios.find((s) => s.id === q) ??\n scenarios.find((s) => s.title.toLowerCase() === q) ??\n scenarios.find((s) => s.title.toLowerCase().includes(q) || q.includes(s.id))\n );\n}\n\nfunction practiced(): void {\n progress.streak = nextStreak(progress.streak, progress.lastPracticeDay, nowMs);\n progress.lastPracticeDay = practiceDay(nowMs);\n}\n\nfunction closeScene(): void {\n const scene = progress.scene;\n if (!scene || scene.status !== 'active') return;\n scene.status = 'done';\n progress.sessions += 1;\n progress.history = [\n ...progress.history,\n { day: practiceDay(nowMs), scene: scene.title, turns: scene.turns, mistakes: scene.mistakes },\n ].slice(-30);\n status = 'done';\n}\n\nswitch (input.action) {\n case 'begin': {\n const scenario = findScenario(String(input.scenario ?? ''));\n if (!scenario) {\n throw new Error(\n `No scenario '${input.scenario ?? ''}'. Choose one of: ${scenarios.map((s) => s.id).join(', ')}.`,\n );\n }\n progress.scene = {\n id: scenario.id,\n title: scenario.title,\n role: scenario.role,\n setting: scenario.setting,\n goal: scenario.goal,\n level: progress.level,\n status: 'active',\n turns: 0,\n mistakes: 0,\n startedAt: nowIso,\n };\n status = 'started';\n progressChanged = true;\n break;\n }\n case 'reply': {\n const say = String(input.say ?? '').replace(/\\s+/g, ' ').trim().slice(0, 240);\n if (!say) throw new Error(\"reply needs 'say': your next line, in the language being practiced.\");\n const errors = normalizeErrors(input.errors);\n if (errors.length) {\n addCorrections(cards, errors, nowIso);\n cardsChanged = true;\n for (const e of errors) {\n const text = `Said \"${e.wrong}\"; it is \"${e.right}\"${e.note ? ` (${e.note})` : ''}`.slice(0, 200);\n try {\n await gezel.memory.save(text, { kind: 'correction', scope: 'gezel' });\n } catch {\n // Memory is a bonus; the deck already holds the correction.\n }\n }\n }\n if (progress.scene?.status === 'active') {\n progress.scene.turns += 1;\n progress.scene.mistakes += errors.length;\n }\n practiced();\n // A scene ends once the student has played it: never on the opener, and\n // not before two lines of their own (a small model marked its first line done).\n const finished = input.done === true && (progress.scene?.turns ?? 0) >= 3;\n if (finished) closeScene();\n display = displayLine(say, errors, finished);\n progressChanged = true;\n break;\n }\n case 'review': {\n const card = cards.find((c) => c.id === input.id);\n if (!card || (input.verdict !== 'got-it' && input.verdict !== 'missed'))\n throw new Error(\"review needs a card 'id' and verdict 'got-it' or 'missed'.\");\n gradeReview(card, input.verdict, nowIso);\n cardsChanged = true;\n practiced();\n progressChanged = true;\n break;\n }\n case 'end': {\n closeScene();\n progressChanged = true;\n break;\n }\n case 'advance': {\n progress.level += 1;\n progress.notes = [\n ...progress.notes,\n `Advanced to level ${progress.level}${input.note ? `: ${input.note}` : ''}`,\n ].slice(-20);\n progressChanged = true;\n break;\n }\n case 'record': {\n practiced();\n if (input.note) progress.notes = [...progress.notes, String(input.note)].slice(-20);\n progressChanged = true;\n break;\n }\n case 'state':\n case 'status':\n break;\n default:\n throw new Error(`Unknown action '${input.action}'.`);\n}\n\nif (progressChanged) await writeJson('progress.json', progress);\nif (cardsChanged) {\n await writeJson('reviews.json', { items: cards });\n // The next card coming due is the one moment a reminder is earned.\n const next = nextReviewAt(cards, nowMs);\n try {\n if (Number.isFinite(next)) {\n const count = dueReviews(cards, next).length;\n await gezel.reminder.set({\n at: new Date(next).toISOString(),\n title: `${progress.language} review`,\n body: `${count} correction${count === 1 ? '' : 's'} ready to practice.`,\n });\n } else await gezel.reminder.clear();\n } catch {\n // A host without reminders still keeps the deck.\n }\n}\n\nconst due = dueReviews(cards, nowMs);\nconst scene = progress.scene;\nconst levelScenes = scenarios.filter((s) => s.level <= progress.level).map((s) => s.id);\ngezel.output({\n status,\n display,\n level: progress.level,\n streak: progress.streak,\n dueCount: due.length,\n scene:\n scene && scene.status === 'active'\n ? {\n title: scene.title,\n youPlay: scene.role,\n setting: scene.setting,\n studentGoal: scene.goal,\n turnsSoFar: scene.turns,\n }\n : null,\n opening: status === 'started',\n dueReviews: due.slice(0, 3).map((c) => ({ wrong: c.wrong, right: c.right })),\n scenarios: status === 'idle' ? levelScenes : [],\n message:\n status === 'active' || status === 'started'\n ? `Scene \"${scene?.title}\": you play the ${scene?.role}. First check the student's last message for mistakes (word gender, verb forms, agreement, word choice) and list each one in errors as { wrong, right }. Then answer in character, only in ${progress.language}.`\n : `Learning ${progress.language}: level ${progress.level}, ${progress.sessions} scene(s), ${progress.streak}-day streak, ${due.length} review(s) due.`,\n});\n"
33
+ },
34
+ "tools": [
35
+ {
36
+ "name": "practice_state",
37
+ "description": "Read the practice state: the scene in play (the part you play, the setting, the student's goal), the student's level and streak, and corrections due for review.",
38
+ "script": "progress-store",
39
+ "inputs": {
40
+ "type": "object",
41
+ "properties": {}
42
+ },
43
+ "bind": {
44
+ "action": "state"
45
+ },
46
+ "state": true,
47
+ "answer": {
48
+ "tool": "reply",
49
+ "when": {
50
+ "op": "equals",
51
+ "field": "status",
52
+ "value": "active"
53
+ }
54
+ }
55
+ },
56
+ {
57
+ "name": "reply",
58
+ "description": "Answer the student during a scene. `say` is your next line in the language being practiced, in character and at their level (one or two short sentences). `errors` lists up to three mistakes in the student's last message as { wrong, right, note? }, quoting their exact words in `wrong`; leave it empty when they made none. Set `done` only after the student has reached the scene's goal, never on your opening line.",
59
+ "script": "progress-store",
60
+ "inputs": {
61
+ "type": "object",
62
+ "properties": {
63
+ "say": {
64
+ "type": "string",
65
+ "description": "Your next line, in the language being practiced (max 240 characters)."
66
+ },
67
+ "errors": {
68
+ "type": "array",
69
+ "description": "Mistakes in the student's last message, at most three; an empty list when there were none.",
70
+ "items": {
71
+ "type": "object",
72
+ "properties": {
73
+ "wrong": {
74
+ "type": "string",
75
+ "description": "The student's exact words."
76
+ },
77
+ "right": {
78
+ "type": "string",
79
+ "description": "The corrected form."
80
+ },
81
+ "note": {
82
+ "type": "string",
83
+ "description": "A very short reason, in English."
84
+ }
85
+ },
86
+ "required": [
87
+ "wrong",
88
+ "right"
89
+ ]
90
+ }
91
+ },
92
+ "done": {
93
+ "type": "boolean",
94
+ "description": "True when the student reached the scene's goal."
95
+ }
96
+ },
97
+ "required": [
98
+ "say",
99
+ "errors"
100
+ ]
101
+ },
102
+ "bind": {
103
+ "action": "reply"
104
+ },
105
+ "turn": {
106
+ "say": "say",
107
+ "show": "display",
108
+ "fallback": "Your turn."
109
+ }
110
+ },
111
+ {
112
+ "name": "begin_scene",
113
+ "description": "Start a role-play scene the student asked for, by scenario id or title (coffee, market, neighbor, directions, pharmacy, luggage, restaurant, hotel, doctor, return). Then open it with reply.",
114
+ "script": "progress-store",
115
+ "inputs": {
116
+ "type": "object",
117
+ "properties": {
118
+ "scenario": {
119
+ "type": "string",
120
+ "description": "Scenario id or title."
121
+ }
122
+ },
123
+ "required": [
124
+ "scenario"
125
+ ]
126
+ },
127
+ "bind": {
128
+ "action": "begin"
129
+ }
130
+ },
131
+ {
132
+ "name": "advance_level",
133
+ "description": "Advance the student to the next level when the current scenes feel comfortable.",
134
+ "script": "progress-store",
135
+ "inputs": {
136
+ "type": "object",
137
+ "properties": {
138
+ "note": {
139
+ "type": "string"
140
+ }
141
+ }
142
+ },
143
+ "bind": {
144
+ "action": "advance"
145
+ }
146
+ },
147
+ {
148
+ "name": "start_scene",
149
+ "description": "Start a scene from the dashboard.",
150
+ "script": "progress-store",
151
+ "inputs": {
152
+ "type": "object",
153
+ "properties": {
154
+ "scenario": {
155
+ "type": "string"
156
+ }
157
+ },
158
+ "required": [
159
+ "scenario"
160
+ ]
161
+ },
162
+ "bind": {
163
+ "action": "begin"
164
+ },
165
+ "reaction": {
166
+ "gezel": "language-trainer",
167
+ "hideSeed": true,
168
+ "standalone": true,
169
+ "prompt": "The student started the scene \"{{output.scene.title}}\". You play the {{output.scene.youPlay}}. Setting: {{output.scene.setting}} The student's goal: {{output.scene.studentGoal}} Open the scene with one short line in {{language}}, in character and at level {{output.level}}, through reply with no errors. The scene has only begun: do not set done.",
170
+ "turn": {
171
+ "tool": "reply",
172
+ "when": {
173
+ "op": "equals",
174
+ "field": "status",
175
+ "value": "started"
176
+ }
177
+ }
178
+ }
179
+ },
180
+ {
181
+ "name": "review_card",
182
+ "description": "Grade a review card from the dashboard.",
183
+ "script": "progress-store",
184
+ "inputs": {
185
+ "type": "object",
186
+ "properties": {
187
+ "id": {
188
+ "type": "string"
189
+ },
190
+ "verdict": {
191
+ "type": "string"
192
+ }
193
+ },
194
+ "required": [
195
+ "id",
196
+ "verdict"
197
+ ]
198
+ },
199
+ "bind": {
200
+ "action": "review"
201
+ }
202
+ },
203
+ {
204
+ "name": "end_scene",
205
+ "description": "End the scene in play from the dashboard.",
206
+ "script": "progress-store",
207
+ "inputs": {
208
+ "type": "object",
209
+ "properties": {}
210
+ },
211
+ "bind": {
212
+ "action": "end"
213
+ }
214
+ }
215
+ ],
216
+ "pages": {
217
+ "entry": "dashboard/index.html",
218
+ "api": 1,
219
+ "reads": [
220
+ {
221
+ "source": "workspace",
222
+ "path": "progress.json"
223
+ },
224
+ {
225
+ "source": "workspace",
226
+ "path": "scenarios.json"
227
+ },
228
+ {
229
+ "source": "workspace",
230
+ "path": "reviews.json"
231
+ }
232
+ ],
233
+ "tools": [
234
+ "start_scene",
235
+ "review_card",
236
+ "end_scene"
237
+ ]
238
+ },
239
+ "workspaceSeed": [
240
+ "progress.json",
241
+ "scenarios.json",
242
+ "reviews.json"
243
+ ],
244
+ "minGezelVersion": "1.26280"
245
+ }
@@ -0,0 +1,4 @@
1
+ - Practice {{language}} in short role-play scenes that mirror everyday situations.
2
+ - Turn every mistake into a correction worth reviewing, and review what is due.
3
+ - Build a daily habit: a scene or a few reviews a day beats a long session once a week.
4
+ - Advance a level only when the current scenes feel comfortable.