@vue-skuilder/db 0.1.17 → 0.1.20

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 (92) hide show
  1. package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CZdMBiTU.d.ts} +427 -104
  2. package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-PxDZTky3.d.cts} +427 -104
  3. package/dist/core/index.d.cts +304 -0
  4. package/dist/core/index.d.ts +237 -25
  5. package/dist/core/index.js +2246 -118
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +2235 -114
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D0MoZMjH.d.cts} +1 -1
  10. package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-D8o6ZnKW.d.ts} +1 -1
  11. package/dist/impl/couch/{index.d.mts → index.d.cts} +47 -5
  12. package/dist/impl/couch/index.d.ts +46 -4
  13. package/dist/impl/couch/index.js +2250 -134
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +2212 -97
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/{index.d.mts → index.d.cts} +6 -6
  18. package/dist/impl/static/index.d.ts +5 -5
  19. package/dist/impl/static/index.js +1950 -143
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +1922 -117
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/{index-Bmll7Xse.d.mts → index-B_j6u5E4.d.cts} +1 -1
  24. package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
  25. package/dist/{index.d.mts → index.d.cts} +97 -13
  26. package/dist/index.d.ts +96 -12
  27. package/dist/index.js +2439 -180
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +2386 -135
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/pouch/index.js +3 -3
  32. package/dist/{types-Dbp5DaRR.d.mts → types-Bn0itutr.d.cts} +1 -1
  33. package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
  34. package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.cts} +3 -1
  35. package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-DDY4N-Uq.d.ts} +3 -1
  36. package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
  37. package/dist/util/packer/index.d.ts +3 -3
  38. package/dist/util/packer/index.js.map +1 -1
  39. package/dist/util/packer/index.mjs.map +1 -1
  40. package/docs/brainstorm-navigation-paradigm.md +369 -0
  41. package/docs/navigators-architecture.md +370 -0
  42. package/docs/todo-evolutionary-orchestration.md +310 -0
  43. package/docs/todo-nominal-tag-types.md +121 -0
  44. package/docs/todo-strategy-authoring.md +401 -0
  45. package/eslint.config.mjs +1 -1
  46. package/package.json +9 -4
  47. package/src/core/index.ts +1 -0
  48. package/src/core/interfaces/contentSource.ts +88 -4
  49. package/src/core/interfaces/courseDB.ts +13 -0
  50. package/src/core/interfaces/navigationStrategyManager.ts +0 -5
  51. package/src/core/interfaces/userDB.ts +32 -0
  52. package/src/core/navigators/CompositeGenerator.ts +268 -0
  53. package/src/core/navigators/Pipeline.ts +318 -0
  54. package/src/core/navigators/PipelineAssembler.ts +194 -0
  55. package/src/core/navigators/elo.ts +104 -15
  56. package/src/core/navigators/filters/eloDistance.ts +132 -0
  57. package/src/core/navigators/filters/index.ts +9 -0
  58. package/src/core/navigators/filters/types.ts +115 -0
  59. package/src/core/navigators/filters/userTagPreference.ts +232 -0
  60. package/src/core/navigators/generators/index.ts +2 -0
  61. package/src/core/navigators/generators/types.ts +107 -0
  62. package/src/core/navigators/hardcodedOrder.ts +111 -12
  63. package/src/core/navigators/hierarchyDefinition.ts +266 -0
  64. package/src/core/navigators/index.ts +404 -3
  65. package/src/core/navigators/inferredPreference.ts +107 -0
  66. package/src/core/navigators/interferenceMitigator.ts +355 -0
  67. package/src/core/navigators/relativePriority.ts +255 -0
  68. package/src/core/navigators/srs.ts +195 -0
  69. package/src/core/navigators/userGoal.ts +136 -0
  70. package/src/core/types/strategyState.ts +84 -0
  71. package/src/core/types/types-legacy.ts +2 -0
  72. package/src/impl/common/BaseUserDB.ts +74 -7
  73. package/src/impl/couch/adminDB.ts +1 -2
  74. package/src/impl/couch/classroomDB.ts +51 -0
  75. package/src/impl/couch/courseDB.ts +147 -49
  76. package/src/impl/static/courseDB.ts +11 -4
  77. package/src/study/SessionController.ts +149 -1
  78. package/src/study/TagFilteredContentSource.ts +255 -0
  79. package/src/study/index.ts +1 -0
  80. package/src/util/dataDirectory.test.ts +51 -22
  81. package/src/util/logger.ts +0 -1
  82. package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
  83. package/tests/core/navigators/Pipeline.test.ts +406 -0
  84. package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
  85. package/tests/core/navigators/SRSNavigator.test.ts +344 -0
  86. package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
  87. package/tests/core/navigators/navigators.test.ts +710 -0
  88. package/tsconfig.json +1 -1
  89. package/vitest.config.ts +29 -0
  90. package/dist/core/index.d.mts +0 -92
  91. /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
  92. /package/dist/pouch/{index.d.mts → index.d.cts} +0 -0
