driftdetect-core 0.1.2 → 0.3.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 (193) hide show
  1. package/dist/boundaries/boundary-store.d.ts +92 -0
  2. package/dist/boundaries/boundary-store.d.ts.map +1 -0
  3. package/dist/boundaries/boundary-store.js +458 -0
  4. package/dist/boundaries/boundary-store.js.map +1 -0
  5. package/dist/boundaries/index.d.ts +8 -0
  6. package/dist/boundaries/index.d.ts.map +1 -0
  7. package/dist/boundaries/index.js +8 -0
  8. package/dist/boundaries/index.js.map +1 -0
  9. package/dist/boundaries/types.d.ts +237 -0
  10. package/dist/boundaries/types.d.ts.map +1 -0
  11. package/dist/boundaries/types.js +8 -0
  12. package/dist/boundaries/types.js.map +1 -0
  13. package/dist/dna/ai-context.d.ts +10 -0
  14. package/dist/dna/ai-context.d.ts.map +1 -0
  15. package/dist/dna/ai-context.js +46 -0
  16. package/dist/dna/ai-context.js.map +1 -0
  17. package/dist/dna/dna-analyzer.d.ts +34 -0
  18. package/dist/dna/dna-analyzer.d.ts.map +1 -0
  19. package/dist/dna/dna-analyzer.js +110 -0
  20. package/dist/dna/dna-analyzer.js.map +1 -0
  21. package/dist/dna/dna-store.d.ts +13 -0
  22. package/dist/dna/dna-store.d.ts.map +1 -0
  23. package/dist/dna/dna-store.js +43 -0
  24. package/dist/dna/dna-store.js.map +1 -0
  25. package/dist/dna/gene-extractors/animation-approach.d.ts +15 -0
  26. package/dist/dna/gene-extractors/animation-approach.d.ts.map +1 -0
  27. package/dist/dna/gene-extractors/animation-approach.js +97 -0
  28. package/dist/dna/gene-extractors/animation-approach.js.map +1 -0
  29. package/dist/dna/gene-extractors/base-extractor.d.ts +53 -0
  30. package/dist/dna/gene-extractors/base-extractor.d.ts.map +1 -0
  31. package/dist/dna/gene-extractors/base-extractor.js +76 -0
  32. package/dist/dna/gene-extractors/base-extractor.js.map +1 -0
  33. package/dist/dna/gene-extractors/index.d.ts +16 -0
  34. package/dist/dna/gene-extractors/index.d.ts.map +1 -0
  35. package/dist/dna/gene-extractors/index.js +38 -0
  36. package/dist/dna/gene-extractors/index.js.map +1 -0
  37. package/dist/dna/gene-extractors/responsive-approach.d.ts +10 -0
  38. package/dist/dna/gene-extractors/responsive-approach.d.ts.map +1 -0
  39. package/dist/dna/gene-extractors/responsive-approach.js +30 -0
  40. package/dist/dna/gene-extractors/responsive-approach.js.map +1 -0
  41. package/dist/dna/gene-extractors/spacing-philosophy.d.ts +10 -0
  42. package/dist/dna/gene-extractors/spacing-philosophy.d.ts.map +1 -0
  43. package/dist/dna/gene-extractors/spacing-philosophy.js +30 -0
  44. package/dist/dna/gene-extractors/spacing-philosophy.js.map +1 -0
  45. package/dist/dna/gene-extractors/state-styling.d.ts +10 -0
  46. package/dist/dna/gene-extractors/state-styling.d.ts.map +1 -0
  47. package/dist/dna/gene-extractors/state-styling.js +29 -0
  48. package/dist/dna/gene-extractors/state-styling.js.map +1 -0
  49. package/dist/dna/gene-extractors/theming.d.ts +10 -0
  50. package/dist/dna/gene-extractors/theming.d.ts.map +1 -0
  51. package/dist/dna/gene-extractors/theming.js +30 -0
  52. package/dist/dna/gene-extractors/theming.js.map +1 -0
  53. package/dist/dna/gene-extractors/variant-handling.d.ts +13 -0
  54. package/dist/dna/gene-extractors/variant-handling.d.ts.map +1 -0
  55. package/dist/dna/gene-extractors/variant-handling.js +38 -0
  56. package/dist/dna/gene-extractors/variant-handling.js.map +1 -0
  57. package/dist/dna/health-calculator.d.ts +21 -0
  58. package/dist/dna/health-calculator.d.ts.map +1 -0
  59. package/dist/dna/health-calculator.js +113 -0
  60. package/dist/dna/health-calculator.js.map +1 -0
  61. package/dist/dna/index.d.ts +21 -0
  62. package/dist/dna/index.d.ts.map +1 -0
  63. package/dist/dna/index.js +19 -0
  64. package/dist/dna/index.js.map +1 -0
  65. package/dist/dna/mutation-detector.d.ts +10 -0
  66. package/dist/dna/mutation-detector.d.ts.map +1 -0
  67. package/dist/dna/mutation-detector.js +39 -0
  68. package/dist/dna/mutation-detector.js.map +1 -0
  69. package/dist/dna/playbook-generator.d.ts +6 -0
  70. package/dist/dna/playbook-generator.d.ts.map +1 -0
  71. package/dist/dna/playbook-generator.js +53 -0
  72. package/dist/dna/playbook-generator.js.map +1 -0
  73. package/dist/dna/types.d.ts +95 -0
  74. package/dist/dna/types.d.ts.map +1 -0
  75. package/dist/dna/types.js +8 -0
  76. package/dist/dna/types.js.map +1 -0
  77. package/dist/index.d.ts +7 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +10 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/parsers/parser-manager.d.ts.map +1 -1
  82. package/dist/parsers/parser-manager.js +2 -0
  83. package/dist/parsers/parser-manager.js.map +1 -1
  84. package/dist/parsers/tree-sitter/config.d.ts +82 -0
  85. package/dist/parsers/tree-sitter/config.d.ts.map +1 -0
  86. package/dist/parsers/tree-sitter/config.js +200 -0
  87. package/dist/parsers/tree-sitter/config.js.map +1 -0
  88. package/dist/parsers/tree-sitter/csharp-ast-converter.d.ts +64 -0
  89. package/dist/parsers/tree-sitter/csharp-ast-converter.d.ts.map +1 -0
  90. package/dist/parsers/tree-sitter/csharp-ast-converter.js +271 -0
  91. package/dist/parsers/tree-sitter/csharp-ast-converter.js.map +1 -0
  92. package/dist/parsers/tree-sitter/csharp-loader.d.ts +43 -0
  93. package/dist/parsers/tree-sitter/csharp-loader.d.ts.map +1 -0
  94. package/dist/parsers/tree-sitter/csharp-loader.js +146 -0
  95. package/dist/parsers/tree-sitter/csharp-loader.js.map +1 -0
  96. package/dist/parsers/tree-sitter/index.d.ts +26 -0
  97. package/dist/parsers/tree-sitter/index.d.ts.map +1 -0
  98. package/dist/parsers/tree-sitter/index.js +47 -0
  99. package/dist/parsers/tree-sitter/index.js.map +1 -0
  100. package/dist/parsers/tree-sitter/java/annotation-extractor.d.ts +79 -0
  101. package/dist/parsers/tree-sitter/java/annotation-extractor.d.ts.map +1 -0
  102. package/dist/parsers/tree-sitter/java/annotation-extractor.js +540 -0
  103. package/dist/parsers/tree-sitter/java/annotation-extractor.js.map +1 -0
  104. package/dist/parsers/tree-sitter/java/class-extractor.d.ts +40 -0
  105. package/dist/parsers/tree-sitter/java/class-extractor.d.ts.map +1 -0
  106. package/dist/parsers/tree-sitter/java/class-extractor.js +770 -0
  107. package/dist/parsers/tree-sitter/java/class-extractor.js.map +1 -0
  108. package/dist/parsers/tree-sitter/java/index.d.ts +14 -0
  109. package/dist/parsers/tree-sitter/java/index.d.ts.map +1 -0
  110. package/dist/parsers/tree-sitter/java/index.js +25 -0
  111. package/dist/parsers/tree-sitter/java/index.js.map +1 -0
  112. package/dist/parsers/tree-sitter/java/method-extractor.d.ts +88 -0
  113. package/dist/parsers/tree-sitter/java/method-extractor.d.ts.map +1 -0
  114. package/dist/parsers/tree-sitter/java/method-extractor.js +551 -0
  115. package/dist/parsers/tree-sitter/java/method-extractor.js.map +1 -0
  116. package/dist/parsers/tree-sitter/java/types.d.ts +545 -0
  117. package/dist/parsers/tree-sitter/java/types.d.ts.map +1 -0
  118. package/dist/parsers/tree-sitter/java/types.js +81 -0
  119. package/dist/parsers/tree-sitter/java/types.js.map +1 -0
  120. package/dist/parsers/tree-sitter/loader.d.ts +50 -0
  121. package/dist/parsers/tree-sitter/loader.d.ts.map +1 -0
  122. package/dist/parsers/tree-sitter/loader.js +156 -0
  123. package/dist/parsers/tree-sitter/loader.js.map +1 -0
  124. package/dist/parsers/tree-sitter/pydantic/config-extractor.d.ts +78 -0
  125. package/dist/parsers/tree-sitter/pydantic/config-extractor.d.ts.map +1 -0
  126. package/dist/parsers/tree-sitter/pydantic/config-extractor.js +278 -0
  127. package/dist/parsers/tree-sitter/pydantic/config-extractor.js.map +1 -0
  128. package/dist/parsers/tree-sitter/pydantic/constraint-parser.d.ts +84 -0
  129. package/dist/parsers/tree-sitter/pydantic/constraint-parser.d.ts.map +1 -0
  130. package/dist/parsers/tree-sitter/pydantic/constraint-parser.js +321 -0
  131. package/dist/parsers/tree-sitter/pydantic/constraint-parser.js.map +1 -0
  132. package/dist/parsers/tree-sitter/pydantic/field-extractor.d.ts +74 -0
  133. package/dist/parsers/tree-sitter/pydantic/field-extractor.d.ts.map +1 -0
  134. package/dist/parsers/tree-sitter/pydantic/field-extractor.js +285 -0
  135. package/dist/parsers/tree-sitter/pydantic/field-extractor.js.map +1 -0
  136. package/dist/parsers/tree-sitter/pydantic/index.d.ts +18 -0
  137. package/dist/parsers/tree-sitter/pydantic/index.d.ts.map +1 -0
  138. package/dist/parsers/tree-sitter/pydantic/index.js +23 -0
  139. package/dist/parsers/tree-sitter/pydantic/index.js.map +1 -0
  140. package/dist/parsers/tree-sitter/pydantic/inheritance-resolver.d.ts +70 -0
  141. package/dist/parsers/tree-sitter/pydantic/inheritance-resolver.d.ts.map +1 -0
  142. package/dist/parsers/tree-sitter/pydantic/inheritance-resolver.js +251 -0
  143. package/dist/parsers/tree-sitter/pydantic/inheritance-resolver.js.map +1 -0
  144. package/dist/parsers/tree-sitter/pydantic/pydantic-extractor.d.ts +102 -0
  145. package/dist/parsers/tree-sitter/pydantic/pydantic-extractor.d.ts.map +1 -0
  146. package/dist/parsers/tree-sitter/pydantic/pydantic-extractor.js +399 -0
  147. package/dist/parsers/tree-sitter/pydantic/pydantic-extractor.js.map +1 -0
  148. package/dist/parsers/tree-sitter/pydantic/type-resolver.d.ts +89 -0
  149. package/dist/parsers/tree-sitter/pydantic/type-resolver.d.ts.map +1 -0
  150. package/dist/parsers/tree-sitter/pydantic/type-resolver.js +426 -0
  151. package/dist/parsers/tree-sitter/pydantic/type-resolver.js.map +1 -0
  152. package/dist/parsers/tree-sitter/pydantic/types.d.ts +177 -0
  153. package/dist/parsers/tree-sitter/pydantic/types.d.ts.map +1 -0
  154. package/dist/parsers/tree-sitter/pydantic/types.js +139 -0
  155. package/dist/parsers/tree-sitter/pydantic/types.js.map +1 -0
  156. package/dist/parsers/tree-sitter/pydantic/validator-extractor.d.ts +88 -0
  157. package/dist/parsers/tree-sitter/pydantic/validator-extractor.d.ts.map +1 -0
  158. package/dist/parsers/tree-sitter/pydantic/validator-extractor.js +315 -0
  159. package/dist/parsers/tree-sitter/pydantic/validator-extractor.js.map +1 -0
  160. package/dist/parsers/tree-sitter/python-ast-converter.d.ts +140 -0
  161. package/dist/parsers/tree-sitter/python-ast-converter.d.ts.map +1 -0
  162. package/dist/parsers/tree-sitter/python-ast-converter.js +360 -0
  163. package/dist/parsers/tree-sitter/python-ast-converter.js.map +1 -0
  164. package/dist/parsers/tree-sitter/tree-sitter-csharp-parser.d.ts +465 -0
  165. package/dist/parsers/tree-sitter/tree-sitter-csharp-parser.d.ts.map +1 -0
  166. package/dist/parsers/tree-sitter/tree-sitter-csharp-parser.js +1146 -0
  167. package/dist/parsers/tree-sitter/tree-sitter-csharp-parser.js.map +1 -0
  168. package/dist/parsers/tree-sitter/tree-sitter-python-parser.d.ts +86 -0
  169. package/dist/parsers/tree-sitter/tree-sitter-python-parser.d.ts.map +1 -0
  170. package/dist/parsers/tree-sitter/tree-sitter-python-parser.js +177 -0
  171. package/dist/parsers/tree-sitter/tree-sitter-python-parser.js.map +1 -0
  172. package/dist/parsers/tree-sitter/types.d.ts +399 -0
  173. package/dist/parsers/tree-sitter/types.d.ts.map +1 -0
  174. package/dist/parsers/tree-sitter/types.js +20 -0
  175. package/dist/parsers/tree-sitter/types.js.map +1 -0
  176. package/dist/parsers/types.d.ts +1 -1
  177. package/dist/parsers/types.d.ts.map +1 -1
  178. package/dist/scanner/file-walker.d.ts.map +1 -1
  179. package/dist/scanner/file-walker.js +5 -0
  180. package/dist/scanner/file-walker.js.map +1 -1
  181. package/dist/store/history-store.d.ts +85 -269
  182. package/dist/store/history-store.d.ts.map +1 -1
  183. package/dist/store/history-store.js +272 -624
  184. package/dist/store/history-store.js.map +1 -1
  185. package/dist/types/index.d.ts +1 -0
  186. package/dist/types/index.d.ts.map +1 -1
  187. package/dist/types/index.js +2 -0
  188. package/dist/types/index.js.map +1 -1
  189. package/dist/types/java-type-mapping.d.ts +79 -0
  190. package/dist/types/java-type-mapping.d.ts.map +1 -0
  191. package/dist/types/java-type-mapping.js +290 -0
  192. package/dist/types/java-type-mapping.js.map +1 -0
  193. package/package.json +8 -3
