@intellectif/lk-core 1.6.0 → 1.8.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 (86) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/{ai-i3jwXnpX.d.ts → ai-Cnm0qE8v.d.cts} +334 -16
  3. package/dist/ai-Cnm0qE8v.d.cts.map +1 -0
  4. package/dist/{ai-i3jwXnpX.d.cts → ai-Cnm0qE8v.d.ts} +334 -16
  5. package/dist/ai-Cnm0qE8v.d.ts.map +1 -0
  6. package/dist/ai-check.cjs +88 -7
  7. package/dist/ai-check.cjs.map +1 -1
  8. package/dist/ai-check.d.cts +11 -7
  9. package/dist/ai-check.d.cts.map +1 -1
  10. package/dist/ai-check.d.ts +11 -7
  11. package/dist/ai-check.d.ts.map +1 -1
  12. package/dist/ai-check.js +88 -7
  13. package/dist/ai-check.js.map +1 -1
  14. package/dist/{ai-writing-C4YRVrFi.cjs → ai-writing-Bh2Y53cY.cjs} +2567 -203
  15. package/dist/ai-writing-Bh2Y53cY.cjs.map +1 -0
  16. package/dist/{ai-writing-OJ5m_1EB.js → ai-writing-DeIV-GfN.js} +2428 -172
  17. package/dist/ai-writing-DeIV-GfN.js.map +1 -0
  18. package/dist/{index-Dj92T76o.d.ts → index-CtoPhJAH.d.ts} +2 -8
  19. package/dist/index-CtoPhJAH.d.ts.map +1 -0
  20. package/dist/{index--U7hHV0s.d.cts → index-DMh3OYSb.d.cts} +2 -8
  21. package/dist/index-DMh3OYSb.d.cts.map +1 -0
  22. package/dist/{index-WBHSrIUU.d.ts → index-HXvg0YRQ.d.ts} +5 -12
  23. package/dist/index-HXvg0YRQ.d.ts.map +1 -0
  24. package/dist/{index-CFRgZLhH.d.cts → index-fZ67k-WR.d.cts} +5 -12
  25. package/dist/index-fZ67k-WR.d.cts.map +1 -0
  26. package/dist/{index-DeD8EI1D.d.ts → index-g-EWIugp.d.ts} +2 -42
  27. package/dist/index-g-EWIugp.d.ts.map +1 -0
  28. package/dist/{index-Uh1u57yq.d.cts → index-oST8GBFa.d.cts} +2 -42
  29. package/dist/index-oST8GBFa.d.cts.map +1 -0
  30. package/dist/index.cjs +287 -20
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +227 -71
  33. package/dist/index.d.cts.map +1 -1
  34. package/dist/index.d.ts +227 -71
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +276 -21
  37. package/dist/index.js.map +1 -1
  38. package/dist/{item-group-BbvvH_Ob.js → item-group-BceUoxoM.js} +4 -6
  39. package/dist/{item-group-BbvvH_Ob.js.map → item-group-BceUoxoM.js.map} +1 -1
  40. package/dist/{item-group-CQRbb8Rx.cjs → item-group-KV4hYu3a.cjs} +4 -6
  41. package/dist/{item-group-CQRbb8Rx.cjs.map → item-group-KV4hYu3a.cjs.map} +1 -1
  42. package/dist/{registry-Ih5n4PVZ.cjs → registry-Cymq7Ah0.cjs} +97 -92
  43. package/dist/registry-Cymq7Ah0.cjs.map +1 -0
  44. package/dist/{registry-B23o9UWh.js → registry-tkacvJUI.js} +79 -91
  45. package/dist/registry-tkacvJUI.js.map +1 -0
  46. package/dist/{schemas-yId-bJhD.js → schemas-DM8sN_f0.js} +5 -7
  47. package/dist/{schemas-yId-bJhD.js.map → schemas-DM8sN_f0.js.map} +1 -1
  48. package/dist/{schemas-Q2dyOgc2.cjs → schemas-wDzbKdKa.cjs} +5 -7
  49. package/dist/{schemas-Q2dyOgc2.cjs.map → schemas-wDzbKdKa.cjs.map} +1 -1
  50. package/dist/schemas.cjs +3 -3
  51. package/dist/schemas.d.cts +1 -1
  52. package/dist/schemas.d.ts +1 -1
  53. package/dist/schemas.js +3 -3
  54. package/dist/{scoring-CaPS6q8b.cjs → scoring-D9tFvPEG.cjs} +22 -42
  55. package/dist/{scoring-CaPS6q8b.cjs.map → scoring-D9tFvPEG.cjs.map} +1 -1
  56. package/dist/{scoring-CLMdk8i9.js → scoring-DIBmo_cy.js} +22 -42
  57. package/dist/{scoring-CLMdk8i9.js.map → scoring-DIBmo_cy.js.map} +1 -1
  58. package/dist/scoring.cjs +2 -2
  59. package/dist/scoring.d.cts +2 -2
  60. package/dist/scoring.d.ts +2 -2
  61. package/dist/scoring.js +2 -2
  62. package/dist/{xapi-BX0hvRRB.cjs → xapi-Dyqcl3JK.cjs} +5 -8
  63. package/dist/{xapi-BX0hvRRB.cjs.map → xapi-Dyqcl3JK.cjs.map} +1 -1
  64. package/dist/{xapi-Cdk9fTvV.js → xapi-gCAo549Q.js} +5 -8
  65. package/dist/{xapi-Cdk9fTvV.js.map → xapi-gCAo549Q.js.map} +1 -1
  66. package/dist/xapi.cjs +1 -1
  67. package/dist/xapi.d.cts +1 -1
  68. package/dist/xapi.d.ts +1 -1
  69. package/dist/xapi.js +1 -1
  70. package/package.json +2 -2
  71. package/vectors/README.md +23 -0
  72. package/vectors/variants.d.mts +20 -0
  73. package/vectors/variants.json +5753 -0
  74. package/vectors/variants.mjs +67 -0
  75. package/dist/ai-i3jwXnpX.d.cts.map +0 -1
  76. package/dist/ai-i3jwXnpX.d.ts.map +0 -1
  77. package/dist/ai-writing-C4YRVrFi.cjs.map +0 -1
  78. package/dist/ai-writing-OJ5m_1EB.js.map +0 -1
  79. package/dist/index--U7hHV0s.d.cts.map +0 -1
  80. package/dist/index-CFRgZLhH.d.cts.map +0 -1
  81. package/dist/index-DeD8EI1D.d.ts.map +0 -1
  82. package/dist/index-Dj92T76o.d.ts.map +0 -1
  83. package/dist/index-Uh1u57yq.d.cts.map +0 -1
  84. package/dist/index-WBHSrIUU.d.ts.map +0 -1
  85. package/dist/registry-B23o9UWh.js.map +0 -1
  86. package/dist/registry-Ih5n4PVZ.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_registry = require("./registry-Ih5n4PVZ.cjs");