@@ -0,0 +1,107 @@
1
+ // ============================================================================
2
+ // INFERRED PREFERENCE NAVIGATOR — STUB
3
+ // ============================================================================
4
+ //
5
+ // STATUS: NOT IMPLEMENTED — This file documents architectural intent only.
6
+ //
7
+ // ============================================================================
8
+ //
9
+ // ## Purpose
10
+ //
11
+ // Inferred preferences are learned from user behavior, as opposed to explicit
12
+ // preferences which are configured via UI. The system observes patterns in
13
+ // user interactions and adjusts card selection accordingly.
14
+ //
15
+ // ## Inference Signals
16
+ //
17
+ // Potential signals to learn from:
18
+ //
19
+ // 1. **Card dismissal patterns**: User consistently skips certain card types
20
+ // 2. **Time-on-card**: User spends less time on certain content (boredom?)
21
+ // 3. **Error patterns**: User struggles with certain presentation styles
22
+ // 4. **Session timing**: User performs better at certain times of day
23
+ // 5. **Tag success rates**: User masters some tags faster than others
24
+ //
25
+ // ## Inferred State (Proposed)
26
+ //
27
+ // ```typescript
28
+ // interface InferredPreferenceState {
29
+ // // Learned tag affinities (positive = user does well, negative = struggles)
30
+ // tagAffinities: Record<string, number>;
31
+ //
32
+ // // Presentation style preferences
33
+ // preferredStyles: {
34
+ // visualVsText: number; // -1 to 1 (negative = text, positive = visual)
35
+ // shortVsLong: number; // -1 to 1 (negative = long, positive = short)
36
+ // };
37
+ //
38
+ // // Temporal patterns
39
+ // optimalSessionLength: number; // minutes
40
+ // optimalTimeOfDay: number; // hour (0-23)
41
+ //
42
+ // // Confidence in inferences
43
+ // sampleSize: number;
44
+ // lastUpdated: string;
45
+ // }
46
+ // ```
47
+ //
48
+ // ## Relationship to Explicit Preferences
49
+ //
50
+ // - Explicit preferences (UserTagPreferenceFilter) always take precedence
51
+ // - Inferred preferences act as soft suggestions when no explicit pref exists
52
+ // - User can "lock in" an inference as an explicit preference via UI
53
+ // - User can dismiss/override an inference ("I actually like text cards")
54
+ //
55
+ // ## Transparency Requirements
56
+ //
57
+ // Inferred preferences must be:
58
+ //
59
+ // 1. **Visible**: User can see what the system has inferred
60
+ // 2. **Explainable**: "We noticed you master visual cards faster"
61
+ // 3. **Overridable**: User can disable or invert any inference
62
+ // 4. **Forgettable**: User can reset inferences and start fresh
63
+ //
64
+ // ## Implementation Considerations
65
+ //
66
+ // 1. **Cold start**: Need minimum sample size before inferring
67
+ // 2. **Drift**: Preferences may change over time; use decay/recency weighting
68
+ // 3. **Privacy**: Inference data is personal; handle with care
69
+ // 4. **Bias**: Avoid reinforcing accidental patterns as permanent preferences
70
+ //
71
+ // ## Related Files
72
+ //
73
+ // - `filters/userTagPreference.ts` — Explicit preferences (takes precedence)
74
+ // - `userGoal.ts` — Goals (destination, not path)
75
+ // - `../types/strategyState.ts` — Storage mechanism
76
+ //
77
+ // ## Next Steps
78
+ //
79
+ // 1. Define minimum viable inference signals
80
+ // 2. Design inference algorithms (simple heuristics vs ML)
81
+ // 3. Build transparency UI ("Here's what we learned about you")
82
+ // 4. Implement override/dismiss mechanism
83
+ // 5. Add to card record collection for inference input
84
+ //
85
+ // ============================================================================
86
+
87
+ // Placeholder export to make this a valid module
88
+ export const INFERRED_PREFERENCE_NAVIGATOR_STUB = true;
89
+
90
+ /**
91
+ * @stub InferredPreferenceNavigator
92
+ *
93
+ * A navigator that learns user preferences from behavior patterns.
94
+ * See module-level documentation for architectural intent.
95
+ *
96
+ * NOT IMPLEMENTED — This is a design placeholder.
97
+ */
98
+ export interface InferredPreferenceState {
99
+ /** Learned affinity scores per tag (-1 to 1) */
100
+ tagAffinities: Record<string, number>;
101
+
102
+ /** Number of card interactions used to build inferences */
103
+ sampleSize: number;
104
+
105
+ /** ISO timestamp of last inference update */
106
+ updatedAt: string;
107
+ }
@@ -0,0 +1,355 @@
1
+ import type { ScheduledCard } from '../types/user';
2
+ import type { CourseDBInterface } from '../interfaces/courseDB';
3
+ import type { UserDBInterface } from '../interfaces/userDB';
4
+ import { ContentNavigator } from './index';
5
+ import type { WeightedCard } from './index';
6
+ import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
7
+ import type { StudySessionReviewItem, StudySessionNewItem } from '..';
8
+ import type { CardFilter, FilterContext } from './filters/types';
9
+ import { toCourseElo } from '@vue-skuilder/common';
10
+
11
+ /**
12
+ * Configuration for the InterferenceMitigator strategy.
13
+ *
14
+ * Course authors define explicit interference relationships between tags.
15
+ * The mitigator discourages introducing new concepts that interfere with
16
+ * currently immature learnings.
17
+ */
18
+ /**
19
+ * A single interference group with its own decay coefficient.
20
+ */
21
+ export interface InterferenceGroup {
22
+ /** Tags that interfere with each other in this group */
23
+ tags: string[];
24
+ /** How strongly these tags interfere (0-1, default: 0.8). Higher = stronger avoidance. */
25
+ decay?: number;
26
+ }
27
+
28
+ export interface InterferenceConfig {
29
+ /**
30
+ * Groups of tags that interfere with each other.
31
+ * Each group can have its own decay coefficient.
32
+ *
33
+ * Example: [
34
+ * { tags: ["b", "d", "p"], decay: 0.9 }, // visual similarity - strong
35
+ * { tags: ["d", "t"], decay: 0.7 }, // phonetic similarity - moderate
36
+ * { tags: ["m", "n"], decay: 0.8 }
37
+ * ]
38
+ */
39
+ interferenceSets: InterferenceGroup[];
40
+
41
+ /**
42
+ * Threshold below which a tag is considered "immature" (still being learned).
43
+ * Immature tags trigger interference avoidance for their interference partners.
44
+ */
45
+ maturityThreshold?: {
46
+ /** Minimum interaction count to be considered mature (default: 10) */
47
+ minCount?: number;
48
+ /** Minimum ELO score to be considered mature (optional) */
49
+ minElo?: number;
50
+ /**
51
+ * Minimum elapsed time (in days) since first interaction to be considered mature.
52
+ * Prevents recent cramming success from indicating maturity.
53
+ * The skill should be "lindy" — maintained over time.
54
+ */
55
+ minElapsedDays?: number;
56
+ };
57
+
58
+ /**
59
+ * Default decay for groups that don't specify their own (0-1, default: 0.8).
60
+ */
61
+ defaultDecay?: number;
62
+ }
63
+
64
+ const DEFAULT_MIN_COUNT = 10;
65
+ const DEFAULT_MIN_ELAPSED_DAYS = 3;
66
+ const DEFAULT_INTERFERENCE_DECAY = 0.8;
67
+
68
+ /**
69
+ * A filter strategy that avoids introducing confusable concepts simultaneously.
70
+ *
71
+ * When a user is learning a concept (tag is "immature"), this strategy reduces
72
+ * scores for cards tagged with interfering concepts. This encourages the system
73
+ * to introduce new content that is maximally distant from current learning focus.
74
+ *
75
+ * Example: While learning 'd', prefer introducing 'x' over 'b' (visual interference)
76
+ * or 't' (phonetic interference).
77
+ *
78
+ * Implements CardFilter for use in Pipeline architecture.
79
+ * Also extends ContentNavigator for backward compatibility.
80
+ */
81
+ export default class InterferenceMitigatorNavigator extends ContentNavigator implements CardFilter {
82
+ private config: InterferenceConfig;
83
+ private _strategyData: ContentNavigationStrategyData;
84
+
85
+ /** Human-readable name for CardFilter interface */
86
+ name: string;
87
+
88
+ /** Precomputed map: tag -> set of { partner, decay } it interferes with */
89
+ private interferenceMap: Map<string, Array<{ partner: string; decay: number }>>;
90
+
91
+ constructor(
92
+ user: UserDBInterface,
93
+ course: CourseDBInterface,
94
+ _strategyData: ContentNavigationStrategyData
95
+ ) {
96
+ super(user, course, _strategyData);
97
+ this._strategyData = _strategyData;
98
+ this.config = this.parseConfig(_strategyData.serializedData);
99
+ this.interferenceMap = this.buildInterferenceMap();
100
+ this.name = _strategyData.name || 'Interference Mitigator';
101
+ }
102
+
103
+ private parseConfig(serializedData: string): InterferenceConfig {
104
+ try {
105
+ const parsed = JSON.parse(serializedData);
106
+ // Normalize legacy format (string[][]) to new format (InterferenceGroup[])
107
+ let sets: InterferenceGroup[] = parsed.interferenceSets || [];
108
+ if (sets.length > 0 && Array.isArray(sets[0])) {
109
+ // Legacy format: convert string[][] to InterferenceGroup[]
110
+ sets = (sets as unknown as string[][]).map((tags) => ({ tags }));
111
+ }
112
+
113
+ return {
114
+ interferenceSets: sets,
115
+ maturityThreshold: {
116
+ minCount: parsed.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT,
117
+ minElo: parsed.maturityThreshold?.minElo,
118
+ minElapsedDays: parsed.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS,
119
+ },
120
+ defaultDecay: parsed.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY,
121
+ };
122
+ } catch {
123
+ return {
124
+ interferenceSets: [],
125
+ maturityThreshold: {
126
+ minCount: DEFAULT_MIN_COUNT,
127
+ minElapsedDays: DEFAULT_MIN_ELAPSED_DAYS,
128
+ },
129
+ defaultDecay: DEFAULT_INTERFERENCE_DECAY,
130
+ };
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Build a map from each tag to its interference partners with decay coefficients.
136
+ * If tags A, B, C are in an interference group with decay 0.8, then:
137
+ * - A interferes with B (decay 0.8) and C (decay 0.8)
138
+ * - B interferes with A (decay 0.8) and C (decay 0.8)
139
+ * - etc.
140
+ */
141
+ private buildInterferenceMap(): Map<string, Array<{ partner: string; decay: number }>> {
142
+ const map = new Map<string, Array<{ partner: string; decay: number }>>();
143
+
144
+ for (const group of this.config.interferenceSets) {
145
+ const decay = group.decay ?? this.config.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY;
146
+
147
+ for (const tag of group.tags) {
148
+ if (!map.has(tag)) {
149
+ map.set(tag, []);
150
+ }
151
+ const partners = map.get(tag)!;
152
+ for (const other of group.tags) {
153
+ if (other !== tag) {
154
+ // Check if partner already exists (from overlapping groups)
155
+ const existing = partners.find((p) => p.partner === other);
156
+ if (existing) {
157
+ // Use the stronger (higher) decay
158
+ existing.decay = Math.max(existing.decay, decay);
159
+ } else {
160
+ partners.push({ partner: other, decay });
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ return map;
168
+ }
169
+
170
+ /**
171
+ * Get the set of tags that are currently immature for this user.
172
+ * A tag is immature if the user has interacted with it but hasn't
173
+ * reached the maturity threshold.
174
+ */
175
+ private async getImmatureTags(context: FilterContext): Promise<Set<string>> {
176
+ const immature = new Set<string>();
177
+
178
+ try {
179
+ const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());
180
+ const userElo = toCourseElo(courseReg.elo);
181
+
182
+ const minCount = this.config.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT;
183
+ const minElo = this.config.maturityThreshold?.minElo;
184
+
185
+ // TODO: To properly check elapsed time, we need access to first interaction timestamp.
186
+ // For now, we use count as a proxy (more interactions = more time elapsed).
187
+ // Future: query card history for earliest timestamp per tag.
188
+ const minElapsedDays =
189
+ this.config.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS;
190
+ const minCountForElapsed = minElapsedDays * 2; // Rough proxy: ~2 interactions per day
191
+
192
+ for (const [tagId, tagElo] of Object.entries(userElo.tags)) {
193
+ // Only consider tags that have been started (count > 0)
194
+ if (tagElo.count === 0) continue;
195
+
196
+ // Check if below maturity threshold
197
+ const belowCount = tagElo.count < minCount;
198
+ const belowElo = minElo !== undefined && tagElo.score < minElo;
199
+ const belowElapsed = tagElo.count < minCountForElapsed; // Proxy for time
200
+
201
+ if (belowCount || belowElo || belowElapsed) {
202
+ immature.add(tagId);
203
+ }
204
+ }
205
+ } catch {
206
+ // If we can't get user data, assume no immature tags
207
+ }
208
+
209
+ return immature;
210
+ }
211
+
212
+ /**
213
+ * Get all tags that interfere with any immature tag, along with their decay coefficients.
214
+ * These are the tags we want to avoid introducing.
215
+ */
216
+ private getTagsToAvoid(immatureTags: Set<string>): Map<string, number> {
217
+ const avoid = new Map<string, number>();
218
+
219
+ for (const immatureTag of immatureTags) {
220
+ const partners = this.interferenceMap.get(immatureTag);
221
+ if (partners) {
222
+ for (const { partner, decay } of partners) {
223
+ // Avoid the partner, but not if it's also immature
224
+ // (if both are immature, we're already learning both)
225
+ if (!immatureTags.has(partner)) {
226
+ // Use the strongest (highest) decay if partner appears multiple times
227
+ const existing = avoid.get(partner) ?? 0;
228
+ avoid.set(partner, Math.max(existing, decay));
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ return avoid;
235
+ }
236
+
237
+ /**
238
+ * Compute interference score reduction for a card.
239
+ * Returns: { multiplier, interfering tags, reason }
240
+ */
241
+ private computeInterferenceEffect(
242
+ cardTags: string[],
243
+ tagsToAvoid: Map<string, number>,
244
+ immatureTags: Set<string>
245
+ ): { multiplier: number; interferingTags: string[]; reason: string } {
246
+ if (tagsToAvoid.size === 0) {
247
+ return {
248
+ multiplier: 1.0,
249
+ interferingTags: [],
250
+ reason: 'No interference detected',
251
+ };
252
+ }
253
+
254
+ let multiplier = 1.0;
255
+ const interferingTags: string[] = [];
256
+
257
+ for (const tag of cardTags) {
258
+ const decay = tagsToAvoid.get(tag);
259
+ if (decay !== undefined) {
260
+ interferingTags.push(tag);
261
+ multiplier *= 1.0 - decay;
262
+ }
263
+ }
264
+
265
+ if (interferingTags.length === 0) {
266
+ return {
267
+ multiplier: 1.0,
268
+ interferingTags: [],
269
+ reason: 'No interference detected',
270
+ };
271
+ }
272
+
273
+ // Find which immature tags these interfere with
274
+ const causingTags = new Set<string>();
275
+ for (const tag of interferingTags) {
276
+ for (const immatureTag of immatureTags) {
277
+ const partners = this.interferenceMap.get(immatureTag);
278
+ if (partners?.some((p) => p.partner === tag)) {
279
+ causingTags.add(immatureTag);
280
+ }
281
+ }
282
+ }
283
+
284
+ const reason = `Interferes with immature tags ${Array.from(causingTags).join(', ')} (tags: ${interferingTags.join(', ')}, multiplier: ${multiplier.toFixed(2)})`;
285
+
286
+ return { multiplier, interferingTags, reason };
287
+ }
288
+
289
+ /**
290
+ * CardFilter.transform implementation.
291
+ *
292
+ * Apply interference-aware scoring. Cards with tags that interfere with
293
+ * immature learnings get reduced scores.
294
+ */
295
+ async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {
296
+ // Identify what to avoid
297
+ const immatureTags = await this.getImmatureTags(context);
298
+ const tagsToAvoid = this.getTagsToAvoid(immatureTags);
299
+
300
+ // Adjust scores based on interference
301
+ const adjusted: WeightedCard[] = [];
302
+
303
+ for (const card of cards) {
304
+ const cardTags = card.tags ?? [];
305
+ const { multiplier, reason } = this.computeInterferenceEffect(
306
+ cardTags,
307
+ tagsToAvoid,
308
+ immatureTags
309
+ );
310
+ const finalScore = card.score * multiplier;
311
+
312
+ const action = multiplier < 1.0 ? 'penalized' : multiplier > 1.0 ? 'boosted' : 'passed';
313
+
314
+ adjusted.push({
315
+ ...card,
316
+ score: finalScore,
317
+ provenance: [
318
+ ...card.provenance,
319
+ {
320
+ strategy: 'interferenceMitigator',
321
+ strategyName: this.strategyName || this.name,
322
+ strategyId: this.strategyId || 'NAVIGATION_STRATEGY-interference',
323
+ action,
324
+ score: finalScore,
325
+ reason,
326
+ },
327
+ ],
328
+ });
329
+ }
330
+
331
+ return adjusted;
332
+ }
333
+
334
+ /**
335
+ * Legacy getWeightedCards - now throws as filters should not be used as generators.
336
+ *
337
+ * Use transform() via Pipeline instead.
338
+ */
339
+ async getWeightedCards(_limit: number): Promise<WeightedCard[]> {
340
+ throw new Error(
341
+ 'InterferenceMitigatorNavigator is a filter and should not be used as a generator. ' +
342
+ 'Use Pipeline with a generator and this filter via transform().'
343
+ );
344
+ }
345
+
346
+ // Legacy methods - stub implementations since filters don't generate cards
347
+
348
+ async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {
349
+ return [];
350
+ }
351
+
352
+ async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
353
+ return [];
354
+ }
355
+ }