conductor-bridge 1.0.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.
- package/README.md +69 -0
- package/dist/bridge/index.d.ts +8 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +8 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/json-interchange.d.ts +94 -0
- package/dist/bridge/json-interchange.d.ts.map +1 -0
- package/dist/bridge/json-interchange.js +301 -0
- package/dist/bridge/json-interchange.js.map +1 -0
- package/dist/cli/commands/amend.d.ts +12 -0
- package/dist/cli/commands/amend.d.ts.map +1 -0
- package/dist/cli/commands/amend.js +205 -0
- package/dist/cli/commands/amend.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +12 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +60 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/dispatch.d.ts +12 -0
- package/dist/cli/commands/dispatch.d.ts.map +1 -0
- package/dist/cli/commands/dispatch.js +207 -0
- package/dist/cli/commands/dispatch.js.map +1 -0
- package/dist/cli/commands/handoff.d.ts +31 -0
- package/dist/cli/commands/handoff.d.ts.map +1 -0
- package/dist/cli/commands/handoff.js +273 -0
- package/dist/cli/commands/handoff.js.map +1 -0
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +301 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/status.d.ts +12 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +206 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +148 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/handoff/encryption.d.ts +85 -0
- package/dist/handoff/encryption.d.ts.map +1 -0
- package/dist/handoff/encryption.js +308 -0
- package/dist/handoff/encryption.js.map +1 -0
- package/dist/handoff/index.d.ts +8 -0
- package/dist/handoff/index.d.ts.map +1 -0
- package/dist/handoff/index.js +10 -0
- package/dist/handoff/index.js.map +1 -0
- package/dist/handoff/mycelium-arc.d.ts +116 -0
- package/dist/handoff/mycelium-arc.d.ts.map +1 -0
- package/dist/handoff/mycelium-arc.js +410 -0
- package/dist/handoff/mycelium-arc.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/index.d.ts +10 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +12 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/plan-parser.d.ts +29 -0
- package/dist/parsers/plan-parser.d.ts.map +1 -0
- package/dist/parsers/plan-parser.js +503 -0
- package/dist/parsers/plan-parser.js.map +1 -0
- package/dist/parsers/spec-parser.d.ts +10 -0
- package/dist/parsers/spec-parser.d.ts.map +1 -0
- package/dist/parsers/spec-parser.js +382 -0
- package/dist/parsers/spec-parser.js.map +1 -0
- package/dist/parsers/state-parser.d.ts +21 -0
- package/dist/parsers/state-parser.d.ts.map +1 -0
- package/dist/parsers/state-parser.js +378 -0
- package/dist/parsers/state-parser.js.map +1 -0
- package/dist/parsers/types.d.ts +190 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +7 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/schemas/cognitive-state.d.ts +1523 -0
- package/dist/schemas/cognitive-state.d.ts.map +1 -0
- package/dist/schemas/cognitive-state.js +328 -0
- package/dist/schemas/cognitive-state.js.map +1 -0
- package/dist/schemas/enums.d.ts +124 -0
- package/dist/schemas/enums.d.ts.map +1 -0
- package/dist/schemas/enums.js +108 -0
- package/dist/schemas/enums.js.map +1 -0
- package/dist/schemas/index.d.ts +9 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +9 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/sync/adhd-continuity.d.ts +91 -0
- package/dist/sync/adhd-continuity.d.ts.map +1 -0
- package/dist/sync/adhd-continuity.js +302 -0
- package/dist/sync/adhd-continuity.js.map +1 -0
- package/dist/sync/convergence-tracker.d.ts +111 -0
- package/dist/sync/convergence-tracker.d.ts.map +1 -0
- package/dist/sync/convergence-tracker.js +299 -0
- package/dist/sync/convergence-tracker.js.map +1 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +15 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/state-sync.d.ts +105 -0
- package/dist/sync/state-sync.d.ts.map +1 -0
- package/dist/sync/state-sync.js +403 -0
- package/dist/sync/state-sync.js.map +1 -0
- package/dist/sync/watcher.d.ts +90 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +281 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/utils/atomic-write.d.ts +31 -0
- package/dist/utils/atomic-write.d.ts.map +1 -0
- package/dist/utils/atomic-write.js +69 -0
- package/dist/utils/atomic-write.js.map +1 -0
- package/dist/utils/error-handling.d.ts +70 -0
- package/dist/utils/error-handling.d.ts.map +1 -0
- package/dist/utils/error-handling.js +109 -0
- package/dist/utils/error-handling.js.map +1 -0
- package/dist/utils/file-lock.d.ts +46 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +117 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +12 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/rlm-navigator.d.ts +160 -0
- package/dist/utils/rlm-navigator.d.ts.map +1 -0
- package/dist/utils/rlm-navigator.js +368 -0
- package/dist/utils/rlm-navigator.js.map +1 -0
- package/dist/utils/safe-path.d.ts +44 -0
- package/dist/utils/safe-path.d.ts.map +1 -0
- package/dist/utils/safe-path.js +96 -0
- package/dist/utils/safe-path.js.map +1 -0
- package/dist/utils/timed-io.d.ts +51 -0
- package/dist/utils/timed-io.d.ts.map +1 -0
- package/dist/utils/timed-io.js +128 -0
- package/dist/utils/timed-io.js.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Watcher Daemon
|
|
3
|
+
*
|
|
4
|
+
* Watches .conductor/ directory for changes and triggers auto-sync.
|
|
5
|
+
* Provides real-time bidirectional state synchronization.
|
|
6
|
+
*/
|
|
7
|
+
import { watch } from 'chokidar';
|
|
8
|
+
import { join, basename } from 'path';
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { syncConductorToUSD, syncUSDToConductor, computeFileChecksum, hasFileChanged, } from './state-sync.js';
|
|
11
|
+
import { ConductorConvergenceTracker } from './convergence-tracker.js';
|
|
12
|
+
import { parsePlan } from '../parsers/plan-parser.js';
|
|
13
|
+
import { parseState } from '../parsers/state-parser.js';
|
|
14
|
+
import { readFile } from 'fs/promises';
|
|
15
|
+
import { withFallback } from '../utils/error-handling.js';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Default Configuration
|
|
18
|
+
// ============================================================================
|
|
19
|
+
const DEFAULT_CONFIG = {
|
|
20
|
+
debounceMs: 500,
|
|
21
|
+
syncOnStart: true,
|
|
22
|
+
watchPatterns: [
|
|
23
|
+
'**/spec.md',
|
|
24
|
+
'**/plan.md',
|
|
25
|
+
'**/state.md',
|
|
26
|
+
'**/amendments/*.md',
|
|
27
|
+
],
|
|
28
|
+
ignorePatterns: [
|
|
29
|
+
'**/node_modules/**',
|
|
30
|
+
'**/.git/**',
|
|
31
|
+
'**/dispatch.md',
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Watcher Class
|
|
36
|
+
// ============================================================================
|
|
37
|
+
export class ConductorWatcher extends EventEmitter {
|
|
38
|
+
config;
|
|
39
|
+
watcher = null;
|
|
40
|
+
checksums = new Map();
|
|
41
|
+
debounceTimer = null;
|
|
42
|
+
stats;
|
|
43
|
+
convergenceTracker;
|
|
44
|
+
isRunning = false;
|
|
45
|
+
constructor(config) {
|
|
46
|
+
super();
|
|
47
|
+
this.config = {
|
|
48
|
+
conductorDir: config.conductorDir || '.conductor',
|
|
49
|
+
cognitiveStatePath: config.cognitiveStatePath || '.conductor/cognitive_state.json',
|
|
50
|
+
autoSave: config.autoSave ?? true,
|
|
51
|
+
autoSaveIntervalMs: config.autoSaveIntervalMs ?? 300000, // 5 min
|
|
52
|
+
debounceMs: config.debounceMs ?? DEFAULT_CONFIG.debounceMs,
|
|
53
|
+
syncOnStart: config.syncOnStart ?? DEFAULT_CONFIG.syncOnStart,
|
|
54
|
+
watchPatterns: config.watchPatterns ?? DEFAULT_CONFIG.watchPatterns,
|
|
55
|
+
ignorePatterns: config.ignorePatterns ?? DEFAULT_CONFIG.ignorePatterns,
|
|
56
|
+
};
|
|
57
|
+
this.stats = {
|
|
58
|
+
startTime: new Date(),
|
|
59
|
+
syncCount: 0,
|
|
60
|
+
lastSyncTime: null,
|
|
61
|
+
lastSyncResult: null,
|
|
62
|
+
errors: [],
|
|
63
|
+
exchangeCount: 0,
|
|
64
|
+
};
|
|
65
|
+
this.convergenceTracker = new ConductorConvergenceTracker();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Start watching for file changes.
|
|
69
|
+
*/
|
|
70
|
+
async start() {
|
|
71
|
+
if (this.isRunning) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.stats.startTime = new Date();
|
|
75
|
+
this.isRunning = true;
|
|
76
|
+
// Initialize file watcher
|
|
77
|
+
this.watcher = watch(this.config.watchPatterns, {
|
|
78
|
+
cwd: this.config.conductorDir,
|
|
79
|
+
ignored: this.config.ignorePatterns,
|
|
80
|
+
persistent: true,
|
|
81
|
+
ignoreInitial: true,
|
|
82
|
+
awaitWriteFinish: {
|
|
83
|
+
stabilityThreshold: 100,
|
|
84
|
+
pollInterval: 50,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
// Set up event handlers
|
|
88
|
+
this.watcher.on('change', (path) => this.handleChange(path));
|
|
89
|
+
this.watcher.on('add', (path) => this.handleChange(path));
|
|
90
|
+
this.watcher.on('error', (error) => this.handleError(error));
|
|
91
|
+
// Initial sync
|
|
92
|
+
if (this.config.syncOnStart) {
|
|
93
|
+
await this.sync();
|
|
94
|
+
}
|
|
95
|
+
// Initial checksum capture
|
|
96
|
+
await this.captureChecksums();
|
|
97
|
+
this.emit('started', { config: this.config });
|
|
98
|
+
console.log(`[Watcher] Started watching ${this.config.conductorDir}`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Stop watching for file changes.
|
|
102
|
+
*/
|
|
103
|
+
async stop() {
|
|
104
|
+
if (!this.isRunning) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (this.debounceTimer) {
|
|
108
|
+
clearTimeout(this.debounceTimer);
|
|
109
|
+
this.debounceTimer = null;
|
|
110
|
+
}
|
|
111
|
+
if (this.watcher) {
|
|
112
|
+
await this.watcher.close();
|
|
113
|
+
this.watcher = null;
|
|
114
|
+
}
|
|
115
|
+
this.isRunning = false;
|
|
116
|
+
this.emit('stopped', { stats: this.stats });
|
|
117
|
+
console.log('[Watcher] Stopped');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Handle file change event.
|
|
121
|
+
*/
|
|
122
|
+
handleChange(path) {
|
|
123
|
+
const filename = basename(path);
|
|
124
|
+
this.emit('change', { path, filename });
|
|
125
|
+
// Debounce sync
|
|
126
|
+
if (this.debounceTimer) {
|
|
127
|
+
clearTimeout(this.debounceTimer);
|
|
128
|
+
}
|
|
129
|
+
this.debounceTimer = setTimeout(() => {
|
|
130
|
+
this.onDebouncedChange(path);
|
|
131
|
+
}, this.config.debounceMs);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Handle debounced file change.
|
|
135
|
+
*/
|
|
136
|
+
async onDebouncedChange(path) {
|
|
137
|
+
const filename = basename(path);
|
|
138
|
+
const fullPath = join(this.config.conductorDir, path);
|
|
139
|
+
try {
|
|
140
|
+
// Check if file actually changed (via checksum)
|
|
141
|
+
const newChecksum = await computeFileChecksum(fullPath);
|
|
142
|
+
const oldChecksum = this.checksums.get(path) || '';
|
|
143
|
+
if (!hasFileChanged(newChecksum, oldChecksum)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Update checksum
|
|
147
|
+
this.checksums.set(path, newChecksum);
|
|
148
|
+
// Determine sync direction based on changed file
|
|
149
|
+
if (filename === 'plan.md' || filename === 'state.md') {
|
|
150
|
+
// Conductor changed → sync to USD
|
|
151
|
+
await this.syncWithConvergence();
|
|
152
|
+
}
|
|
153
|
+
else if (filename === 'cognitive_state.json') {
|
|
154
|
+
// USD changed → sync to Conductor
|
|
155
|
+
const result = await syncUSDToConductor(this.config);
|
|
156
|
+
this.recordSyncResult(result);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.handleError(error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Perform sync with convergence tracking.
|
|
165
|
+
*/
|
|
166
|
+
async syncWithConvergence() {
|
|
167
|
+
try {
|
|
168
|
+
// Read current state with proper error logging
|
|
169
|
+
const statePath = join(this.config.conductorDir, 'state.md');
|
|
170
|
+
const stateContent = await withFallback(() => readFile(statePath, 'utf-8'), '', { operation: 'readStateFile', path: statePath });
|
|
171
|
+
const stateResult = parseState(stateContent);
|
|
172
|
+
if (!stateResult.success || !stateResult.data) {
|
|
173
|
+
await this.sync();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const planPath = join(this.config.conductorDir, stateResult.data.frontmatter.plan || 'plan.md');
|
|
177
|
+
const planContent = await withFallback(() => readFile(planPath, 'utf-8'), '', { operation: 'readPlanFile', path: planPath });
|
|
178
|
+
const planResult = parsePlan(planContent);
|
|
179
|
+
if (!planResult.success || !planResult.data) {
|
|
180
|
+
await this.sync();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Track convergence
|
|
184
|
+
this.stats.exchangeCount++;
|
|
185
|
+
const convergence = this.convergenceTracker.recordExchange(planResult.data);
|
|
186
|
+
this.emit('convergence', {
|
|
187
|
+
xi_n: convergence.xi_n,
|
|
188
|
+
attractor: convergence.attractor,
|
|
189
|
+
stability: convergence.stability,
|
|
190
|
+
glyph: this.convergenceTracker.getGlyph(),
|
|
191
|
+
isConverged: convergence.isConverged,
|
|
192
|
+
});
|
|
193
|
+
// Perform sync
|
|
194
|
+
await this.sync();
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
this.handleError(error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Perform bidirectional sync.
|
|
202
|
+
*/
|
|
203
|
+
async sync() {
|
|
204
|
+
const result = await syncConductorToUSD(this.config);
|
|
205
|
+
this.recordSyncResult(result);
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Record sync result in stats.
|
|
210
|
+
*/
|
|
211
|
+
recordSyncResult(result) {
|
|
212
|
+
this.stats.syncCount++;
|
|
213
|
+
this.stats.lastSyncTime = new Date();
|
|
214
|
+
this.stats.lastSyncResult = result;
|
|
215
|
+
if (!result.success) {
|
|
216
|
+
this.stats.errors.push(...result.warnings);
|
|
217
|
+
}
|
|
218
|
+
this.emit('sync', result);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Handle errors.
|
|
222
|
+
*/
|
|
223
|
+
handleError(error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
this.stats.errors.push(message);
|
|
226
|
+
this.emit('error', { message, timestamp: new Date().toISOString() });
|
|
227
|
+
console.error(`[Watcher] Error: ${message}`);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Capture initial file checksums.
|
|
231
|
+
*/
|
|
232
|
+
async captureChecksums() {
|
|
233
|
+
const files = ['state.md', 'plan.md', 'spec.md'];
|
|
234
|
+
for (const file of files) {
|
|
235
|
+
try {
|
|
236
|
+
const fullPath = join(this.config.conductorDir, file);
|
|
237
|
+
const checksum = await computeFileChecksum(fullPath);
|
|
238
|
+
if (checksum) {
|
|
239
|
+
this.checksums.set(file, checksum);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// File doesn't exist, ignore
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get current stats.
|
|
249
|
+
*/
|
|
250
|
+
getStats() {
|
|
251
|
+
return { ...this.stats };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get convergence glyph.
|
|
255
|
+
*/
|
|
256
|
+
getConvergenceGlyph() {
|
|
257
|
+
return this.convergenceTracker.getGlyph();
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if watcher is running.
|
|
261
|
+
*/
|
|
262
|
+
isActive() {
|
|
263
|
+
return this.isRunning;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// Factory Function
|
|
268
|
+
// ============================================================================
|
|
269
|
+
/**
|
|
270
|
+
* Create and start a conductor watcher.
|
|
271
|
+
*/
|
|
272
|
+
export async function createWatcher(conductorDir, options) {
|
|
273
|
+
const watcher = new ConductorWatcher({
|
|
274
|
+
conductorDir,
|
|
275
|
+
cognitiveStatePath: join(conductorDir, 'cognitive_state.json'),
|
|
276
|
+
...options,
|
|
277
|
+
});
|
|
278
|
+
await watcher.start();
|
|
279
|
+
return watcher;
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/sync/watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic Write Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic file writes using temp file + rename pattern.
|
|
5
|
+
* This ensures file integrity even during crashes or power failures.
|
|
6
|
+
*/
|
|
7
|
+
export interface AtomicWriteOptions {
|
|
8
|
+
/** File encoding (default: 'utf-8') */
|
|
9
|
+
encoding?: BufferEncoding;
|
|
10
|
+
/** Temp file suffix (default: '.tmp') */
|
|
11
|
+
tempSuffix?: string;
|
|
12
|
+
/** Whether to fsync after write (default: true) */
|
|
13
|
+
fsync?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Write file atomically using temp file + rename pattern.
|
|
17
|
+
*
|
|
18
|
+
* On POSIX systems, rename() is atomic within the same filesystem.
|
|
19
|
+
* On Windows, we unlink the target first if it exists (not fully atomic
|
|
20
|
+
* but prevents data corruption).
|
|
21
|
+
*
|
|
22
|
+
* @param filePath - Target file path
|
|
23
|
+
* @param data - Data to write
|
|
24
|
+
* @param options - Write options
|
|
25
|
+
*/
|
|
26
|
+
export declare function atomicWriteFile(filePath: string, data: string | Buffer, options?: AtomicWriteOptions): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Check if a file exists
|
|
29
|
+
*/
|
|
30
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
31
|
+
//# sourceMappingURL=atomic-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../../src/utils/atomic-write.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic Write Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic file writes using temp file + rename pattern.
|
|
5
|
+
* This ensures file integrity even during crashes or power failures.
|
|
6
|
+
*/
|
|
7
|
+
import { writeFile, rename, unlink, stat, mkdir } from 'fs/promises';
|
|
8
|
+
import { join, dirname, basename } from 'path';
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
10
|
+
/**
|
|
11
|
+
* Write file atomically using temp file + rename pattern.
|
|
12
|
+
*
|
|
13
|
+
* On POSIX systems, rename() is atomic within the same filesystem.
|
|
14
|
+
* On Windows, we unlink the target first if it exists (not fully atomic
|
|
15
|
+
* but prevents data corruption).
|
|
16
|
+
*
|
|
17
|
+
* @param filePath - Target file path
|
|
18
|
+
* @param data - Data to write
|
|
19
|
+
* @param options - Write options
|
|
20
|
+
*/
|
|
21
|
+
export async function atomicWriteFile(filePath, data, options = {}) {
|
|
22
|
+
const { encoding = 'utf-8', tempSuffix = '.tmp', } = options;
|
|
23
|
+
// Generate unique temp file name to avoid collisions
|
|
24
|
+
const uniqueId = randomBytes(6).toString('hex');
|
|
25
|
+
const tempPath = join(dirname(filePath), `.${basename(filePath)}.${uniqueId}${tempSuffix}`);
|
|
26
|
+
try {
|
|
27
|
+
// Ensure parent directory exists
|
|
28
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
29
|
+
// Write to temp file
|
|
30
|
+
await writeFile(tempPath, data, { encoding });
|
|
31
|
+
// On Windows, rename fails if target exists, so unlink first
|
|
32
|
+
if (process.platform === 'win32') {
|
|
33
|
+
try {
|
|
34
|
+
await unlink(filePath);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// File doesn't exist, that's fine
|
|
38
|
+
if (err.code !== 'ENOENT') {
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Rename temp to target (atomic on most filesystems)
|
|
44
|
+
await rename(tempPath, filePath);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
// Clean up temp file on failure
|
|
48
|
+
try {
|
|
49
|
+
await unlink(tempPath);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Ignore cleanup errors
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a file exists
|
|
59
|
+
*/
|
|
60
|
+
export async function fileExists(filePath) {
|
|
61
|
+
try {
|
|
62
|
+
await stat(filePath);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=atomic-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/utils/atomic-write.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAWrC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,IAAqB,EACrB,UAA8B,EAAE;IAEhC,MAAM,EACJ,QAAQ,GAAG,OAAO,EAClB,UAAU,GAAG,MAAM,GACpB,GAAG,OAAO,CAAC;IAEZ,qDAAqD;IACrD,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,IAAI,CACnB,OAAO,CAAC,QAAQ,CAAC,EACjB,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,UAAU,EAAE,CAClD,CAAC;IAEF,IAAI,CAAC;QACH,iCAAiC;QACjC,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEpD,qBAAqB;QACrB,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAE9C,6DAA6D;QAC7D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YACzB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,kCAAkC;gBAClC,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACrD,MAAM,GAAG,CAAC;gBACZ,CAAC;YACH,CAAC;QACH,CAAC;QAED,qDAAqD;QACrD,MAAM,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gCAAgC;QAChC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides structured error handling with logging before fallbacks.
|
|
5
|
+
* Replaces silent .catch(() => '') patterns with logged fallbacks.
|
|
6
|
+
*/
|
|
7
|
+
export interface FallbackContext {
|
|
8
|
+
/** Name of the operation that failed */
|
|
9
|
+
operation: string;
|
|
10
|
+
/** File path or other identifier */
|
|
11
|
+
path?: string;
|
|
12
|
+
/** Additional context */
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface FallbackOptions {
|
|
16
|
+
/** Whether to log warnings (default: true) */
|
|
17
|
+
logWarning?: boolean;
|
|
18
|
+
/** Custom logger function */
|
|
19
|
+
logger?: (message: string, context: FallbackContext, error: unknown) => void;
|
|
20
|
+
/** Whether to include stack trace in logs (default: false in production) */
|
|
21
|
+
includeStack?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Execute an async operation with a fallback value, logging errors before fallback.
|
|
25
|
+
*
|
|
26
|
+
* This replaces patterns like:
|
|
27
|
+
* ```ts
|
|
28
|
+
* const content = await readFile(path).catch(() => '');
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* With structured error handling:
|
|
32
|
+
* ```ts
|
|
33
|
+
* const content = await withFallback(
|
|
34
|
+
* () => readFile(path, 'utf-8'),
|
|
35
|
+
* '',
|
|
36
|
+
* { operation: 'readStateFile', path }
|
|
37
|
+
* );
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param operation - Async operation to execute
|
|
41
|
+
* @param fallback - Value to return if operation fails
|
|
42
|
+
* @param context - Context for logging
|
|
43
|
+
* @param options - Options for error handling
|
|
44
|
+
* @returns Result of operation or fallback value
|
|
45
|
+
*/
|
|
46
|
+
export declare function withFallback<T>(operation: () => Promise<T>, fallback: T, context: FallbackContext, options?: FallbackOptions): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Synchronous version of withFallback.
|
|
49
|
+
*/
|
|
50
|
+
export declare function withFallbackSync<T>(operation: () => T, fallback: T, context: FallbackContext, options?: FallbackOptions): T;
|
|
51
|
+
/**
|
|
52
|
+
* Execute an operation with retry logic and exponential backoff.
|
|
53
|
+
*
|
|
54
|
+
* @param operation - Async operation to execute
|
|
55
|
+
* @param context - Context for logging
|
|
56
|
+
* @param maxRetries - Maximum number of retries (default: 3)
|
|
57
|
+
* @param baseDelayMs - Base delay between retries in ms (default: 100)
|
|
58
|
+
* @returns Result of operation
|
|
59
|
+
* @throws Last error if all retries fail
|
|
60
|
+
*/
|
|
61
|
+
export declare function withRetry<T>(operation: () => Promise<T>, context: FallbackContext, maxRetries?: number, baseDelayMs?: number): Promise<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Create a logged fallback wrapper for a specific operation.
|
|
64
|
+
*
|
|
65
|
+
* @param operationName - Name of the operation
|
|
66
|
+
* @param fallbackValue - Default fallback value
|
|
67
|
+
* @returns A function that wraps operations with logging
|
|
68
|
+
*/
|
|
69
|
+
export declare function createFallbackWrapper<T>(operationName: string, fallbackValue: T): (operation: () => Promise<T>, path?: string) => Promise<T>;
|
|
70
|
+
//# sourceMappingURL=error-handling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handling.d.ts","sourceRoot":"","sources":["../../src/utils/error-handling.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7E,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAcD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,SAAS,EAAE,MAAM,CAAC,EAClB,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,eAAoB,GAC5B,CAAC,CAWH;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,eAAe,EACxB,UAAU,GAAE,MAAU,EACtB,WAAW,GAAE,MAAY,GACxB,OAAO,CAAC,CAAC,CAAC,CAqBZ;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,CAAC,GACf,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAO5D"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides structured error handling with logging before fallbacks.
|
|
5
|
+
* Replaces silent .catch(() => '') patterns with logged fallbacks.
|
|
6
|
+
*/
|
|
7
|
+
const defaultLogger = (message, context, error) => {
|
|
8
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9
|
+
console.warn(`[${context.operation}] ${message}: ${errorMessage}`, {
|
|
10
|
+
path: context.path,
|
|
11
|
+
...context.details,
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Execute an async operation with a fallback value, logging errors before fallback.
|
|
16
|
+
*
|
|
17
|
+
* This replaces patterns like:
|
|
18
|
+
* ```ts
|
|
19
|
+
* const content = await readFile(path).catch(() => '');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* With structured error handling:
|
|
23
|
+
* ```ts
|
|
24
|
+
* const content = await withFallback(
|
|
25
|
+
* () => readFile(path, 'utf-8'),
|
|
26
|
+
* '',
|
|
27
|
+
* { operation: 'readStateFile', path }
|
|
28
|
+
* );
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @param operation - Async operation to execute
|
|
32
|
+
* @param fallback - Value to return if operation fails
|
|
33
|
+
* @param context - Context for logging
|
|
34
|
+
* @param options - Options for error handling
|
|
35
|
+
* @returns Result of operation or fallback value
|
|
36
|
+
*/
|
|
37
|
+
export async function withFallback(operation, fallback, context, options = {}) {
|
|
38
|
+
const { logWarning = true, logger = defaultLogger } = options;
|
|
39
|
+
try {
|
|
40
|
+
return await operation();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (logWarning) {
|
|
44
|
+
logger('Operation failed, using fallback', context, error);
|
|
45
|
+
}
|
|
46
|
+
return fallback;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Synchronous version of withFallback.
|
|
51
|
+
*/
|
|
52
|
+
export function withFallbackSync(operation, fallback, context, options = {}) {
|
|
53
|
+
const { logWarning = true, logger = defaultLogger } = options;
|
|
54
|
+
try {
|
|
55
|
+
return operation();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (logWarning) {
|
|
59
|
+
logger('Operation failed, using fallback', context, error);
|
|
60
|
+
}
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Execute an operation with retry logic and exponential backoff.
|
|
66
|
+
*
|
|
67
|
+
* @param operation - Async operation to execute
|
|
68
|
+
* @param context - Context for logging
|
|
69
|
+
* @param maxRetries - Maximum number of retries (default: 3)
|
|
70
|
+
* @param baseDelayMs - Base delay between retries in ms (default: 100)
|
|
71
|
+
* @returns Result of operation
|
|
72
|
+
* @throws Last error if all retries fail
|
|
73
|
+
*/
|
|
74
|
+
export async function withRetry(operation, context, maxRetries = 3, baseDelayMs = 100) {
|
|
75
|
+
let lastError;
|
|
76
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
77
|
+
try {
|
|
78
|
+
return await operation();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
lastError = error;
|
|
82
|
+
if (attempt < maxRetries) {
|
|
83
|
+
const delay = baseDelayMs * Math.pow(2, attempt);
|
|
84
|
+
console.warn(`[${context.operation}] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, { path: context.path, error: error instanceof Error ? error.message : String(error) });
|
|
85
|
+
await sleep(delay);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw lastError;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a logged fallback wrapper for a specific operation.
|
|
93
|
+
*
|
|
94
|
+
* @param operationName - Name of the operation
|
|
95
|
+
* @param fallbackValue - Default fallback value
|
|
96
|
+
* @returns A function that wraps operations with logging
|
|
97
|
+
*/
|
|
98
|
+
export function createFallbackWrapper(operationName, fallbackValue) {
|
|
99
|
+
return async (operation, path) => {
|
|
100
|
+
return withFallback(operation, fallbackValue, {
|
|
101
|
+
operation: operationName,
|
|
102
|
+
path,
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function sleep(ms) {
|
|
107
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=error-handling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handling.js","sourceRoot":"","sources":["../../src/utils/error-handling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Lock Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides cross-platform file locking with retry logic.
|
|
5
|
+
* Uses proper-lockfile for reliable locking behavior.
|
|
6
|
+
*/
|
|
7
|
+
export interface LockOptions {
|
|
8
|
+
/** Maximum time to wait for lock (ms) */
|
|
9
|
+
timeout?: number;
|
|
10
|
+
/** Time between retry attempts (ms) */
|
|
11
|
+
retryInterval?: number;
|
|
12
|
+
/** Maximum number of retries */
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
/** Stale lock timeout (ms) - locks older than this are considered stale */
|
|
15
|
+
stale?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class FileLockError extends Error {
|
|
18
|
+
readonly filePath: string;
|
|
19
|
+
readonly code: 'ELOCKED' | 'ETIMEOUT' | 'EUNLOCK';
|
|
20
|
+
constructor(message: string, filePath: string, code: 'ELOCKED' | 'ETIMEOUT' | 'EUNLOCK');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Acquire a lock on a file, execute an operation, then release the lock.
|
|
24
|
+
*
|
|
25
|
+
* @param filePath - Path to the file to lock
|
|
26
|
+
* @param operation - Async operation to perform while holding the lock
|
|
27
|
+
* @param options - Lock options
|
|
28
|
+
* @returns Result of the operation
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const result = await withFileLock('/path/to/file.json', async () => {
|
|
33
|
+
* const data = await readFile('/path/to/file.json', 'utf-8');
|
|
34
|
+
* const parsed = JSON.parse(data);
|
|
35
|
+
* parsed.count++;
|
|
36
|
+
* await writeFile('/path/to/file.json', JSON.stringify(parsed));
|
|
37
|
+
* return parsed;
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function withFileLock<T>(filePath: string, operation: () => Promise<T>, options?: LockOptions): Promise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Check if a file is currently locked.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isFileLocked(filePath: string): boolean;
|
|
46
|
+
//# sourceMappingURL=file-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD,qBAAa,aAAc,SAAQ,KAAK;aAGpB,QAAQ,EAAE,MAAM;aAChB,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS;gBAFxD,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS;CAK3D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,CAAC,CAAC,CA2EZ;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGtD"}
|