3
- const require_ai_writing = require("./ai-writing-C4YRVrFi.cjs");
4
- const require_scoring = require("./scoring-CaPS6q8b.cjs");
5
- const require_item_group = require("./item-group-CQRbb8Rx.cjs");
6
- const require_schemas = require("./schemas-Q2dyOgc2.cjs");
7
- const require_xapi = require("./xapi-BX0hvRRB.cjs");
8
- //#region src/delivery.ts
2
+ const require_registry = require("./registry-Cymq7Ah0.cjs");
3
+ const require_ai_writing = require("./ai-writing-Bh2Y53cY.cjs");
4
+ const require_scoring = require("./scoring-D9tFvPEG.cjs");
5
+ const require_item_group = require("./item-group-KV4hYu3a.cjs");
6
+ const require_schemas = require("./schemas-wDzbKdKa.cjs");
7
+ const require_xapi = require("./xapi-Dyqcl3JK.cjs");
8
+ // src/delivery.ts
9
9
  /**
10
10
  * The policy that restricts nothing: every setting `true`. It is what an absent
11
11
  * policy resolves to, and it is exactly the behaviour the SDK had before
@@ -159,8 +159,7 @@ function validateDeliveryPolicy(policy) {
159
159
  data: given
160
160
  };
161
161
  }
162
- //#endregion
163
- //#region src/item-group.ts
162
+ // src/item-group.ts
164
163
  /** Narrows a sequence entry to an item group. */
