Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.0] - 2026-07-11

### Added

- **Pick-the-right-word cloze exercises** (contract v1.0 §3.6.1, the two-variant
`cloze` type) across the full story ladder: one new exercise per story (11
total, each named `q5` after its story's existing four), spanning all five
CEFR rungs (A1 through C1) and all three coarse levels. Every new exercise
carries `text` (a passage lifted near-verbatim from the story body, so it
stays answerable from the reading alone) and `blanks`, with level-appropriate
distractors: same-word-class confusions for A1/A2 (units of measure, flavor
nouns); gender/number agreement traps for B1 (`spacieux`/`spacieuse`,
`retenus`/`retenue`); mood and tense contrasts, including the subjunctive
triggered by `s'inquiéter que`, for B2; and a near-homophone reading trap
(`deviner`/`devenir`) for the C1 literary story. Each new exercise reuses an
existing exercise's `item_id` from its own story — no existing `item_id` or
exercise was touched, so learner mastery on those items carries over intact.

### Changed

- **Re-synced `french/contract_cite/schemas/{story,practice}.json` and
`_validate.py` from learn-cli** (source commit `cdac077`) to pick up the
`text`/`blanks` exercise fields and the new `cloze_blank` def added for the
pick-the-right-word variant. Purely additive — the contract stays `1.0`, and
every payload that predates these fields keeps validating unchanged; see the
updated stamp in `docs/contract-provenance.md`.

## [0.5.0] - 2026-07-11

### Added
Expand Down
11 changes: 11 additions & 0 deletions content/stories/fr-a1-la-rentree.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@
"item_id": "fr.story.fr-a1-la-rentree.4",
"prompt": "Pourquoi Nina a-t-elle un peu peur le matin de la rentrée ?",
"rubric": "Passes if the answer mentions the new teacher or general first-day nerves; partial if it just says 'she is scared' with no reason."
},
{
"id": "fr-a1-la-rentree-q5",
"type": "cloze",
"item_id": "fr.story.fr-a1-la-rentree.2",
"prompt": "Fill in each blank with the right word.",
"text": "« Il y a une nouvelle {{enseignante}} cette année, » dit Nina.",
"answer": "maîtresse",
"blanks": [
{ "id": "enseignante", "options": ["maîtresse", "maître", "professeur"], "answer": "maîtresse" }
]
}
],
"audio": null,
Expand Down
11 changes: 11 additions & 0 deletions content/stories/fr-a1-le-marche.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@
"item_id": "fr.story.fr-a1-le-marche.4",
"prompt": "Pourquoi Léo est-il fier à la fin de l'histoire ?",
"rubric": "Passes if the answer names that he spoke French with the vendors by himself/alone; partial if it only says he went to the market."
},
{
"id": "fr-a1-le-marche-q5",
"type": "cloze",
"item_id": "fr.story.fr-a1-le-marche.3",
"prompt": "Fill in each blank with the right word.",
"text": "Léo dit au vendeur : « Bonjour, monsieur. Je voudrais un {{unit}} de carottes, s'il vous plaît. »",
"answer": "kilo",
"blanks": [
{ "id": "unit", "options": ["kilo", "litre", "mètre"], "answer": "kilo" }
]
}
],
"audio": null,
Expand Down
11 changes: 11 additions & 0 deletions content/stories/fr-a2-le-cafe-du-coin.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@
"item_id": "fr.story.fr-a2-le-cafe-du-coin.4",
"prompt": "Amina dit que retrouver une vieille amie lui a fait du bien. Raconte une fois où tu as retrouvé un ami après une longue absence.",
"rubric": "Passes if the learner produces a coherent short personal narrative in French connected to the theme of reunion; partial if the answer is a single word or off-topic."
},
{
"id": "fr-a2-le-cafe-du-coin-q5",
"type": "cloze",
"item_id": "fr.story.fr-a2-le-cafe-du-coin.2",
"prompt": "Fill in each blank with the right word.",
"text": "Chloé choisit un thé à la {{gout}}.",
"answer": "menthe",
"blanks": [
{ "id": "gout", "options": ["menthe", "citron", "vanille"], "answer": "menthe" }
]
}
],
"audio": null,
Expand Down
11 changes: 11 additions & 0 deletions content/stories/fr-a2-le-weekend-a-la-montagne.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@
"item_id": "fr.story.fr-a2-le-weekend-a-la-montagne.4",
"prompt": "Décris ce que la famille fait le soir dans le chalet.",
"rubric": "Passes if the answer mentions warming up by the fireplace, the father telling stories, and the mother making hot chocolate; partial if only one detail is given."
},
{
"id": "fr-a2-le-weekend-a-la-montagne-q5",
"type": "cloze",
"item_id": "fr.story.fr-a2-le-weekend-a-la-montagne.1",
"prompt": "Fill in each blank with the right word.",
"text": "Ils sortent et construisent un grand {{construction}} de neige avec une carotte pour le nez.",
"answer": "bonhomme",
"blanks": [
{ "id": "construction", "options": ["bonhomme", "château", "bateau"], "answer": "bonhomme" }
]
}
],
"audio": null,
Expand Down
15 changes: 15 additions & 0 deletions content/stories/fr-b1-entretien-embauche.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@
"item_id": "fr.story.fr-b1-entretien-embauche.4",
"prompt": "Que ferais-tu si on te posait une question à laquelle tu ne t'attends pas pendant un entretien ?",
"rubric": "Passes if the learner gives a coherent personal strategy in French (e.g. pausing to think, asking for clarification, giving a concrete example); partial if the answer is a single word."
},
{
"id": "fr-b1-entretien-embauche-q5",
"type": "cloze",
"item_id": "fr.story.fr-b1-entretien-embauche.3",
"prompt": "Fill in each blank with the right word.",
"text": "L'entreprise contactera les candidats {{participe}} dans les deux semaines. Toute cette nervosité, finalement, en valait la {{mot}}.",
"blanks": [
{
"id": "participe",
"options": ["retenus", "retenues", "retenu"],
"answer": "retenus"
},
{ "id": "mot", "options": ["peine", "douleur", "peur"], "answer": "peine" }
]
}
],
"audio": null,
Expand Down
19 changes: 19 additions & 0 deletions content/stories/fr-b1-la-fete-des-voisins.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@
"item_id": "fr.story.fr-b1-la-fete-des-voisins.4",
"prompt": "Selon le texte, pourquoi la fête des voisins est-elle importante pour une communauté ? Ton quartier organise-t-il un événement similaire ?",
"rubric": "Passes if the response connects the event to building community between strangers and offers at least a brief reflection on the learner's own context; partial if it only summarizes the story."
},
{
"id": "fr-b1-la-fete-des-voisins-q5",
"type": "cloze",
"item_id": "fr.story.fr-b1-la-fete-des-voisins.4",
"prompt": "Fill in each blank with the right word.",
"text": "Après quelques verres de vin et beaucoup de rires, l'ambiance se {{verbe}}. Avant cette soirée, elle connaissait {{adverbe}} le nom de ses voisins.",
"blanks": [
{
"id": "verbe",
"options": ["détend", "détendent", "détends"],
"answer": "détend"
},
{
"id": "adverbe",
"options": ["à peine", "déjà", "toujours"],
"answer": "à peine"
}
]
}
],
"audio": null,
Expand Down
19 changes: 19 additions & 0 deletions content/stories/fr-b1-la-recherche-appartement.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@
"item_id": "fr.story.fr-b1-la-recherche-appartement.4",
"prompt": "Camille hésite entre l'espace et la proximité du travail. Si tu devais choisir, que privilégierais-tu, et pourquoi ?",
"rubric": "Passes if the learner gives a reasoned personal preference in French that engages with the space-vs-commute trade-off; partial if it only picks one side without a reason."
},
{
"id": "fr-b1-la-recherche-appartement-q5",
"type": "cloze",
"item_id": "fr.story.fr-b1-la-recherche-appartement.2",
"prompt": "Fill in each blank with the right word.",
"text": "Le propriétaire explique que le loyer comprend les {{mot}}, mais pas internet. Bien qu'il soit {{adj}} et lumineux, le trajet jusqu'au travail prendrait presque quarante minutes.",
"blanks": [
{
"id": "mot",
"options": ["charges", "frais", "taxes"],
"answer": "charges"
},
{
"id": "adj",
"options": ["spacieux", "spacieuse", "spacieuses"],
"answer": "spacieux"
}
]
}
],
"audio": null,
Expand Down
19 changes: 19 additions & 0 deletions content/stories/fr-b1-le-covoiturage.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@
"item_id": "fr.story.fr-b1-le-covoiturage.4",
"prompt": "Selon Julien, qu'est-ce que le covoiturage lui a apporté en plus des économies d'essence ?",
"rubric": "Passes if the answer names new friendships / social connection with the other passengers; partial if it only restates the fuel savings."
},
{
"id": "fr-b1-le-covoiturage-q5",
"type": "cloze",
"item_id": "fr.story.fr-b1-le-covoiturage.2",
"prompt": "Fill in each blank with the right word.",
"text": "Un matin, la voiture tombe en {{mot}} sur l'autoroute. Ines appelle une {{mot2}} pendant que Marc plaisante pour détendre l'atmosphère.",
"blanks": [
{
"id": "mot",
"options": ["panne", "accident", "retard"],
"answer": "panne"
},
{
"id": "mot2",
"options": ["dépanneuse", "ambulance", "police"],
"answer": "dépanneuse"
}
]
}
],
"audio": null,
Expand Down
19 changes: 19 additions & 0 deletions content/stories/fr-b2-la-transition-ecologique.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@
"item_id": "fr.story.fr-b2-la-transition-ecologique.4",
"prompt": "L'article insiste sur l'absence de « discours idéologique » dans la démarche du village. Penses-tu que ce soit une force ou une limite pour la transition écologique en général ?",
"rubric": "Passes if the learner engages with the tension between pragmatic local action and broader ideological/political framing, and takes a defensible position; partial if it only summarizes the village's approach without answering the question."
},
{
"id": "fr-b2-la-transition-ecologique-q5",
"type": "cloze",
"item_id": "fr.story.fr-b2-la-transition-ecologique.2",
"prompt": "Fill in each blank with the right word.",
"text": "Le maire, initialement {{attitude}} quant à la rentabilité du projet, admet aujourd'hui que les économies réalisées sur la facture d'électricité ont {{verbe}} ses attentes les plus optimistes.",
"blanks": [
{
"id": "attitude",
"options": ["sceptique", "optimiste", "confiant"],
"answer": "sceptique"
},
{
"id": "verbe",
"options": ["dépassé", "respecté", "déçu"],
"answer": "dépassé"
}
]
}
],
"audio": null,
Expand Down
19 changes: 19 additions & 0 deletions content/stories/fr-b2-le-teletravail.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@
"item_id": "fr.story.fr-b2-le-teletravail.4",
"prompt": "L'auteur termine par une question sur une remise en cause plus vaste de l'organisation du travail salarié. Qu'en penses-tu : le télétravail est-il une simple adaptation, ou le début d'un changement plus profond ?",
"rubric": "Passes if the learner takes and defends a position with at least one supporting reason, engaging with the article's closing question; partial if it restates the question without taking a position."
},
{
"id": "fr-b2-le-teletravail-q5",
"type": "cloze",
"item_id": "fr.story.fr-b2-le-teletravail.2",
"prompt": "Fill in each blank with the right word.",
"text": "Plusieurs managers s'inquiètent que le télétravail n'{{verbe}} la cohésion des équipes, et que la frontière entre vie privée et vie professionnelle ne {{verbe2}} complètement.",
"blanks": [
{
"id": "verbe",
"options": ["affaiblisse", "affaiblit", "affaiblira"],
"answer": "affaiblisse"
},
{
"id": "verbe2",
"options": ["s'efface", "s'effacent", "s'effaçait"],
"answer": "s'efface"
}
]
}
],
"audio": null,
Expand Down
19 changes: 19 additions & 0 deletions content/stories/fr-c1-le-vieux-libraire.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@
"item_id": "fr.story.fr-c1-le-vieux-libraire.4",
"prompt": "Le texte ne révèle jamais ce que contenait le livre offert par Monsieur Aubert. Pourquoi, selon toi, l'auteur fait-il ce choix ? Qu'est-ce que cela ajoute à l'histoire ?",
"rubric": "Passes if the learner reflects on the literary effect of withholding the title (mystery, focus on the human moment rather than the object, room for the reader's imagination) rather than simply guessing a specific title; partial if the answer only guesses a title without reflecting on the choice."
},
{
"id": "fr-c1-le-vieux-libraire-q5",
"type": "cloze",
"item_id": "fr.story.fr-c1-le-vieux-libraire.2",
"prompt": "Fill in each blank with the right word.",
"text": "Il avait ce don, rare, de {{verbe}} en quelques mots à peine échangés ce qu'une personne {{verbe2}} vraiment à lire.",
"blanks": [
{
"id": "verbe",
"options": ["deviner", "décider", "devenir"],
"answer": "deviner"
},
{
"id": "verbe2",
"options": ["cherchait", "demandait", "voulait"],
"answer": "cherchait"
}
]
}
],
"audio": null,
Expand Down
9 changes: 7 additions & 2 deletions docs/contract-provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ subject as a subprocess.