@@ -1,69 +1,41 @@
1
1
  /**
2
- * History Store - Pattern change tracking and approval history
2
+ * History Store - Pattern snapshot and trend tracking
3
3
  *
4
- * Tracks pattern changes over time and stores approval history.
5
- * History entries are stored in .drift/history/ directory.
4
+ * Captures pattern state over time to detect regressions and improvements.
5
+ * Stores daily snapshots in .drift/history/snapshots/
6
6
  *
7
- * @requirements 4.4 - THE Pattern_Store SHALL maintain history of pattern changes in .drift/history/
7
+ * @requirements 4.4 - Pattern history SHALL be tracked in .drift/history/
8
8
  */
9
9
  import * as fs from 'node:fs/promises';
10
10
  import * as path from 'node:path';
11
11
  import { EventEmitter } from 'node:events';
12
- import { HISTORY_FILE_VERSION } from './types.js';
12
+ const DEFAULT_CONFIG = {
13
+ rootDir: '.',
14
+ maxSnapshots: 90,
15
+ snapshotInterval: 'scan',
16
+ };
13
17
  // ============================================================================
14
18
  // Constants
15
19
  // ============================================================================
16
- /** Directory name for drift configuration */
17
20
  const DRIFT_DIR = '.drift';
18
- /** Directory name for history */
19
21
  const HISTORY_DIR = 'history';