165
164
  function isItemGroup(entry) {
166
165
  return entry.type === "item-group";
@@ -284,8 +283,7 @@ function flattenSequence(entries, options = {}) {
284
283
  }
285
284
  return slots;
286
285
  }
287
- //#endregion
288
- //#region src/media-budget.ts
286
+ // src/media-budget.ts
289
287
  /**
290
288
  * The authored-entry prefix of a slot id: `"3"` for both `"3"` and `"3.1"`.
291
289
  *
@@ -399,8 +397,7 @@ function restoreMediaPlayLedger(plan, stored) {
399
397
  if (stored.planHash !== plan.planHash) throw new Error(`restoreMediaPlayLedger: this ledger belongs to a different paper (ledger ${stored.planHash}, plan ${plan.planHash}). Restoring it would hold the learner to a play budget from an exam they never sat.`);
400
398
  return serializeMediaPlayLedger(plan, stored.entries, { ...stored.savedAt !== void 0 ? { savedAt: stored.savedAt } : {} });
401
399
  }
402
- //#endregion
403
- //#region src/attempt-plan.ts
400
+ // src/attempt-plan.ts
404
401
  /**
405
402
  * The activity as CONTENT, with its slot key removed.
406
403
  *
@@ -651,8 +648,7 @@ function scoredItemsFromPlan(plan, outcomes, options = {}) {
651
648
  outcome: Object.hasOwn(outcomes, slot.slotId) ? outcomes[slot.slotId] : missingOutcome(slot, policy)
652
649
  }));
653
650
  }
654
- //#endregion
655
- //#region src/attempt-state.ts
651
+ // src/attempt-state.ts
656
652
  /**
657
653
  * A DEEP copy of the responses.
658
654
  *
@@ -776,8 +772,7 @@ function diffResponses(before, after) {
776
772
  }
777
773
  return entries;
778
774
  }
779
- //#endregion
780
- //#region src/redact.ts
775
+ // src/redact.ts
781
776
  /** Merges per-call overrides over a registered policy, one level deep. */
782
777
  function mergePolicy(base, overrides) {
783
778
  if (overrides === void 0) return base;
@@ -1044,8 +1039,7 @@ function withoutAuthorOnly(entry) {
1044
1039
  ...Array.isArray(items) ? { items: items.map((item) => withoutAuthorOnlyValue(item, policyOf(item?.type))) } : items !== void 0 ? { items } : {}
1045
1040
  };
1046
1041
  }
