@intellectif/lk-core 0.8.1 → 0.9.0

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 (37) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -6
  3. package/dist/{chunk-2M76F32Y.js → chunk-6DM2H6BD.js} +2 -2
  4. package/dist/{chunk-HCOWSGEZ.js → chunk-AHEUYOT3.js} +705 -17
  5. package/dist/chunk-AHEUYOT3.js.map +1 -0
  6. package/dist/{chunk-FKH4YT5X.cjs → chunk-CVRALNUK.cjs} +706 -18
  7. package/dist/chunk-CVRALNUK.cjs.map +1 -0
  8. package/dist/{chunk-NHGXOOZ2.js → chunk-ELTP5P6V.js} +2 -2
  9. package/dist/{chunk-YJZY5TPY.cjs → chunk-MOLL5KXO.cjs} +8 -8
  10. package/dist/{chunk-YJZY5TPY.cjs.map → chunk-MOLL5KXO.cjs.map} +1 -1
  11. package/dist/{chunk-LNA33IK7.js → chunk-QX7P3CHP.js} +2 -2
  12. package/dist/{chunk-7ACNBDSF.cjs → chunk-RL2PQLCY.cjs} +4 -4
  13. package/dist/{chunk-7ACNBDSF.cjs.map → chunk-RL2PQLCY.cjs.map} +1 -1
  14. package/dist/{chunk-FVLKIL6W.cjs → chunk-YVZCFUGP.cjs} +12 -12
  15. package/dist/{chunk-FVLKIL6W.cjs.map → chunk-YVZCFUGP.cjs.map} +1 -1
  16. package/dist/index.cjs +134 -42
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +157 -6
  19. package/dist/index.d.ts +157 -6
  20. package/dist/index.js +115 -23
  21. package/dist/index.js.map +1 -1
  22. package/dist/schemas.cjs +3 -3
  23. package/dist/schemas.js +2 -2
  24. package/dist/scoring.cjs +3 -3
  25. package/dist/scoring.js +2 -2
  26. package/dist/xapi.cjs +3 -3
  27. package/dist/xapi.js +2 -2
  28. package/package.json +2 -1
  29. package/vectors/README.md +97 -0
  30. package/vectors/replay.d.mts +44 -0
  31. package/vectors/replay.mjs +159 -0
  32. package/vectors/scoring.json +7698 -0
  33. package/dist/chunk-FKH4YT5X.cjs.map +0 -1
  34. package/dist/chunk-HCOWSGEZ.js.map +0 -1
  35. /package/dist/{chunk-2M76F32Y.js.map → chunk-6DM2H6BD.js.map} +0 -0
  36. /package/dist/{chunk-NHGXOOZ2.js.map → chunk-ELTP5P6V.js.map} +0 -0
  37. /package/dist/{chunk-LNA33IK7.js.map → chunk-QX7P3CHP.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @intellectif/lk-core
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8ca07c8: Authoring: tell an unfinished question from a wrong one.
8
+
9
+ `validateActivity` answers "may this be stored?", and to that question a question an author added a second ago and a broken one get the same answer. An editor that used it as its only check reported every new question as an error — enough to disable a whole form's Save button and blank its preview.
10
+
11
+ **lk-core**
12
+
13
+ - `validateDraft(type, draft)` returns `{ status: 'complete', data, issues: [] }` or `{ status: 'incomplete' | 'invalid', issues }`. Each issue is a `ValidationError` plus a `severity`, at the paths `validateActivity` reports. Its `code` is documented per type in the authoring guide for every problem the checks recognise; anything else is the schema's own diagnostic, at `invalid`. There is deliberately no `success` boolean, which would have to call an incomplete draft either a success or a failure.
14
+ - It is stricter than `validateActivity` and never looser. It reports a title or option text that is only whitespace, a blank written-response `prompt` even beside `promptHtml`, a rubric criterion name that is only whitespace, a rubric whose weights are all 0 or add up to more than a number can hold, a fill-in-the-blanks match `locale` that is not a language tag (scoring would throw on it), and `redacted: true`. A `null` in a required field reads as not set; any other `null` the schema refuses is `null_not_allowed`, for every registered type. It does not report `minWords: 0`, which means no lower limit, or a rubric weight above 1.
15
+ - `createDraft(type, { newId })` returns an empty draft that `validateDraft` reports as `incomplete`. It invents no ids, and a new multiple-choice draft marks no option correct, so an untouched correctness control can never become an answer key.
16
+ - `ActivityTypeDescriptor.authoring` — `createDraft` and `checkDraft` — gives a registered type the same support. The three built-in descriptors carry one.
17
+
18
+ **lk-react**
19
+
20
+ - `<ActivityPreview>`, also at `@intellectif/lk-react/components/ActivityPreview`, renders a draft in any `renderMode` with a simulated `response` — marked with `evaluate()` in `review` — and renders a notice, or your `fallback` with the issues, while the draft is not complete. Nothing it renders is recorded, and a recording's play limit is enforced in memory in `practice` and `exam`. It checks the draft by content on every render, whatever order its keys are in, so an editor that rebuilds or reloads its payload does not restart the question being tried.
21
+ - `LkStrings` gains `previewIncomplete` and `previewInvalid`. A dictionary declared as a complete `LkStrings` must add both; partial overrides are unaffected.
22
+
23
+ Nothing that already exists changes behaviour in either package. lk-react's major version comes from its lk-core peer range.
24
+
25
+ ## 0.8.2
26
+
27
+ ### Patch Changes
28
+
29
+ - e1b9bb9: Grade-stability vectors: lk-core's grading, frozen as data and shipped in the package.
30
+
31
+ The standing rule for this package is that anything which can change a historical grade is a major release. The unit suite already asserted scoring numbers, but against source, and those assertions change alongside the code they test. Nothing froze them independently, and nothing checked them against the package you actually install. Now something does.
32
+
33
+ `vectors/scoring.json` records 231 calls across every path that decides a grade — `score` and `evaluate` for each built-in activity type (including the legacy per-blank `caseSensitive` / `trimWhitespace` flags, and the refusal to score redacted data), written-response word counts recomputed from the text rather than taken from the client, `matchText` across every authored policy, rounding in every mode, `countWords`, `seededShuffle`, rubric grading, how a stored attempt plan becomes scored items, and whole-assessment composition — together with the exact value each returned. The values were frozen by executing 0.8.1, so **nothing about grading changes in this release**: the corpus records what already is.
34
+
35
+ - **It is replayed on every change.** `pnpm test` replays it against source; `check-packaging` replays it against the built package, CJS and ESM; and CI runs both on Node 22 and on Node 24, the Node the release is built with. A vector that returns anything different fails the build, by name.
36
+ - **It cannot drift from its inputs unnoticed.** `pnpm test` also fails when a case was never generated, a vector was deleted by hand, or a frozen call's arguments no longer match its case. The generator adds vectors freely but refuses to rewrite or remove one without `--accept-grade-change`.
37
+ - **Its coverage was tested rather than assumed.** The scoring source was deliberately broken — comparisons flipped, defaults changed, guards removed — and every one of those breaks that can reach a stored grade now fails at least one vector. The breaks that still pass change no grade: they touch feedback prose, xAPI patterns or a hook nothing in this package calls, have no observable effect, or sit exactly on a floating-point epsilon that only the epsilon itself can reach.
38
+ - **You can replay it against the build you install.** It ships in the tarball beside `vectors/replay.mjs`, which does the comparison, so a test suite that pins lk-core's arithmetic no longer has to restate it. `vectors/README.md` has the recipe.
39
+ - **It is deliberately not an export.** An export would be new public API — a minor, which would force a `@intellectif/lk-react` major for a file of test data. The corpus is reached by resolving `@intellectif/lk-core/package.json`, which is already exported.
40
+
41
+ Several vectors pin sharp edges on purpose, each with a note saying why. None of them changes in this release:
42
+
43
+ - rubric weights are read from each criterion and never joined from the activity's rubric;
44
+ - with no activity, `gradeFromRubric` compares against a literal `0.7` rather than `DEFAULT_PASS_THRESHOLD`;
45
+ - an unscorable item leaves the denominator of a composed score, which stays final;
46
+ - a paper whose graded sections all weigh 0 records a final 0 and a fail;
47
+ - `ignorePunctuation` on its own leaves the space a removed mark stood beside, so `"hola !"` does not match `"hola"` unless `collapseInnerWhitespace` is also set;
48
+ - band classification absorbs float noise just below a boundary, but not a genuine shortfall;
49
+ - `levenshtein: 1` against a one-character answer accepts any single character.
50
+
51
+ Also: the README now states the real browser floor — the built code needs `Object.hasOwn`, so Chrome and Edge 93, Firefox 92, Safari 15.4 — and the contributor docs no longer describe a Node 20 CI matrix that was retired.
52
+
3
53
  ## 0.8.1
4
54
 
5
55
  ### Patch Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Framework-free TypeScript core of [learning-kit](https://github.com/intellectif/learning-kit): Zod-based **activity schemas**, a pure **scoring engine**, **rubric and deferred grading**, **weighted assessment composition**, reproducible **exam attempt plans**, fail-closed **redaction**, and an **xAPI 1.0.3 statement builder + validator**. Zero React, zero DOM — usable in Node, browsers, edge, workers, or as the data layer behind `@intellectif/lk-react`.
4
4
 
5
- It is built for **summative** assessment as much as practice: nothing here grades on the client, "not marked yet" is a first-class outcome that is never conflated with a zero, and every behavioural change that could move a historical grade is opt-in.
5
+ It is built for **summative** assessment as much as practice: nothing here grades on the client, "not marked yet" is a first-class outcome that is never conflated with a zero, and every behavioural change that could move a historical grade is opt-in. A frozen corpus of grading vectors ships in the package, fails CI if any of them moves, and can be replayed against the build you install.
6
6
 
7
7
  ```bash
8
8
  pnpm add @intellectif/lk-core
@@ -16,7 +16,8 @@ npm install @intellectif/lk-core
16
16
 
17
17
  - **Activity schemas** for `multiple-choice`, `fill-in-the-blanks` and `written-response`, with semantic refinements (e.g. *at least one correct option*, *single-select ⇒ exactly one correct*, *passage ↔ blank-id bijection*, *image/embed require `alt`*). Loose at every level, so your sidecar fields survive validation.
18
18
  - **`validateActivity(type, data)`** — the authoritative runtime validator. Returns a typed `{ success, data }` or a structured `{ success: false, errors[] }`, and **throws `UnknownActivityTypeError` if `type` is not registered** — so guard it when validating a content bank that may carry types this build does not know. `validateItemGroup(data)` is the equivalent for a `Stimulus` + `ItemGroup` container, which `validateActivity` cannot take.
19
- - **An open type system** — `defineActivityType` / `registerActivityType` make an activity type a *value*, not a hard-coded union member: register one and `validateActivity`, `score`, `evaluate`, `redact` and `jsonSchemaFor` all work for it, with no SDK release.
19
+ - **`validateDraft(type, draft)`**for editors. Reports `complete`, `incomplete` (something not written yet) or `invalid` (something wrong), each issue with a `severity` and a `code` documented for every problem the checks recognise and deliberately no `success` boolean to misread. Stricter than `validateActivity`, never looser. **`createDraft(type, { newId })`** returns an empty draft to start from; a multiple-choice draft marks no option correct.
20
+ - **An open type system** — `defineActivityType` / `registerActivityType` make an activity type a *value*, not a hard-coded union member: register one and `validateActivity`, `validateDraft`, `score`, `evaluate`, `redact` and `jsonSchemaFor` all work for it, with no SDK release. An `authoring` block on the descriptor gives it draft support.
20
21
  - **JSON Schema (Draft-7) export** — `jsonSchemaFor(type)` for any registered type (plus the static per-type exports), suitable for form generators or AI prompting.
21
22
  - **Scoring engine** — `score(activityType, data, response)` returning `{ score ∈ [0,1], maxScore: 1, passed, feedback, details }`. Pure, deterministic, 100 % test coverage enforced. It **throws** rather than inventing a number: `DeferredScoringError` for a deferred-graded type (`written-response`), and `RedactedScoringError` when handed a `redact()` projection, which has no answer key. Both are on the documented exam path — use `evaluate()` there, which returns `deferred` / `unscorable` instead of throwing.
22
23
  - **`evaluate(data, response)` → `ItemOutcome`** — the resilient result: `scored`, `deferred` (graded later by an AI or a human), `graded` (the grade came back), or `unscorable`. "Not graded yet" is expressible in the type system and is never conflated with a zero.
@@ -114,17 +115,18 @@ result.passed; // boolean, or null while provisional. Never record a provision
114
115
  | `@intellectif/lk-core/xapi` | `xAPIBuilder`, `XAPIVerb`, `validateXAPIStatement` |
115
116
 
116
117
  The barrel re-exports everything, and it is the **only** entry point for attempt
117
- plans, attempt state, item groups, redaction, the type registry and content
118
- hashing — those have no subpath of their own.
118
+ plans, attempt state, item groups, redaction, the type registry, authoring drafts
119
+ and content hashing — those have no subpath of their own.
119
120
 
120
- All exports ship as ESM + CJS with `.d.ts` types. Tree-shakeable; `sideEffects: false`. Node >= 20.
121
+ All exports ship as ESM + CJS with `.d.ts` types. Tree-shakeable; `sideEffects: false`. Node >= 20. In browsers, the SDK's own code needs `Object.hasOwn` — Chrome and Edge 93, Firefox 92, Safari 15.4 — because output targets ES2022 and nothing is polyfilled.
121
122
  Zod is the single runtime dependency: the package depends on `zod@^3.25` and imports the **Zod 4 API** from its `zod/v4` subpath, so it coexists with an app still on Zod 3.
122
123
 
123
124
  ## Documentation
124
125
 
125
126
  - [Upgrading](https://github.com/intellectif/learning-kit/blob/main/docs/upgrading.md) — start here on any upgrade from 0.3.x, 0.4.x or 0.5.x.
126
127
  - [Changelog](https://github.com/intellectif/learning-kit/blob/main/packages/lk-core/CHANGELOG.md) — every release, with the reasoning.
127
- - [Authoring & content storage](https://github.com/intellectif/learning-kit/blob/main/docs/authoring.md) — data model, validation, fetch validate render flow.
128
+ - [Grade-stability vectors](https://github.com/intellectif/learning-kit/blob/main/packages/lk-core/vectors/README.md) — the package's grading frozen as data; replay it against the build you install.
129
+ - [Authoring & content storage](https://github.com/intellectif/learning-kit/blob/main/docs/authoring.md) — data model, validation, fetch → validate → render flow, and building an editor (the draft issue codes).
128
130
  - [Project README](https://github.com/intellectif/learning-kit#readme) — the full picture, including the React renderers.
129
131
  - [Contributing](https://github.com/intellectif/learning-kit/blob/main/CONTRIBUTING.md) — adding a new activity type.
130
132
 
@@ -6,7 +6,7 @@ import {
6
6
  UnknownActivityTypeError,
7
7
  WrittenResponseDataSchema,
8
8
  getActivityTypeDescriptor
9
- } from "./chunk-HCOWSGEZ.js";
9
+ } from "./chunk-AHEUYOT3.js";
10
10
 
11
11
  // src/schemas/item-group.ts
12
12
  import { z } from "zod/v4";
@@ -191,4 +191,4 @@ export {
191
191
  jsonSchemaFor,
192
192
  validateActivity
193
193
  };
194
- //# sourceMappingURL=chunk-2M76F32Y.js.map
194
+ //# sourceMappingURL=chunk-6DM2H6BD.js.map