@arclabs561/ai-visual-test 0.5.1

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 (93) hide show
  1. package/.secretsignore.example +20 -0
  2. package/CHANGELOG.md +360 -0
  3. package/CONTRIBUTING.md +63 -0
  4. package/DEPLOYMENT.md +80 -0
  5. package/LICENSE +22 -0
  6. package/README.md +142 -0
  7. package/SECURITY.md +108 -0
  8. package/api/health.js +34 -0
  9. package/api/validate.js +252 -0
  10. package/index.d.ts +1221 -0
  11. package/package.json +112 -0
  12. package/public/index.html +149 -0
  13. package/src/batch-optimizer.mjs +451 -0
  14. package/src/bias-detector.mjs +370 -0
  15. package/src/bias-mitigation.mjs +233 -0
  16. package/src/cache.mjs +433 -0
  17. package/src/config.mjs +268 -0
  18. package/src/constants.mjs +80 -0
  19. package/src/context-compressor.mjs +350 -0
  20. package/src/convenience.mjs +617 -0
  21. package/src/cost-tracker.mjs +257 -0
  22. package/src/cross-modal-consistency.mjs +170 -0
  23. package/src/data-extractor.mjs +232 -0
  24. package/src/dynamic-few-shot.mjs +140 -0
  25. package/src/dynamic-prompts.mjs +361 -0
  26. package/src/ensemble/index.mjs +53 -0
  27. package/src/ensemble-judge.mjs +366 -0
  28. package/src/error-handler.mjs +67 -0
  29. package/src/errors.mjs +167 -0
  30. package/src/experience-propagation.mjs +128 -0
  31. package/src/experience-tracer.mjs +487 -0
  32. package/src/explanation-manager.mjs +299 -0
  33. package/src/feedback-aggregator.mjs +248 -0
  34. package/src/game-goal-prompts.mjs +478 -0
  35. package/src/game-player.mjs +548 -0
  36. package/src/hallucination-detector.mjs +155 -0
  37. package/src/helpers/playwright.mjs +80 -0
  38. package/src/human-validation-manager.mjs +516 -0
  39. package/src/index.mjs +364 -0
  40. package/src/judge.mjs +929 -0
  41. package/src/latency-aware-batch-optimizer.mjs +192 -0
  42. package/src/load-env.mjs +159 -0
  43. package/src/logger.mjs +55 -0
  44. package/src/metrics.mjs +187 -0
  45. package/src/model-tier-selector.mjs +221 -0
  46. package/src/multi-modal/index.mjs +36 -0
  47. package/src/multi-modal-fusion.mjs +190 -0
  48. package/src/multi-modal.mjs +524 -0
  49. package/src/natural-language-specs.mjs +1071 -0
  50. package/src/pair-comparison.mjs +277 -0
  51. package/src/persona/index.mjs +42 -0
  52. package/src/persona-enhanced.mjs +200 -0
  53. package/src/persona-experience.mjs +572 -0
  54. package/src/position-counterbalance.mjs +140 -0
  55. package/src/prompt-composer.mjs +375 -0
  56. package/src/render-change-detector.mjs +583 -0
  57. package/src/research-enhanced-validation.mjs +436 -0
  58. package/src/retry.mjs +152 -0
  59. package/src/rubrics.mjs +231 -0
  60. package/src/score-tracker.mjs +277 -0
  61. package/src/smart-validator.mjs +447 -0
  62. package/src/spec-config.mjs +106 -0
  63. package/src/spec-templates.mjs +347 -0
  64. package/src/specs/index.mjs +38 -0
  65. package/src/temporal/index.mjs +102 -0
  66. package/src/temporal-adaptive.mjs +163 -0
  67. package/src/temporal-batch-optimizer.mjs +222 -0
  68. package/src/temporal-constants.mjs +69 -0
  69. package/src/temporal-context.mjs +49 -0
  70. package/src/temporal-decision-manager.mjs +271 -0
  71. package/src/temporal-decision.mjs +669 -0
  72. package/src/temporal-errors.mjs +58 -0
  73. package/src/temporal-note-pruner.mjs +173 -0
  74. package/src/temporal-preprocessor.mjs +543 -0
  75. package/src/temporal-prompt-formatter.mjs +219 -0
  76. package/src/temporal-validation.mjs +159 -0
  77. package/src/temporal.mjs +415 -0
  78. package/src/type-guards.mjs +311 -0
  79. package/src/uncertainty-reducer.mjs +470 -0
  80. package/src/utils/index.mjs +175 -0
  81. package/src/validation-framework.mjs +321 -0
  82. package/src/validation-result-normalizer.mjs +64 -0
  83. package/src/validation.mjs +243 -0
  84. package/src/validators/accessibility-programmatic.mjs +345 -0
  85. package/src/validators/accessibility-validator.mjs +223 -0
  86. package/src/validators/batch-validator.mjs +143 -0
  87. package/src/validators/hybrid-validator.mjs +268 -0
  88. package/src/validators/index.mjs +34 -0
  89. package/src/validators/prompt-builder.mjs +218 -0
  90. package/src/validators/rubric.mjs +85 -0
  91. package/src/validators/state-programmatic.mjs +260 -0
  92. package/src/validators/state-validator.mjs +291 -0
  93. package/vercel.json +27 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Natural Language Spec Templates