1047
- //#endregion
1048
- //#region src/timeline.ts
1042
+ // src/timeline.ts
1049
1043
  /**
1050
1044
  * Reads a stored {@link MediaProgress}. Never throws: anything that is not one
1051
1045
  * — a string, a stale shape, an object whose getters throw — answers `null`,
@@ -1099,7 +1093,268 @@ function composeTimelineScore(entryKey, items, policy) {
1099
1093
  items: own
1100
1094
  }], policy);
1101
1095
  }
1102
- //#endregion
1096
+ // src/versions.ts
1097
+ /**
1098
+ * Every code `critiqueVersions` reports, and its severity: a contract, as the
1099
+ * critic's codes are. `versions_shared_media` is `advice` for a picture both
1100
+ * forms may show, and a `warning` otherwise.
1101
+ */
1102
+ const VERSION_FINDING_SEVERITY = {
1103
+ versions_section_mismatch: "invalid",
1104
+ versions_entry_count: "invalid",
1105
+ versions_type_mismatch: "invalid",
1106
+ versions_structure_mismatch: "invalid",
1107
+ versions_objectives_mismatch: "warning",
1108
+ versions_length_out_of_band: "warning",
1109
+ versions_near_copy: "warning",
1110
+ versions_same_key: "warning",
1111
+ versions_shared_media: "warning"
1112
+ };
1113
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1114
+ const listOf = (value) => Array.isArray(value) ? value : [];
1115
+ const stringOf = (value) => typeof value === "string" ? value : "";
1116
+ const key = (path) => path.join(".");
1117
+ function report(code, message, a, b, severity = VERSION_FINDING_SEVERITY[code]) {
1118
+ return {
1119
+ path: [...b ?? a ?? []],
1120
+ message,
1121
+ code,
1122
+ severity,
1123
+ ...a !== void 0 ? { a: [...a] } : {},
1124
+ ...b !== void 0 ? { b: [...b] } : {}
1125
+ };
1126
+ }
1127
+ function entriesOf(paper) {
1128
+ return listOf(paper?.sections).flatMap((section, s) => listOf(isRecord(section) ? section.entries : void 0).flatMap((value, index) => isRecord(value) ? [{
1129
+ path: [
1130
+ "sections",
1131
+ String(s),
1132
+ "entries",
1133
+ String(index)
1134
+ ],
1135
+ section: s,
1136
+ index,
1137
+ value
1138
+ }] : []));
1139
+ }
1140
+ /** A group's items, or the entry itself: the slots a learner answers. */
1141
+ const itemsOf = (entry) => entry.value.type === "item-group" ? listOf(entry.value.items).flatMap((item, at) => isRecord(item) ? [{
1142
+ path: [
1143
+ ...entry.path,
1144
+ "items",
1145
+ String(at)
1146
+ ],
1147
+ value: item
1148
+ }] : []) : [{
1149
+ path: entry.path,
1150
+ value: entry.value
1151
+ }];
1152
+ const typeName = (value) => stringOf(value.type) || "no type";
1153
+ function pairEntries(aEntries, bEntries, given) {
1154
+ const pairs = [];
1155
+ const takenA = /* @__PURE__ */ new Set();
1156
+ const takenB = /* @__PURE__ */ new Set();
1157
+ const add = (a, b, by) => {
1158
+ pairs.push({
1159
+ a,
1160
+ b,
1161
+ by
1162
+ });
1163
+ takenA.add(key(a.path));
1164
+ takenB.add(key(b.path));
1165
+ };
1166
+ const aByPath = new Map(aEntries.map((one) => [key(one.path), one]));
1167
+ const bByPath = new Map(bEntries.map((one) => [key(one.path), one]));
1168
+ for (const pair of given ?? []) {
1169
+ const a = aByPath.get(key(pair.a.map(String)));
1170
+ const b = bByPath.get(key(pair.b.map(String)));
1171
+ if (a !== void 0 && b !== void 0 && !takenA.has(key(a.path)) && !takenB.has(key(b.path))) add(a, b, "pairs");
1172
+ }
1173
+ for (const a of aEntries) {
1174
+ const slotKey = a.value.slotKey;
1175
+ if (takenA.has(key(a.path)) || typeof slotKey !== "string") continue;
1176
+ const b = bEntries.find((one) => one.value.slotKey === slotKey && !takenB.has(key(one.path)));
1177
+ if (b !== void 0) add(a, b, "slotKey");
1178
+ }
1179
+ for (const a of aEntries) {
1180
+ if (takenA.has(key(a.path))) continue;
1181
+ const b = bEntries.find((one) => one.section === a.section && one.index === a.index && !takenB.has(key(one.path)));
1182
+ if (b !== void 0) add(a, b, "position");
1183
+ }
1184
+ return pairs;
1185
+ }
1186
+ /** A paired group's items, by their own `slotKey` when both have one, else by position. */
1187
+ function pairItems(pair) {
1188
+ const aItems = itemsOf(pair.a);
1189
+ const bItems = itemsOf(pair.b);
1190
+ if (pair.a.value.type !== "item-group" || pair.b.value.type !== "item-group") {
1191
+ const one = aItems[0];
1192
+ const two = bItems[0];
1193
+ return one !== void 0 && two !== void 0 ? [{
1194
+ a: one,
1195
+ b: two,
1196
+ by: pair.by
1197
+ }] : [];
1198
+ }
1199
+ const taken = /* @__PURE__ */ new Set();
1200
+ const pairs = [];
1201
+ aItems.forEach((a) => {
1202
+ const slotKey = a.value.slotKey;
1203
+ const found = typeof slotKey === "string" ? bItems.findIndex((b, index) => b.value.slotKey === slotKey && !taken.has(index)) : -1;
1204
+ if (found !== -1) {
1205
+ taken.add(found);
1206
+ pairs.push({
1207
+ a,
1208
+ b: bItems[found],
1209
+ by: "slotKey"
1210
+ });
1211
+ }
1212
+ });
1213
+ aItems.forEach((a, at) => {
1214
+ if (pairs.some((one) => one.a === a) || at >= bItems.length || taken.has(at)) return;
1215
+ taken.add(at);
1216
+ pairs.push({
1217
+ a,
1218
+ b: bItems[at],
1219
+ by: "position"
1220
+ });
1221
+ });
1222
+ return pairs.sort((x, y) => aItems.indexOf(x.a) - aItems.indexOf(y.a));
1223
+ }
1224
+ const objectives = (value) => [...listOf(value.learningObjectives).filter((one) => typeof one === "string")].sort().join("\n");
1225
+ /**
1226
+ * Compares two forms of an exam slot by slot, and the whole of one with the
1227
+ * whole of the other: whether they are parallel — the same blueprint, types,
1228
+ * structure and scoring (`invalid` when not) — and whether a learner who sat
1229
+ * one would meet the other again: a near copy anywhere in the other form, a
1230
+ * shared right answer in a paired slot, the same recording (`warning`), or
1231
+ * the same picture (`advice` while `allowSharedImages`, its default).
1232
+ * Objectives and lengths that differ are warnings.
1233
+ *
1234
+ * Slots are paired by `pairs` (paths from each paper's root, as
1235
+ * `generateVariants`' pairs give them once a host places them), then by
1236
+ * `slotKey`, then by position: the same entry of the same section. A paired
1237
+ * group's items pair by their own `slotKey`, then by position.
1238
+ *
1239
+ * It reads content as stored, finished or not, and never throws.
1240
+ */
1241
+ function critiqueVersions(a, b, options = {}) {
1242
+ const findings = [];
1243
+ const aSections = listOf(a?.sections);
1244
+ const bSections = listOf(b?.sections);
1245
+ if (aSections.length !== bSections.length) findings.push(report("versions_section_mismatch", `The second form has ${bSections.length} sections; the first has ${aSections.length}.`, void 0, void 0));
1246
+ for (let s = 0; s < Math.min(aSections.length, bSections.length); s += 1) {
1247
+ const one = isRecord(aSections[s]) ? aSections[s] : {};
1248
+ const two = isRecord(bSections[s]) ? bSections[s] : {};
1249
+ const at = ["sections", String(s)];
1250
+ if (one.key !== two.key || one.title !== two.title) findings.push(report("versions_section_mismatch", `Section ${s + 1} is "${stringOf(two.title) || stringOf(two.key)}" in the second form and "${stringOf(one.title) || stringOf(one.key)}" in the first.`, at, at));
1251
+ const aCount = listOf(one.entries).length;
1252
+ const bCount = listOf(two.entries).length;
1253
+ if (aCount !== bCount) findings.push(report("versions_entry_count", `Section ${s + 1} has ${bCount} entries in the second form and ${aCount} in the first.`, [...at, "entries"], [...at, "entries"]));
1254
+ }
1255
+ const aEntries = entriesOf(a);
1256
+ const bEntries = entriesOf(b);
1257
+ const pairs = pairEntries(aEntries, bEntries, options.pairs);
1258
+ let structureMismatches = 0;
1259
+ let paired = 0;
1260
+ const slots = [];
1261
+ for (const pair of pairs) {
1262
+ slots.push({
1263
+ a: pair.a.path,
1264
+ b: pair.b.path,
1265
+ by: pair.by
1266
+ });
1267
+ if (pair.a.value.type !== pair.b.value.type) {
1268
+ structureMismatches += 1;
1269
+ findings.push(report("versions_type_mismatch", `The second form has ${typeName(pair.b.value)} here; the first has ${typeName(pair.a.value)}.`, pair.a.path, pair.b.path));
1270
+ continue;
1271
+ }
1272
+ const misses = require_ai_writing.structureMisses(pair.a.value, pair.b.value);
1273
+ if (misses.length > 0) structureMismatches += 1;
1274
+ for (const miss of misses) {
1275
+ const code = miss.path[miss.path.length - 1] === "type" ? "versions_type_mismatch" : miss.path.length === 1 && miss.path[0] === "items" ? "versions_entry_count" : "versions_structure_mismatch";
1276
+ findings.push(report(code, miss.message, [...pair.a.path, ...miss.path], [...pair.b.path, ...miss.path]));
1277
+ }
1278
+ for (const miss of require_ai_writing.lengthMisses(pair.a.value, pair.b.value, options.lengthTolerance)) findings.push(report("versions_length_out_of_band", `${miss.words} words; the first form's has ${miss.exemplarWords}, so ${miss.bounds[0]} to ${miss.bounds[1]}.`, [...pair.a.path, ...miss.path], [...pair.b.path, ...miss.path]));
1279
+ const items = pairItems(pair);
1280
+ if (pair.a.value.type === "item-group") for (const item of items) slots.push({
1281
+ a: item.a.path,
1282
+ b: item.b.path,
1283
+ by: item.by
1284
+ });
1285
+ for (const item of items) {
1286
+ if (item.a.value.type !== item.b.value.type) continue;
1287
+ paired += 1;
1288
+ if ((item.a.value.learningObjectives !== void 0 || item.b.value.learningObjectives !== void 0) && objectives(item.a.value) !== objectives(item.b.value)) findings.push(report("versions_objectives_mismatch", "The two forms test different learning objectives here.", item.a.path, item.b.path));
1289
+ const aKeys = require_ai_writing.keysOf(item.a.value);
1290
+ for (const slot of require_ai_writing.keysOf(item.b.value)) {
1291
+ const match = aKeys.find((one) => one.slot === slot.slot);
1292
+ const shared = match !== void 0 ? require_ai_writing.sharedAnswers(slot, match) : [];
1293
+ if (shared.length > 0) findings.push(report("versions_same_key", `The right answer "${shared[0]}" is the same in both forms: a learner who sat the first scores again by memory.`, [...item.a.path, ...match.path], [...item.b.path, ...slot.path]));
1294
+ }
1295
+ }
1296
+ }
1297
+ const pairedA = new Set(slots.flatMap((slot) => slot.a !== null ? [key(slot.a)] : []));
1298
+ const pairedB = new Set(slots.flatMap((slot) => slot.b !== null ? [key(slot.b)] : []));
1299
+ for (const entry of aEntries) for (const one of entry.value.type === "item-group" ? [{ path: entry.path }, ...itemsOf(entry)] : [entry]) if (!pairedA.has(key(one.path))) slots.push({
1300
+ a: one.path,
1301
+ b: null,
1302
+ by: "position"
1303
+ });
1304
+ for (const entry of bEntries) for (const one of entry.value.type === "item-group" ? [{ path: entry.path }, ...itemsOf(entry)] : [entry]) if (!pairedB.has(key(one.path))) slots.push({
1305
+ a: null,
1306
+ b: one.path,
1307
+ by: "position"
1308
+ });
1309
+ const aTexts = aEntries.flatMap((entry) => require_ai_writing.textsOf(entry.value).map((text) => ({
1310
+ ...text,
1311
+ path: [...entry.path, ...text.path]
1312
+ })));
1313
+ for (const entry of bEntries) for (const text of require_ai_writing.textsOf(entry.value)) {
1314
+ let best;
1315
+ for (const other of aTexts) {
1316
+ if (other.kind !== text.kind) continue;
1317
+ const score = require_ai_writing.textSimilarity(other.text, text.text);
1318
+ if (score >= .5 && (best === void 0 || score > best.score)) best = {
1319
+ score,
1320
+ path: other.path
1321
+ };
1322
+ }
1323
+ if (best !== void 0) findings.push(report("versions_near_copy", `This reads like the first form's text here (similarity ${require_ai_writing.shownScore(best.score)}).`, best.path, [...entry.path, ...text.path]));
1324
+ }
1325
+ const aOptions = aEntries.flatMap((entry) => itemsOf(entry).flatMap((item) => {
1326
+ const set = require_ai_writing.optionSet(item.value);
1327
+ return set !== null ? [{
1328
+ path: item.path,
1329
+ set: set.join("\n")
1330
+ }] : [];
1331
+ }));
1332
+ for (const entry of bEntries) for (const item of itemsOf(entry)) {
1333
+ const set = require_ai_writing.optionSet(item.value);
1334
+ const match = set !== null ? aOptions.find((other) => other.set === set.join("\n")) : void 0;
1335
+ if (match !== void 0) findings.push(report("versions_near_copy", "These options are the first form's, in another order.", [...match.path, "options"], [...item.path, "options"]));
1336
+ }
1337
+ const allowImages = options.allowSharedImages !== false;
1338
+ const aFiles = /* @__PURE__ */ new Map();
1339
+ for (const entry of aEntries) for (const ref of require_ai_writing.listMediaRefs(entry.value)) if (ref.url !== null && ref.url !== "" && !aFiles.has(ref.url)) aFiles.set(ref.url, [...entry.path, ...ref.path.map(String)]);
1340
+ for (const entry of bEntries) for (const ref of require_ai_writing.listMediaRefs(entry.value)) {
1341
+ const first = ref.url !== null ? aFiles.get(ref.url) : void 0;
1342
+ if (first === void 0) continue;
1343
+ const picture = ref.kind === "image";
1344
+ findings.push(report("versions_shared_media", picture ? "Both forms show this picture." : "Both forms use this file: a learner who sat the first hears or sees it again.", first, [...entry.path, ...ref.path.map(String)], picture && allowImages ? "advice" : "warning"));
1345
+ }
1346
+ const count = (entries) => entries.reduce((sum, entry) => sum + itemsOf(entry).length, 0);
1347
+ return {
1348
+ findings,
1349
+ slots,
1350
+ summary: {
1351
+ aItems: count(aEntries),
1352
+ bItems: count(bEntries),
1353
+ paired,
1354
+ structureMismatches
1355
+ }
1356
+ };
1357
+ }
1103
1358
  exports.AI_COACHING_MAX_TIP_LENGTH = require_ai_writing.AI_COACHING_MAX_TIP_LENGTH;