20
- /** Main history file name */
21
- const HISTORY_FILE = 'patterns.json';
22
- /** Default maximum history entries per pattern */
23
- const DEFAULT_MAX_ENTRIES_PER_PATTERN = 100;
24
- /** Default maximum age for history entries in days */
25
- const DEFAULT_MAX_AGE_DAYS = 365;
26
- // ============================================================================
27
- // Error Classes
28
- // ============================================================================
29
- /**
30
- * Error thrown when a history operation fails
31
- */
32
- export class HistoryStoreError extends Error {
33
- errorCause;
34
- constructor(message, errorCause) {
35
- super(message);
36
- this.name = 'HistoryStoreError';
37
- this.errorCause = errorCause;
38
- }
39
- }
40
- /**
41
- * Error thrown when a pattern history is not found
42
- */
43
- export class PatternHistoryNotFoundError extends Error {
44
- patternId;
45
- constructor(patternId) {
46
- super(`Pattern history not found: ${patternId}`);
47
- this.patternId = patternId;
48
- this.name = 'PatternHistoryNotFoundError';
49
- }
50
- }
51
- /**
52
- * Default history store configuration
53
- */
54
- export const DEFAULT_HISTORY_STORE_CONFIG = {
55
- rootDir: '.',
56
- maxEntriesPerPattern: DEFAULT_MAX_ENTRIES_PER_PATTERN,
57
- maxAgeDays: DEFAULT_MAX_AGE_DAYS,
58
- autoSave: false,
59
- autoSaveDebounce: 1000,
22
+ const SNAPSHOTS_DIR = 'snapshots';
23
+ // Thresholds for detecting significant changes
24
+ const REGRESSION_THRESHOLDS = {
25
+ confidence: -0.05, // 5% drop in confidence
26
+ compliance: -0.10, // 10% drop in compliance
27
+ outliers: 3, // 3+ new outliers
28
+ };
29
+ const CRITICAL_THRESHOLDS = {
30
+ confidence: -0.15, // 15% drop = critical
31
+ compliance: -0.20, // 20% drop = critical
60
32
  };
61
33
  // ============================================================================
62
34
  // Helper Functions
63
35
  // ============================================================================
64
- /**
65
- * Check if a file exists
66
- */
36
+ async function ensureDir(dirPath) {
37
+ await fs.mkdir(dirPath, { recursive: true });
38
+ }
67
39
  async function fileExists(filePath) {
68
40
  try {
69
41
  await fs.access(filePath);
@@ -73,635 +45,311 @@ async function fileExists(filePath) {
73
45
  return false;
74
46
  }
75
47
  }
76
- /**
77
- * Ensure a directory exists
78
- */
79
- async function ensureDir(dirPath) {
80
- await fs.mkdir(dirPath, { recursive: true });
48
+ function getDateString(date = new Date()) {
49
+ return date.toISOString().split('T')[0];
50
+ }
51
+ function calculateComplianceRate(locations, outliers) {
52
+ const total = locations + outliers;
53
+ return total > 0 ? locations / total : 1;
81
54
  }
82
55
  // ============================================================================
83
56
  // History Store Class
84
57
  // ============================================================================
85
- /**
86
- * History Store - Manages pattern change history
87
- *
88
- * Tracks all pattern changes including:
89
- * - Pattern created
90
- * - Pattern updated
91
- * - Pattern approved
92
- * - Pattern ignored
93
- * - Pattern deleted
94
- * - Confidence changed
95
- * - Locations changed
96
- * - Severity changed
97
- *
98
- * History is stored in .drift/history/patterns.json
99
- *
100
- * @requirements 4.4 - Pattern history tracked in .drift/history/
101
- */
102
58
  export class HistoryStore extends EventEmitter {
103
59
  config;
104
60
  historyDir;
105
- historyFilePath;
106
- histories = new Map();
107
- loaded = false;
108
- dirty = false;
109
- saveTimeout = null;
61
+ snapshotsDir;
110
62
  constructor(config = {}) {
111
63
  super();
112
- this.config = { ...DEFAULT_HISTORY_STORE_CONFIG, ...config };
64
+ this.config = { ...DEFAULT_CONFIG, ...config };
113
65
  this.historyDir = path.join(this.config.rootDir, DRIFT_DIR, HISTORY_DIR);
114
- this.historyFilePath = path.join(this.historyDir, HISTORY_FILE);
66
+ this.snapshotsDir = path.join(this.historyDir, SNAPSHOTS_DIR);
115
67
  }
116
- // ==========================================================================
117
- // Initialization
118
- // ==========================================================================
119
68
  /**
120
69
  * Initialize the history store
121
- *
122
- * Creates necessary directories and loads existing history.
123
70
  */
124
71
  async initialize() {
125
- // Create directory structure
126
- await ensureDir(this.historyDir);
127
- // Load existing history
128
- await this.load();
129
- this.loaded = true;
130
- }
131
- // ==========================================================================
132
- // Loading
133
- // ==========================================================================
134
- /**
135
- * Load history from disk
136
- *
137
- * @requirements 4.4 - Load history from .drift/history/
138
- */
139
- async load() {
140
- this.histories.clear();
141
- if (!(await fileExists(this.historyFilePath))) {
142
- this.emitEvent('file:loaded', undefined, { count: 0 });
143
- return;
144
- }
145
- try {
146
- const content = await fs.readFile(this.historyFilePath, 'utf-8');
147
- const data = JSON.parse(content);
148
- // Load pattern histories into map
149
- for (const history of data.patterns) {
150
- this.histories.set(history.patternId, history);
151
- }
152
- this.emitEvent('file:loaded', undefined, { count: this.histories.size });
153
- }
154
- catch (error) {
155
- if (error.code === 'ENOENT') {
156
- return; // File doesn't exist, skip
157
- }
158
- throw new HistoryStoreError(`Failed to load history file: ${this.historyFilePath}`, error);
159
- }
160
- }
161
- // ==========================================================================
162
- // Saving
163
- // ==========================================================================
164
- /**
165
- * Save history to disk
166
- *
167
- * @requirements 4.4 - Persist history in .drift/history/
168
- */
169
- async save() {
170
- const historyFile = {
171
- version: HISTORY_FILE_VERSION,
172
- patterns: Array.from(this.histories.values()),
173
- lastUpdated: new Date().toISOString(),
72
+ await ensureDir(this.snapshotsDir);
73
+ }
74
+ /**
75
+ * Create a snapshot from current patterns
76
+ */
77
+ async createSnapshot(patterns) {
78
+ const now = new Date();
79
+ const timestamp = now.toISOString();
80
+ const date = getDateString(now);
81
+ // Convert patterns to snapshots
82
+ const patternSnapshots = patterns.map(p => ({
83
+ patternId: p.id,
84
+ patternName: p.name,
85
+ category: p.category,
86
+ confidence: p.confidence.score,
87
+ locationCount: p.locations.length,
88
+ outlierCount: p.outliers.length,
89
+ complianceRate: calculateComplianceRate(p.locations.length, p.outliers.length),
90
+ status: p.status,
91
+ }));
92
+ // Calculate summary
93
+ const summary = this.calculateSummary(patternSnapshots);
94
+ const snapshot = {
95
+ timestamp,
96
+ date,
97
+ patterns: patternSnapshots,
98
+ summary,
174
99
  };
175
- // Ensure directory exists
176
- await ensureDir(this.historyDir);
177
- // Write file
178
- await fs.writeFile(this.historyFilePath, JSON.stringify(historyFile, null, 2));
179
- this.dirty = false;
180
- this.emitEvent('file:saved', undefined, { count: this.histories.size });
100
+ // Save snapshot
101
+ await this.saveSnapshot(snapshot);
102
+ // Cleanup old snapshots
103
+ await this.cleanupOldSnapshots();
104
+ this.emit('snapshot:created', snapshot);
105
+ return snapshot;
181
106
  }
182
107
  /**
183
- * Schedule an auto-save if enabled
108
+ * Get snapshots for a date range
184
109
  */
185
- scheduleAutoSave() {
186
- if (!this.config.autoSave) {
187
- return;
188
- }
189
- if (this.saveTimeout) {
190
- clearTimeout(this.saveTimeout);
110
+ async getSnapshots(startDate, endDate) {
111
+ const snapshots = [];
112
+ if (!(await fileExists(this.snapshotsDir))) {
113
+ return snapshots;
191
114
  }
192
- this.saveTimeout = setTimeout(async () => {
193
- if (this.dirty) {
194
- await this.save();
115
+ const files = await fs.readdir(this.snapshotsDir);
116
+ const jsonFiles = files.filter(f => f.endsWith('.json')).sort();
117
+ for (const file of jsonFiles) {
118
+ const date = file.replace('.json', '');
119
+ // Filter by date range
120
+ if (startDate && date < startDate)
121
+ continue;
122
+ if (endDate && date > endDate)
123
+ continue;
124
+ try {
125
+ const content = await fs.readFile(path.join(this.snapshotsDir, file), 'utf-8');
126
+ const snapshot = JSON.parse(content);
127
+ snapshots.push(snapshot);
128
+ }
129
+ catch (error) {
130
+ console.error(`Error reading snapshot ${file}:`, error);
195
131
  }
196
- }, this.config.autoSaveDebounce);
197
- }
198
- // ==========================================================================
199
- // Recording Events
200
- // ==========================================================================
201
- /**
202
- * Record a history event for a pattern
203
- *
204
- * @param patternId - Pattern ID
205
- * @param category - Pattern category
206
- * @param eventType - Type of event
207
- * @param options - Additional event options
208
- */
209
- recordEvent(patternId, category, eventType, options = {}) {
210
- const now = new Date().toISOString();
211
- // Create the event
212
- const event = {
213
- timestamp: now,
214
- type: eventType,
215
- patternId,
216
- ...(options.user && { user: options.user }),
217
- ...(options.previousValue !== undefined && { previousValue: options.previousValue }),
218
- ...(options.newValue !== undefined && { newValue: options.newValue }),
219
- ...(options.details && { details: options.details }),
220
- };
221
- // Get or create pattern history
222
- let history = this.histories.get(patternId);
223
- if (!history) {
224
- history = {
225
- patternId,
226
- category,
227
- events: [],
228
- createdAt: now,
229
- lastModified: now,
230
- };
231
- this.histories.set(patternId, history);
232
- }
233
- // Add event to history
234
- history.events.push(event);
235
- history.lastModified = now;
236
- // Prune if needed
237
- this.prunePatternHistory(patternId);
238
- this.dirty = true;
239
- this.emitEvent('event:recorded', patternId, { eventType });
240
- this.scheduleAutoSave();
241
- return event;
242
- }
243
- /**
244
- * Record a pattern creation event
245
- */
246
- recordCreated(pattern, user) {
247
- const options = {
248
- newValue: {
249
- name: pattern.name,
250
- description: pattern.description,
251
- confidence: pattern.confidence.score,
252
- severity: pattern.severity,
253
- },
254
- };
255
- if (user !== undefined) {
256
- options.user = user;
257
- }
258
- return this.recordEvent(pattern.id, pattern.category, 'created', options);
259
- }
260
- /**
261
- * Record a pattern update event
262
- */
263
- recordUpdated(pattern, previousPattern, user) {
264
- const options = {
265
- previousValue: {
266
- name: previousPattern.name,
267
- description: previousPattern.description,
268
- },
269
- newValue: {
270
- name: pattern.name,
271
- description: pattern.description,
272
- },
273
- };
274
- if (user !== undefined) {
275
- options.user = user;
276
- }
277
- return this.recordEvent(pattern.id, pattern.category, 'updated', options);
278
- }
279
- /**
280
- * Record a pattern approval event
281
- */
282
- recordApproved(pattern, user) {
283
- const options = {
284
- details: {
285
- confidence: pattern.confidence.score,
286
- severity: pattern.severity,
287
- },
288
- };
289
- if (user !== undefined) {
290
- options.user = user;
291
- }
292
- return this.recordEvent(pattern.id, pattern.category, 'approved', options);
293
- }
294
- /**
295
- * Record a pattern ignore event
296
- */
297
- recordIgnored(pattern, user) {
298
- const options = {};
299
- if (user !== undefined) {
300
- options.user = user;
301
- }
302
- return this.recordEvent(pattern.id, pattern.category, 'ignored', options);
303
- }
304
- /**
305
- * Record a pattern deletion event
306
- */
307
- recordDeleted(pattern, user) {
308
- const options = {
309
- previousValue: {
310
- name: pattern.name,
311
- status: pattern.status,
312
- },
313
- };
314
- if (user !== undefined) {
315
- options.user = user;
316
- }
317
- return this.recordEvent(pattern.id, pattern.category, 'deleted', options);
318
- }
319
- /**
320
- * Record a confidence change event
321
- */
322
- recordConfidenceChanged(pattern, previousScore, user) {
323
- const options = {
324
- previousValue: previousScore,
325
- newValue: pattern.confidence.score,
326
- };
327
- if (user !== undefined) {
328
- options.user = user;
329
- }
330
- return this.recordEvent(pattern.id, pattern.category, 'confidence_changed', options);
331
- }
332
- /**
333
- * Record a locations change event
334
- */
335
- recordLocationsChanged(pattern, previousCount, user) {
336
- const options = {
337
- previousValue: previousCount,
338
- newValue: pattern.locations.length,
339
- details: {
340
- added: Math.max(0, pattern.locations.length - previousCount),
341
- removed: Math.max(0, previousCount - pattern.locations.length),
342
- },
343
- };
344
- if (user !== undefined) {
345
- options.user = user;
346
- }
347
- return this.recordEvent(pattern.id, pattern.category, 'locations_changed', options);
348
- }
349
- /**
350
- * Record a severity change event
351
- */
352
- recordSeverityChanged(pattern, previousSeverity, user) {
353
- const options = {
354
- previousValue: previousSeverity,
355
- newValue: pattern.severity,
356
- };
357
- if (user !== undefined) {
358
- options.user = user;
359
- }
360
- return this.recordEvent(pattern.id, pattern.category, 'severity_changed', options);
361
- }
362
- // ==========================================================================
363
- // Querying
364
- // ==========================================================================
365
- /**
366
- * Get history for a specific pattern
367
- *
368
- * @param patternId - Pattern ID
369
- * @returns Pattern history or undefined if not found
370
- */
371
- getPatternHistory(patternId) {
372
- return this.histories.get(patternId);
373
- }
374
- /**
375
- * Get history for a specific pattern, throwing if not found
376
- *
377
- * @param patternId - Pattern ID
378
- * @returns Pattern history
379
- * @throws PatternHistoryNotFoundError if not found
380
- */
381
- getPatternHistoryOrThrow(patternId) {
382
- const history = this.histories.get(patternId);
383
- if (!history) {
384
- throw new PatternHistoryNotFoundError(patternId);
385
132
  }
386
- return history;
133
+ return snapshots;
387
134
  }
388
135
  /**
389
- * Check if history exists for a pattern
390
- *
391
- * @param patternId - Pattern ID
392
- * @returns True if history exists
136
+ * Get the most recent snapshot
393
137
  */
394
- hasPatternHistory(patternId) {
395
- return this.histories.has(patternId);
138
+ async getLatestSnapshot() {
139
+ const snapshots = await this.getSnapshots();
140
+ return snapshots.length > 0 ? snapshots[snapshots.length - 1] : null;
396
141
  }
397
142
  /**
398
- * Query history events with filtering and pagination
399
- *
400
- * @param query - Query options
401
- * @returns Query result with matching events
143
+ * Get snapshot from N days ago
402
144
  */
403
- query(query = {}) {
404
- const startTime = Date.now();
405
- // Collect all events from relevant histories
406
- let events = [];
407
- // Filter by pattern ID(s)
408
- if (query.patternId) {
409
- const history = this.histories.get(query.patternId);
410
- if (history) {
411
- events = [...history.events];
412
- }
145
+ async getSnapshotFromDaysAgo(days) {
146
+ const targetDate = new Date();
147
+ targetDate.setDate(targetDate.getDate() - days);
148
+ const dateStr = getDateString(targetDate);
149
+ const filePath = path.join(this.snapshotsDir, `${dateStr}.json`);
150
+ if (!(await fileExists(filePath))) {
151
+ // Find closest snapshot before target date
152
+ const snapshots = await this.getSnapshots(undefined, dateStr);
153
+ return snapshots.length > 0 ? snapshots[snapshots.length - 1] : null;
413
154
  }
414
- else if (query.patternIds && query.patternIds.length > 0) {
415
- for (const patternId of query.patternIds) {
416
- const history = this.histories.get(patternId);
417
- if (history) {
418
- events.push(...history.events);
419
- }
420
- }
155
+ try {
156
+ const content = await fs.readFile(filePath, 'utf-8');
157
+ return JSON.parse(content);
421
158
  }
422
- else {
423
- // Get all events
424
- for (const history of this.histories.values()) {
425
- events.push(...history.events);
426
- }
159
+ catch {
160
+ return null;
427
161
  }
428
- // Apply filters
429
- events = this.applyFilters(events, query);
430
- // Sort by timestamp descending (most recent first)
431
- events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
432
- // Get total before pagination
433
- const total = events.length;
434
- // Apply pagination
435
- const offset = query.offset ?? 0;
436
- const limit = query.limit ?? events.length;
437
- const hasMore = offset + limit < total;
438
- events = events.slice(offset, offset + limit);
439
- return {
440
- events,
441
- total,
442
- hasMore,
443
- executionTime: Date.now() - startTime,
444
- };
445
162
  }
446
163
  /**
447
- * Apply filters to events
164
+ * Calculate trends between two snapshots
448
165
  */
449
- applyFilters(events, query) {
450
- return events.filter((event) => {
451
- // Filter by event type
452
- if (query.eventType) {
453
- const types = Array.isArray(query.eventType)
454
- ? query.eventType
455
- : [query.eventType];
456
- if (!types.includes(event.type)) {
457
- return false;
458
- }
459
- }
460
- // Filter by category
461
- if (query.category) {
462
- const categories = Array.isArray(query.category)
463
- ? query.category
464
- : [query.category];
465
- const history = this.histories.get(event.patternId);
466
- if (!history || !categories.includes(history.category)) {
467
- return false;
468
- }
469
- }
470
- // Filter by user
471
- if (query.user && event.user !== query.user) {
472
- return false;
166
+ calculateTrends(current, previous) {
167
+ const trends = [];
168
+ const previousMap = new Map(previous.patterns.map(p => [p.patternId, p]));
169
+ for (const currentPattern of current.patterns) {
170
+ const prevPattern = previousMap.get(currentPattern.patternId);
171
+ if (!prevPattern) {
172
+ // New pattern, skip for now
173
+ continue;
473
174
  }
474
- // Filter by date range
475
- if (query.after) {
476
- const eventTime = new Date(event.timestamp).getTime();
477
- const afterTime = new Date(query.after).getTime();
478
- if (eventTime < afterTime) {
479
- return false;
480
- }
175
+ // Check confidence change
176
+ const confidenceChange = currentPattern.confidence - prevPattern.confidence;
177
+ if (Math.abs(confidenceChange) >= Math.abs(REGRESSION_THRESHOLDS.confidence)) {
178
+ const isRegression = confidenceChange < 0;
179
+ const isCritical = confidenceChange <= CRITICAL_THRESHOLDS.confidence;
180
+ trends.push({
181
+ patternId: currentPattern.patternId,
182
+ patternName: currentPattern.patternName,
183
+ category: currentPattern.category,
184
+ type: isRegression ? 'regression' : 'improvement',
185
+ metric: 'confidence',
186
+ previousValue: prevPattern.confidence,
187
+ currentValue: currentPattern.confidence,
188
+ change: confidenceChange,
189
+ changePercent: (confidenceChange / prevPattern.confidence) * 100,
190
+ severity: isCritical ? 'critical' : isRegression ? 'warning' : 'info',
191
+ firstSeen: previous.timestamp,
192
+ details: `Confidence ${isRegression ? 'dropped' : 'improved'} from ${(prevPattern.confidence * 100).toFixed(0)}% to ${(currentPattern.confidence * 100).toFixed(0)}%`,
193
+ });
481
194
  }
482
- if (query.before) {
483
- const eventTime = new Date(event.timestamp).getTime();
484
- const beforeTime = new Date(query.before).getTime();
485
- if (eventTime > beforeTime) {
486
- return false;
487
- }
195
+ // Check compliance change
196
+ const complianceChange = currentPattern.complianceRate - prevPattern.complianceRate;
197
+ if (Math.abs(complianceChange) >= Math.abs(REGRESSION_THRESHOLDS.compliance)) {
198
+ const isRegression = complianceChange < 0;
199
+ const isCritical = complianceChange <= CRITICAL_THRESHOLDS.compliance;
200
+ trends.push({
201
+ patternId: currentPattern.patternId,
202
+ patternName: currentPattern.patternName,
203
+ category: currentPattern.category,
204
+ type: isRegression ? 'regression' : 'improvement',
205
+ metric: 'compliance',
206
+ previousValue: prevPattern.complianceRate,
207
+ currentValue: currentPattern.complianceRate,
208
+ change: complianceChange,
209
+ changePercent: prevPattern.complianceRate > 0
210
+ ? (complianceChange / prevPattern.complianceRate) * 100
211
+ : 0,
212
+ severity: isCritical ? 'critical' : isRegression ? 'warning' : 'info',
213
+ firstSeen: previous.timestamp,
214
+ details: `Compliance ${isRegression ? 'dropped' : 'improved'} from ${(prevPattern.complianceRate * 100).toFixed(0)}% to ${(currentPattern.complianceRate * 100).toFixed(0)}%`,
215
+ });
488
216
  }
489
- return true;
490
- });
491
- }
492
- // ==========================================================================
493
- // Convenience Query Methods
494
- // ==========================================================================
495
- /**
496
- * Get all history events
497
- */
498
- getAllEvents() {
499
- return this.query().events;
500
- }
501
- /**
502
- * Get events by type
503
- */
504
- getEventsByType(eventType) {
505
- return this.query({ eventType }).events;
506
- }
507
- /**
508
- * Get events by category
509
- */
510
- getEventsByCategory(category) {
511
- return this.query({ category }).events;
512
- }
513
- /**
514
- * Get events in date range
515
- */
516
- getEventsInDateRange(after, before) {
517
- return this.query({ after, before }).events;
518
- }
519
- /**
520
- * Get recent events
521
- *
522
- * @param limit - Maximum number of events to return
523
- */
524
- getRecentEvents(limit = 50) {
525
- return this.query({ limit }).events;
526
- }
527
- /**
528
- * Get approval history
529
- */
530
- getApprovalHistory() {
531
- return this.getEventsByType('approved');
532
- }
533
- /**
534
- * Get events by user
535
- */
536
- getEventsByUser(user) {
537
- return this.query({ user }).events;
538
- }
539
- // ==========================================================================
540
- // Pruning
541
- // ==========================================================================
542
- /**
543
- * Prune history for a specific pattern
544
- *
545
- * Removes old entries based on maxEntriesPerPattern and maxAgeDays config.
546
- *
547
- * @param patternId - Pattern ID to prune
548
- */
549
- prunePatternHistory(patternId) {
550
- const history = this.histories.get(patternId);
551
- if (!history) {
552
- return;
553
- }
554
- const now = Date.now();
555
- const maxAgeMs = this.config.maxAgeDays * 24 * 60 * 60 * 1000;
556
- // Filter out old events
557
- history.events = history.events.filter((event) => {
558
- const eventTime = new Date(event.timestamp).getTime();
559
- return now - eventTime < maxAgeMs;
560
- });
561
- // Limit number of entries
562
- if (history.events.length > this.config.maxEntriesPerPattern) {
563
- // Keep most recent events
564
- history.events = history.events
565
- .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
566
- .slice(0, this.config.maxEntriesPerPattern);
567
- }
568
- }
569
- /**
570
- * Prune all history
571
- *
572
- * Removes old entries based on maxEntriesPerPattern and maxAgeDays config.
573
- */
574
- prune() {
575
- for (const patternId of this.histories.keys()) {
576
- this.prunePatternHistory(patternId);
577
- }
578
- // Remove empty histories
579
- for (const [patternId, history] of this.histories.entries()) {
580
- if (history.events.length === 0) {
581
- this.histories.delete(patternId);
217
+ // Check outlier increase
218
+ const outlierChange = currentPattern.outlierCount - prevPattern.outlierCount;
219
+ if (outlierChange >= REGRESSION_THRESHOLDS.outliers) {
220
+ trends.push({
221
+ patternId: currentPattern.patternId,
222
+ patternName: currentPattern.patternName,
223
+ category: currentPattern.category,
224
+ type: 'regression',
225
+ metric: 'outliers',
226
+ previousValue: prevPattern.outlierCount,
227
+ currentValue: currentPattern.outlierCount,
228
+ change: outlierChange,
229
+ changePercent: prevPattern.outlierCount > 0
230
+ ? (outlierChange / prevPattern.outlierCount) * 100
231
+ : 100,
232
+ severity: outlierChange >= 10 ? 'critical' : 'warning',
233
+ firstSeen: previous.timestamp,
234
+ details: `${outlierChange} new outliers detected (${prevPattern.outlierCount} → ${currentPattern.outlierCount})`,
235
+ });
582
236
  }
583
237
  }
584
- this.dirty = true;
585
- this.emitEvent('history:pruned', undefined, { count: this.histories.size });
586
- this.scheduleAutoSave();
238
+ return trends;
587
239
  }
588
240
  /**
589
- * Delete history for a specific pattern
590
- *
591
- * @param patternId - Pattern ID
592
- * @returns True if history was deleted
241
+ * Get trend summary for a period
593
242
  */
594
- deletePatternHistory(patternId) {
595
- const deleted = this.histories.delete(patternId);
596
- if (deleted) {
597
- this.dirty = true;
598
- this.scheduleAutoSave();
243
+ async getTrendSummary(period = '7d') {
244
+ const days = period === '7d' ? 7 : period === '30d' ? 30 : 90;
245
+ const current = await this.getLatestSnapshot();
246
+ const previous = await this.getSnapshotFromDaysAgo(days);
247
+ if (!current || !previous) {
248
+ return null;
599
249
  }
600
- return deleted;
601
- }
602
- // ==========================================================================
603
- // Statistics
604
- // ==========================================================================
605
- /**
606
- * Get statistics about the history store
607
- */
608
- getStats() {
609
- const eventsByType = {
610
- created: 0,
611
- approved: 0,
612
- ignored: 0,
613
- updated: 0,
614
- deleted: 0,
615
- confidence_changed: 0,
616
- locations_changed: 0,
617
- severity_changed: 0,
618
- };
619
- let totalEvents = 0;
620
- let oldestEvent = null;
621
- let newestEvent = null;
622
- for (const history of this.histories.values()) {
623
- for (const event of history.events) {
624
- totalEvents++;
625
- eventsByType[event.type]++;
626
- if (!oldestEvent || event.timestamp < oldestEvent) {
627
- oldestEvent = event.timestamp;
628
- }
629
- if (!newestEvent || event.timestamp > newestEvent) {
630
- newestEvent = event.timestamp;
631
- }
250
+ const trends = this.calculateTrends(current, previous);
251
+ const regressions = trends.filter(t => t.type === 'regression');
252
+ const improvements = trends.filter(t => t.type === 'improvement');
253
+ // Calculate category trends
254
+ const categoryTrends = {};
255
+ const categories = new Set([
256
+ ...current.patterns.map(p => p.category),
257
+ ...previous.patterns.map(p => p.category),
258
+ ]);
259
+ for (const category of categories) {
260
+ const currentCat = current.summary.byCategory[category];
261
+ const prevCat = previous.summary.byCategory[category];
262
+ if (currentCat && prevCat) {
263
+ const avgConfidenceChange = currentCat.avgConfidence - prevCat.avgConfidence;
264
+ const complianceChange = currentCat.complianceRate - prevCat.complianceRate;
265
+ categoryTrends[category] = {
266
+ trend: avgConfidenceChange > 0.02 ? 'improving'
267
+ : avgConfidenceChange < -0.02 ? 'declining'
268
+ : 'stable',
269
+ avgConfidenceChange,
270
+ complianceChange,
271
+ };
632
272
  }
633
273
  }
274
+ // Calculate overall trend
275
+ const healthDelta = current.summary.overallComplianceRate - previous.summary.overallComplianceRate;
276
+ const overallTrend = healthDelta > 0.02 ? 'improving'
277
+ : healthDelta < -0.02 ? 'declining'
278
+ : 'stable';
279
+ // Count stable patterns
280
+ const changedPatternIds = new Set(trends.map(t => t.patternId));
281
+ const stableCount = current.patterns.filter(p => !changedPatternIds.has(p.patternId)).length;
634
282
  return {
635
- totalPatterns: this.histories.size,
636
- totalEvents,
637
- eventsByType,
638
- oldestEvent,
639
- newestEvent,
283
+ period,
284
+ startDate: previous.date,
285
+ endDate: current.date,
286
+ regressions,
287
+ improvements,
288
+ stable: stableCount,
289
+ overallTrend,
290
+ healthDelta,
291
+ categoryTrends,
640
292
  };
641
293
  }
642
294
  // ==========================================================================
643
- // Event Handling
295
+ // Private Methods
644
296
  // ==========================================================================
645
- /**
646
- * Emit a history store event
647
- */
648
- emitEvent(type, patternId, data) {
649
- const event = {
650
- type,
651
- timestamp: new Date().toISOString(),
652
- };
653
- if (patternId !== undefined) {
654
- event.patternId = patternId;
297
+ calculateSummary(patterns) {
298
+ const byCategory = {};
299
+ let totalLocations = 0;
300
+ let totalOutliers = 0;
301
+ let totalConfidence = 0;
302
+ for (const pattern of patterns) {
303
+ totalLocations += pattern.locationCount;
304
+ totalOutliers += pattern.outlierCount;
305
+ totalConfidence += pattern.confidence;
306
+ // Aggregate by category
307
+ if (!byCategory[pattern.category]) {
308
+ byCategory[pattern.category] = {
309
+ patternCount: 0,
310
+ avgConfidence: 0,
311
+ totalLocations: 0,
312
+ totalOutliers: 0,
313
+ complianceRate: 0,
314
+ };
315
+ }
316
+ const cat = byCategory[pattern.category];
317
+ cat.patternCount++;
318
+ cat.avgConfidence += pattern.confidence;
319
+ cat.totalLocations += pattern.locationCount;
320
+ cat.totalOutliers += pattern.outlierCount;
655
321
  }
656
- if (data !== undefined) {
657
- event.data = data;
322
+ // Finalize category averages
323
+ for (const cat of Object.values(byCategory)) {
324
+ if (cat.patternCount > 0) {
325
+ cat.avgConfidence /= cat.patternCount;
326
+ }
327
+ cat.complianceRate = calculateComplianceRate(cat.totalLocations, cat.totalOutliers);
658
328
  }
659
- this.emit(type, event);
660
- this.emit('*', event); // Wildcard for all events
661
- }
662
- // ==========================================================================
663
- // Utility Methods
664
- // ==========================================================================
665
- /**
666
- * Get the number of patterns with history
667
- */
668
- get size() {
669
- return this.histories.size;
670
- }
671
- /**
672
- * Check if the store has been loaded
673
- */
674
- get isLoaded() {
675
- return this.loaded;
676
- }
677
- /**
678
- * Check if there are unsaved changes
679
- */
680
- get isDirty() {
681
- return this.dirty;
329
+ return {
330
+ totalPatterns: patterns.length,
331
+ avgConfidence: patterns.length > 0 ? totalConfidence / patterns.length : 0,
332
+ totalLocations,
333
+ totalOutliers,
334
+ overallComplianceRate: calculateComplianceRate(totalLocations, totalOutliers),
335
+ byCategory,
336
+ };
682
337
  }
683
- /**
684
- * Get the history directory path
685
- */
686
- get path() {
687
- return this.historyDir;
338
+ async saveSnapshot(snapshot) {
339
+ const filePath = path.join(this.snapshotsDir, `${snapshot.date}.json`);
340
+ await fs.writeFile(filePath, JSON.stringify(snapshot, null, 2));
688
341
  }
689
- /**
690
- * Clear all history from memory (does not affect disk)
691
- */
692
- clear() {
693
- this.histories.clear();
694
- this.dirty = true;
695
- }
696
- /**
697
- * Dispose of the history store
698
- */
699
- dispose() {
700
- if (this.saveTimeout) {
701
- clearTimeout(this.saveTimeout);
702
- this.saveTimeout = null;
342
+ async cleanupOldSnapshots() {
343
+ if (!(await fileExists(this.snapshotsDir))) {
344
+ return;
345
+ }
346
+ const files = await fs.readdir(this.snapshotsDir);
347
+ const jsonFiles = files.filter(f => f.endsWith('.json')).sort();
348
+ // Remove oldest files if over limit
349
+ const toRemove = jsonFiles.slice(0, Math.max(0, jsonFiles.length - this.config.maxSnapshots));
350
+ for (const file of toRemove) {
351
+ await fs.unlink(path.join(this.snapshotsDir, file));
703
352
  }
704
- this.removeAllListeners();
705
353
  }
706
354
  }
707
355
  //# sourceMappingURL=history-store.js.map