3
+ *
4
+ * Reusable templates for common testing patterns based on real-world BDD usage.
5
+ * Supports template inheritance and composition.
6
+ *
7
+ * Based on research findings:
8
+ * - Real-world BDD patterns (Cucumber, SpecFlow, Behave)
9
+ * - Real-world usage patterns (200+ tests)
10
+ * - Best practices (scenario independence, domain language, living documentation)
11
+ */
12
+
13
+ import { log } from './logger.mjs';
14
+
15
+ /**
16
+ * Built-in templates for common patterns
17
+ */
18
+ export const TEMPLATES = {
19
+ /**
20
+ * Game Testing Template
21
+ * Based on real-world interactive game patterns
22
+ */
23
+ game: {
24
+ name: 'Game Testing',
25
+ description: 'Template for testing interactive games with activation keys',
26
+ spec: `Given I visit {url}
27
+ When I activate the game (press '{activationKey}'{selector})
28
+ Then the game should be playable
29
+ {goals}
30
+ Context: viewport={viewport}, device={device}{temporal}`,
31
+ variables: {
32
+ url: 'game.example.com',
33
+ activationKey: 'g',
34
+ selector: '',
35
+ viewport: '1280x720',
36
+ device: 'desktop',
37
+ goals: '',
38
+ temporal: ''
39
+ },
40
+ examples: [
41
+ {
42
+ name: 'Basic Game',
43
+ values: {
44
+ url: 'example.com',
45
+ activationKey: 'g',
46
+ selector: ', selector: #game-paddle'
47
+ }
48
+ },
49
+ {
50
+ name: 'Game with Temporal',
51
+ values: {
52
+ url: 'game.example.com',
53
+ activationKey: 'g',
54
+ selector: ', selector: #game-element',
55
+ temporal: ', fps: 2, duration: 10 seconds, temporal: true'
56
+ }
57
+ }
58
+ ]
59
+ },
60
+
61
+ /**
62
+ * Accessibility Template
63
+ */
64
+ accessibility: {
65
+ name: 'Accessibility Testing',
66
+ description: 'Template for accessibility validation',
67
+ spec: `Given I visit {url}{persona}
68
+ When the page loads
69
+ Then it should be accessible
70
+ {checks}
71
+ Context: viewport={viewport}, device={device}`,
72
+ variables: {
73
+ url: 'example.com',
74
+ persona: '',
75
+ viewport: '1280x720',
76
+ device: 'desktop',
77
+ checks: 'And contrast should meet WCAG standards'
78
+ },
79
+ examples: [
80
+ {
81
+ name: 'Basic Accessibility',
82
+ values: {
83
+ url: 'example.com'
84
+ }
85
+ },
86
+ {
87
+ name: 'Accessibility with Persona',
88
+ values: {
89
+ url: 'example.com',
90
+ persona: ' as a visually impaired user'
91
+ }
92
+ }
93
+ ]
94
+ },
95
+
96
+ /**
97
+ * Browser Experience Template
98
+ */
99
+ browser_experience: {
100
+ name: 'Browser Experience',
101
+ description: 'Template for complete user journey testing',
102
+ spec: `Given I visit {url}
103
+ {steps}
104
+ Then {outcome}
105
+ Context: viewport={viewport}, device={device}`,
106
+ variables: {
107
+ url: 'example.com',
108
+ steps: 'When the page loads',
109
+ outcome: 'the page should be usable',
110
+ viewport: '1280x720',
111
+ device: 'desktop'
112
+ },
113
+ examples: [
114
+ {
115
+ name: 'E-commerce Journey',
116
+ values: {
117
+ url: 'shop.example.com',
118
+ steps: `When I browse products
119
+ And I add items to cart
120
+ And I proceed to checkout`,
121
+ outcome: 'the checkout form should be usable'
122
+ }
123
+ }
124
+ ]
125
+ },
126
+
127
+ /**
128
+ * State Validation Template
129
+ */
130
+ state_validation: {
131
+ name: 'State Validation',
132
+ description: 'Template for validating state consistency',
133
+ spec: `Given {initialState}
134
+ When {action}
135
+ Then the state should be consistent
136
+ {checks}
137
+ Context: viewport={viewport}`,
138
+ variables: {
139
+ initialState: 'I visit a page',
140
+ action: 'the state changes',
141
+ checks: 'And the visual representation should match the internal state',
142
+ viewport: '1280x720'
143
+ },
144
+ examples: [
145
+ {
146
+ name: 'Game State',
147
+ values: {
148
+ initialState: 'I play a game',
149
+ action: 'the game state changes',
150
+ checks: 'And cleared elements should be visually removed'
151
+ }
152
+ }
153
+ ]
154
+ },
155
+
156
+ /**
157
+ * Temporal Sequence Template
158
+ */
159
+ temporal: {
160
+ name: 'Temporal Sequence',
161
+ description: 'Template for testing sequences over time',
162
+ spec: `Given {initial}
163
+ When I observe {target} for {duration}
164
+ Then {expected}
165
+ Context: fps={fps}, duration={duration} seconds, temporal: true`,
166
+ variables: {
167
+ initial: 'I visit a page with animations',
168
+ target: 'the page',
169
+ duration: '5',
170
+ fps: '2',
171
+ expected: 'animations should be smooth'
172
+ },
173
+ examples: [
174
+ {
175
+ name: 'Animation Validation',
176
+ values: {
177
+ initial: 'I visit a page with animations',
178
+ target: 'the page',
179
+ duration: '5',
180
+ fps: '2',
181
+ expected: 'animations should be smooth'
182
+ }
183
+ }
184
+ ]
185
+ },
186
+
187
+ /**
188
+ * Property-Based Template
189
+ */
190
+ property: {
191
+ name: 'Property-Based Testing',
192
+ description: 'Template for property/invariant testing',
193
+ spec: 'For all {scope}, {property}',
194
+ variables: {
195
+ scope: 'screenshots',
196
+ property: 'the validation score should be between 0 and 10'
197
+ },
198
+ examples: [
199
+ {
200
+ name: 'Score Range',
201
+ values: {
202
+ scope: 'screenshots',
203
+ property: 'the validation score should be between 0 and 10'
204
+ }
205
+ },
206
+ {
207
+ name: 'State Consistency',
208
+ values: {
209
+ scope: 'game states',
210
+ property: 'the visual representation should match the internal state'
211
+ }
212
+ }
213
+ ]
214
+ }
215
+ };
216
+
217
+ /**
218
+ * Create a spec from a template
219
+ */
220
+ export function createSpecFromTemplate(templateName, variables = {}) {
221
+ const template = TEMPLATES[templateName];
222
+
223
+ if (!template) {
224
+ throw new Error(`Template "${templateName}" not found. Available: ${Object.keys(TEMPLATES).join(', ')}`);
225
+ }
226
+
227
+ // Merge template variables with provided variables
228
+ const mergedVars = {
229
+ ...template.variables,
230
+ ...variables
231
+ };
232
+
233
+ // Replace variables in spec
234
+ let spec = template.spec;
235
+ for (const [key, value] of Object.entries(mergedVars)) {
236
+ const placeholder = `{${key}}`;
237
+ spec = spec.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), String(value));
238
+ }
239
+
240
+ return spec;
241
+ }
242
+
243
+ /**
244
+ * Compose multiple templates
245
+ */
246
+ export function composeTemplates(templates, composition = 'sequential') {
247
+ if (composition === 'sequential') {
248
+ // Sequential: execute one after another
249
+ return templates.map(t => t.spec).join('\n\n');
250
+ } else if (composition === 'parallel') {
251
+ // Parallel: execute all (would need execution framework support)
252
+ return templates.map(t => t.spec).join('\n\n---\n\n');
253
+ } else {
254
+ throw new Error(`Unknown composition type: ${composition}`);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Inherit from a base template
260
+ */
261
+ export function inheritTemplate(baseTemplateName, overrides = {}) {
262
+ const base = TEMPLATES[baseTemplateName];
263
+
264
+ if (!base) {
265
+ throw new Error(`Base template "${baseTemplateName}" not found`);
266
+ }
267
+
268
+ return {
269
+ ...base,
270
+ ...overrides,
271
+ variables: {
272
+ ...base.variables,
273
+ ...(overrides.variables || {})
274
+ }
275
+ };
276
+ }
277
+
278
+ /**
279
+ * Register a custom template
280
+ */
281
+ export function registerTemplate(name, template) {
282
+ if (!template.name || !template.spec || !template.variables) {
283
+ throw new Error('Template must have name, spec, and variables');
284
+ }
285
+
286
+ TEMPLATES[name] = template;
287
+ log(`[SpecTemplates] Registered custom template: ${name}`);
288
+ }
289
+
290
+ /**
291
+ * List available templates
292
+ */
293
+ export function listTemplates() {
294
+ return Object.keys(TEMPLATES).map(name => ({
295
+ name,
296
+ ...TEMPLATES[name]
297
+ }));
298
+ }
299
+
300
+ /**
301
+ * Get template by name
302
+ */
303
+ export function getTemplate(name) {
304
+ const template = TEMPLATES[name];
305
+
306
+ if (!template) {
307
+ throw new Error(`Template "${name}" not found. Available: ${Object.keys(TEMPLATES).join(', ')}`);
308
+ }
309
+
310
+ return template;
311
+ }
312
+
313
+ /**
314
+ * Validate template structure
315
+ */
316
+ export function validateTemplate(template) {
317
+ const errors = [];
318
+
319
+ if (!template.name) {
320
+ errors.push('Template must have a name');
321
+ }
322
+
323
+ if (!template.spec) {
324
+ errors.push('Template must have a spec');
325
+ }
326
+
327
+ if (!template.variables || typeof template.variables !== 'object') {
328
+ errors.push('Template must have variables object');
329
+ }
330
+
331
+ // Check that all placeholders in spec have corresponding variables
332
+ const placeholders = template.spec.match(/\{(\w+)\}/g) || [];
333
+ const placeholderNames = placeholders.map(p => p.slice(1, -1));
334
+ const variableNames = Object.keys(template.variables || {});
335
+
336
+ for (const placeholder of placeholderNames) {
337
+ if (!variableNames.includes(placeholder)) {
338
+ errors.push(`Placeholder {${placeholder}} in spec has no corresponding variable`);
339
+ }
340
+ }
341
+
342
+ return {
343
+ valid: errors.length === 0,
344
+ errors
345
+ };
346
+ }
347
+
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Natural Language Specs Sub-Module
3
+ *
4
+ * Natural language specification parsing and execution.
5
+ *
6
+ * Import from 'ai-visual-test/specs'
7
+ */
8
+
9
+ // Core spec functions
10
+ export {
11
+ parseSpec,
12
+ mapToInterfaces,
13
+ executeSpec,
14
+ generatePropertyTests,
15
+ testBehavior,
16
+ validateSpec
17
+ } from '../natural-language-specs.mjs';
18
+
19
+ // Spec templates
20
+ export {
21
+ TEMPLATES,
22
+ createSpecFromTemplate,
23
+ composeTemplates,
24
+ inheritTemplate,
25
+ registerTemplate,
26
+ listTemplates,
27
+ getTemplate,
28
+ validateTemplate
29
+ } from '../spec-templates.mjs';
30
+
31
+ // Spec config
32
+ export {
33
+ createSpecConfig,
34
+ getSpecConfig,
35
+ setSpecConfig,
36
+ resetSpecConfig
37
+ } from '../spec-config.mjs';
38
+
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Temporal Sub-Module
3
+ *
4
+ * All temporal aggregation and decision-making functionality.
5
+ *
6
+ * Import from 'ai-visual-test/temporal'
7
+ */
8
+
9
+ // Core temporal aggregation
10
+ export {
11
+ aggregateTemporalNotes,
12
+ formatNotesForPrompt,
13
+ calculateCoherenceExported as calculateCoherence
14
+ } from '../temporal.mjs';
15
+
16
+ // Temporal formatting
17
+ export {
18
+ formatTemporalContext,
19
+ formatTemporalForPrompt,
20
+ formatSingleScaleForPrompt,
21
+ formatMultiScaleForPrompt
22
+ } from '../temporal-prompt-formatter.mjs';
23
+
24
+ // Temporal decision management
25
+ export {
26
+ TemporalDecisionManager,
27
+ createTemporalDecisionManager
28
+ } from '../temporal-decision-manager.mjs';
29
+
30
+ // Temporal preprocessing
31
+ export {
32
+ TemporalPreprocessingManager,
33
+ AdaptiveTemporalProcessor,
34
+ createTemporalPreprocessingManager,
35
+ createAdaptiveTemporalProcessor
36
+ } from '../temporal-preprocessor.mjs';
37
+
38
+ // Temporal note pruning
39
+ export {
40
+ pruneTemporalNotes,
41
+ propagateNotes,
42
+ selectTopWeightedNotes
43
+ } from '../temporal-note-pruner.mjs';
44
+
45
+ // Temporal decision functions
46
+ export {
47
+ aggregateMultiScale,
48
+ SequentialDecisionContext,
49
+ humanPerceptionTime,
50
+ calculateAttentionWeight
51
+ } from '../temporal-decision.mjs';
52
+
53
+ // Render change detection
54
+ export {
55
+ detectRenderChanges,
56
+ calculateOptimalFPS,
57
+ detectVisualChanges,
58
+ captureOnRenderChanges,
59
+ captureAdaptiveTemporalScreenshots
60
+ } from '../render-change-detector.mjs';
61
+
62
+ // Adaptive temporal
63
+ export {
64
+ aggregateTemporalNotesAdaptive,
65
+ calculateOptimalWindowSize,
66
+ detectActivityPattern
67
+ } from '../temporal-adaptive.mjs';
68
+
69
+ // Temporal batch optimization
70
+ export { TemporalBatchOptimizer } from '../temporal-batch-optimizer.mjs';
71
+
72
+ // Temporal context
73
+ export {
74
+ createTemporalContext,
75
+ mergeTemporalContext,
76
+ extractTemporalContext
77
+ } from '../temporal-context.mjs';
78
+
79
+ // Temporal constants
80
+ export {
81
+ TIME_SCALES,
82
+ MULTI_SCALE_WINDOWS,
83
+ READING_SPEEDS,
84
+ ATTENTION_MULTIPLIERS,
85
+ COMPLEXITY_MULTIPLIERS,
86
+ CONFIDENCE_THRESHOLDS,
87
+ TIME_BOUNDS,
88
+ CONTENT_THRESHOLDS
89
+ } from '../temporal-constants.mjs';
90
+
91
+ // Temporal errors
92
+ export {
93
+ TemporalError,
94
+ PerceptionTimeError,
95
+ SequentialContextError,
96
+ MultiScaleError,
97
+ TemporalBatchError
98
+ } from '../temporal-errors.mjs';
99
+
100
+ // Temporal screenshots (from multi-modal)
101
+ export { captureTemporalScreenshots } from '../multi-modal.mjs';
102
+
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Adaptive Temporal Aggregation
3
+ *
4
+ * Implements adaptive window sizing based on note frequency and activity type.
5
+ *
6
+ * Research context:
7
+ * - "Towards Dynamic Theory of Mind: Evaluating LLM Adaptation to Temporal Evolution of Human States"
8
+ * (arXiv:2505.17663) - DynToM benchmark, optimal window sizes vary by activity pattern
9
+ * * We use adaptive window sizing based on frequency (loosely related concept)
10
+ * * We do NOT implement the DynToM benchmark or its specific methods
11
+ * - "The Other Mind: How Language Models Exhibit Human Temporal Cognition" (arXiv:2507.15851)
12
+ * * Paper discusses Weber-Fechner law: logarithmic compression of temporal perception
13
+ * * Paper discusses temporal reference points and hierarchical construction
14
+ * * We use LINEAR frequency-based adjustment, NOT logarithmic compression
15
+ * * We do NOT implement temporal reference points
16
+ *
17
+ * IMPORTANT: This implementation uses LINEAR frequency-based window adjustment, NOT the
18
+ * logarithmic compression (Weber-Fechner law) described in arXiv:2507.15851. We cite
19
+ * the papers for adaptive window concepts, but do NOT implement their specific findings.
20
+ */
21
+
22
+ import { aggregateTemporalNotes } from './temporal.mjs';
23
+
24
+ /**
25
+ * Calculate optimal window size based on note frequency
26
+ *
27
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
28
+ * @param {{
29
+ * minWindow?: number;
30
+ * maxWindow?: number;
31
+ * defaultWindow?: number;
32
+ * }} [options={}] - Options
33
+ * @returns {number} Optimal window size in milliseconds
34
+ */
35
+ export function calculateOptimalWindowSize(notes, options = {}) {
36
+ const {
37
+ minWindow = 5000,
38
+ maxWindow = 30000,
39
+ defaultWindow = 10000
40
+ } = options;
41
+
42
+ if (notes.length < 2) {
43
+ return defaultWindow;
44
+ }
45
+
46
+ // Calculate note frequency (notes per second)
47
+ const timeSpan = notes[notes.length - 1].timestamp - notes[0].timestamp;
48
+ if (timeSpan <= 0) {
49
+ return defaultWindow;
50
+ }
51
+
52
+ const frequency = notes.length / (timeSpan / 1000); // notes per second
53
+
54
+ // Adaptive logic based on frequency
55
+ // High frequency (>2 notes/sec): use smaller windows
56
+ // Low frequency (<0.5 notes/sec): use larger windows
57
+ // Medium frequency: use default window
58
+
59
+ if (frequency > 2) {
60
+ return Math.max(minWindow, defaultWindow * 0.5); // 5s for high frequency
61
+ } else if (frequency < 0.5) {
62
+ return Math.min(maxWindow, defaultWindow * 2); // 20s for low frequency
63
+ } else {
64
+ return defaultWindow; // 10s for medium frequency
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Detect activity pattern from notes
70
+ *
71
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
72
+ * @returns {'fastChange' | 'slowChange' | 'consistent' | 'erratic'} Activity pattern
73
+ */
74
+ export function detectActivityPattern(notes) {
75
+ if (notes.length < 3) {
76
+ return 'consistent';
77
+ }
78
+
79
+ // Calculate change rate
80
+ const timeSpan = notes[notes.length - 1].timestamp - notes[0].timestamp;
81
+ const avgTimeBetween = timeSpan / (notes.length - 1);
82
+
83
+ // Calculate score variance
84
+ const scores = notes
85
+ .map(n => n.gameState?.score || 0)
86
+ .filter(s => typeof s === 'number');
87
+
88
+ if (scores.length < 2) {
89
+ return 'consistent';
90
+ }
91
+
92
+ const meanScore = scores.reduce((a, b) => a + b, 0) / scores.length;
93
+ const variance = scores.reduce((sum, score) => sum + Math.pow(score - meanScore, 2), 0) / scores.length;
94
+
95
+ // Detect direction changes
96
+ let directionChanges = 0;
97
+ for (let i = 1; i < scores.length; i++) {
98
+ const prev = scores[i - 1];
99
+ const curr = scores[i];
100
+ if ((prev < curr && i > 1 && scores[i - 2] > prev) ||
101
+ (prev > curr && i > 1 && scores[i - 2] < prev)) {
102
+ directionChanges++;
103
+ }
104
+ }
105
+
106
+ // Classify pattern
107
+ if (avgTimeBetween < 1000 && variance > meanScore * 0.5) {
108
+ return 'fastChange';
109
+ } else if (avgTimeBetween > 2000 && variance < meanScore * 0.2) {
110
+ return 'slowChange';
111
+ } else if (directionChanges > scores.length * 0.3) {
112
+ return 'erratic';
113
+ } else {
114
+ return 'consistent';
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Aggregate temporal notes with adaptive window sizing
120
+ *
121
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
122
+ * @param {{
123
+ * adaptive?: boolean;
124
+ * windowSize?: number;
125
+ * decayFactor?: number;
126
+ * coherenceThreshold?: number;
127
+ * }} [options={}] - Aggregation options
128
+ * @returns {import('./index.mjs').AggregatedTemporalNotes} Aggregated temporal notes
129
+ */
130
+ export function aggregateTemporalNotesAdaptive(notes, options = {}) {
131
+ const {
132
+ adaptive = true,
133
+ windowSize,
134
+ decayFactor = 0.9,
135
+ coherenceThreshold = 0.7
136
+ } = options;
137
+
138
+ let finalWindowSize = windowSize;
139
+
140
+ if (adaptive && !windowSize) {
141
+ // Calculate optimal window size based on note frequency
142
+ finalWindowSize = calculateOptimalWindowSize(notes);
143
+
144
+ // Adjust based on activity pattern
145
+ const pattern = detectActivityPattern(notes);
146
+ if (pattern === 'fastChange') {
147
+ finalWindowSize = Math.min(finalWindowSize, 5000); // Prefer smaller for fast changes
148
+ } else if (pattern === 'slowChange') {
149
+ finalWindowSize = Math.max(finalWindowSize, 20000); // Prefer larger for slow changes
150
+ }
151
+ } else if (!finalWindowSize) {
152
+ finalWindowSize = 10000; // Default
153
+ }
154
+
155
+ return aggregateTemporalNotes(notes, {
156
+ windowSize: finalWindowSize,
157
+ decayFactor,
158
+ coherenceThreshold
159
+ });
160
+ }
161
+
162
+
163
+