1104
1359
  exports.AI_COACHING_MAX_WORDS = require_ai_writing.AI_COACHING_MAX_WORDS;
1105
1360
  exports.AI_CRITIQUE_MAX_FIELD_LENGTH = require_ai_writing.AI_CRITIQUE_MAX_FIELD_LENGTH;
@@ -1111,6 +1366,12 @@ exports.AI_GRADING_PROBABILITY_TOLERANCE = require_ai_writing.AI_GRADING_PROBABI
1111
1366
  exports.AI_HINT_TYPES = require_ai_writing.AI_HINT_TYPES;
1112
1367
  exports.AI_MATERIAL_MAX_LENGTH = require_ai_writing.AI_MATERIAL_MAX_LENGTH;
1113
1368
  exports.AI_TEXT_MAX_LENGTH = require_ai_writing.AI_TEXT_MAX_LENGTH;
1369
+ exports.AI_VARIANTS_MAX_EXEMPLARS = require_ai_writing.AI_VARIANTS_MAX_EXEMPLARS;
1370
+ exports.AI_VARIANTS_MAX_MATERIAL_LENGTH = require_ai_writing.AI_VARIANTS_MAX_MATERIAL_LENGTH;
1371
+ exports.AI_VARIANTS_MAX_REPAIRS = require_ai_writing.AI_VARIANTS_MAX_REPAIRS;
1372
+ exports.AI_VARIANT_CODE_SEVERITY = require_ai_writing.AI_VARIANT_CODE_SEVERITY;
1373
+ exports.AI_VARIANT_LENGTH_BANDS = require_ai_writing.AI_VARIANT_LENGTH_BANDS;
1374
+ exports.AI_VARIANT_NEAR_COPY_THRESHOLD = require_ai_writing.AI_VARIANT_NEAR_COPY_THRESHOLD;
1114
1375
  exports.AI_WRITING_MAX_CORRECTIONS = require_ai_writing.AI_WRITING_MAX_CORRECTIONS;