- **Source repo:** `agentculture/learn-cli`
- **Contract:** subject-plugin contract **v1.0** (`docs/specs/subject-plugin-contract.md`)
- **Source commit:** `ed81b87` (2026-07-11)
- **Source commit:** `cdac077` (2026-07-11)
- **Cited on:** 2026-07-11
- **Cited by:** french-cli (task t4 of the learn uplift)
- **Cited by:** french-cli (task t4 of the learn uplift; re-synced by task t11 to
pick up the pick-the-right-word cloze variant fields — `text`/`blanks` on the
exercise def and the new `cloze_blank` def — added to `story.json` and
`practice.json`, contract §3.6.1. Purely additive: contract version stays
`1.0`, and every payload that predates these fields keeps validating
unchanged.)
Comment on lines +28 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Provenance stamps diverged 🐞 Bug ⚙ Maintainability

The contract provenance stamp was updated to upstream commit cdac077 in the ledger doc and
_validate.py, but french/contract_cite/__init__.py still advertises the older ed81b87. This
creates an inconsistent provenance trail that makes future contract re-syncs harder to audit
correctly.
Agent Prompt
### Issue description
The contract provenance stamp is inconsistent across the cited contract files: docs and `_validate.py` point at commit `cdac077`, while `french/contract_cite/__init__.py` still claims `ed81b87`.

### Issue Context
This repo treats `french/contract_cite/*` as a cite-don’t-import mirror of learn-cli’s contract, so keeping provenance stamps consistent is important for auditability and repeatable re-syncs.

### Fix Focus Areas
- french/contract_cite/__init__.py[5-12]
- docs/contract-provenance.md[26-35]
- french/contract_cite/_validate.py[7-14]

### What to change
- Update the provenance header in `french/contract_cite/__init__.py` to match the new upstream commit (`cdac077`) and the updated “cited by” context (task t11), OR remove/centralize the duplicated provenance text so there is only one authoritative stamp.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


## Re-sync procedure

Expand Down
Loading
Loading