1115
1376
  exports.AI_WRITING_MAX_FIELD_LENGTH = require_ai_writing.AI_WRITING_MAX_FIELD_LENGTH;
1116
1377
  exports.ActivitySchemaError = require_registry.ActivitySchemaError;
@@ -1138,6 +1399,7 @@ exports.TIMELINE_MAX_ITEMS = require_item_group.TIMELINE_MAX_ITEMS;
1138
1399
  exports.TIMELINE_MAX_QUIZZES = require_item_group.TIMELINE_MAX_QUIZZES;
1139
1400
  exports.TIMELINE_MAX_TITLE_LENGTH = require_item_group.TIMELINE_MAX_TITLE_LENGTH;
1140
1401
  exports.UnknownActivityTypeError = require_registry.UnknownActivityTypeError;
1402
+ exports.VERSION_FINDING_SEVERITY = VERSION_FINDING_SEVERITY;
1141
1403
  exports.XAPIVerb = require_xapi.XAPIVerb;
1142
1404
  exports.XAPI_VERB_DISPLAY = require_xapi.XAPI_VERB_DISPLAY;
1143
1405
  exports.aiAllowedByContent = require_ai_writing.aiAllowedByContent;
@@ -1150,6 +1412,8 @@ exports.aiGradeOf = require_ai_writing.aiGradeOf;
1150
1412
  exports.aiGradingRequest = require_ai_writing.aiGradingRequest;
1151
1413
  exports.aiHintRequest = require_ai_writing.aiHintRequest;
1152
1414
  exports.aiSupports = require_ai_writing.aiSupports;
1415
+ exports.aiVariantsRepairRequest = require_ai_writing.aiVariantsRepairRequest;
1416
+ exports.aiVariantsRequest = require_ai_writing.aiVariantsRequest;
1153
1417
  exports.aiWritingFeedbackRequest = require_ai_writing.aiWritingFeedbackRequest;
1154
1418
  exports.alignDictation = require_registry.alignDictation;
1155
1419
  exports.alignReadAloud = require_scoring.alignReadAloud;
@@ -1164,6 +1428,7 @@ exports.checkAiDrafts = require_ai_writing.checkAiDrafts;
1164
1428
  exports.checkAiExplanation = require_ai_writing.checkAiExplanation;
1165
1429
  exports.checkAiGrading = require_ai_writing.checkAiGrading;
1166
1430
  exports.checkAiHint = require_ai_writing.checkAiHint;
1431
+ exports.checkAiVariants = require_ai_writing.checkAiVariants;
1167
1432
  exports.checkAiWritingFeedback = require_ai_writing.checkAiWritingFeedback;
1168
1433
  exports.classifyBand = require_scoring.classifyBand;
1169
1434
  exports.combineDeliveryPolicies = combineDeliveryPolicies;
@@ -1179,6 +1444,7 @@ exports.createItemGroupDraft = require_ai_writing.createItemGroupDraft;
1179
1444
  exports.critiqueDraft = require_ai_writing.critiqueDraft;
1180
1445
  exports.critiqueDrafts = require_ai_writing.critiqueDrafts;
1181
1446
  exports.critiqueItemGroupDraft = require_ai_writing.critiqueItemGroupDraft;
1447
+ exports.critiqueVersions = critiqueVersions;
1182
1448
  exports.defineActivityType = require_registry.defineActivityType;
1183
1449
  exports.dictationJsonSchema = require_schemas.dictationJsonSchema;
1184
1450
  exports.dictationReferenceWords = require_registry.dictationReferenceWords;
@@ -1195,6 +1461,7 @@ exports.flattenSequence = flattenSequence;
1195
1461
  exports.gapSelectJsonSchema = require_schemas.gapSelectJsonSchema;
1196
1462
  exports.gapSelectType = require_registry.gapSelectType;
1197
1463
  exports.generateDrafts = require_ai_writing.generateDrafts;
1464
+ exports.generateVariants = require_ai_writing.generateVariants;
1198
1465
  exports.getActivityTypeDescriptor = require_registry.getActivityTypeDescriptor;
1199
1466
  exports.gradeFromJudgement = require_scoring.gradeFromJudgement;
1200
1467
  exports.gradeFromRubric = require_scoring.gradeFromRubric;