gsd-agent 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.
Files changed (155) hide show
  1. package/README.md +221 -0
  2. package/bin/cli.js +313 -0
  3. package/dist/auth-flow.d.ts +50 -0
  4. package/dist/auth-flow.d.ts.map +1 -0
  5. package/dist/auth-flow.js +233 -0
  6. package/dist/auth-flow.js.map +1 -0
  7. package/dist/auth.d.ts +42 -0
  8. package/dist/auth.d.ts.map +1 -0
  9. package/dist/auth.js +117 -0
  10. package/dist/auth.js.map +1 -0
  11. package/dist/command-executor.d.ts +44 -0
  12. package/dist/command-executor.d.ts.map +1 -0
  13. package/dist/command-executor.js +193 -0
  14. package/dist/command-executor.js.map +1 -0
  15. package/dist/command-executor.test.d.ts +8 -0
  16. package/dist/command-executor.test.d.ts.map +1 -0
  17. package/dist/command-executor.test.js +87 -0
  18. package/dist/command-executor.test.js.map +1 -0
  19. package/dist/command-queue.d.ts +44 -0
  20. package/dist/command-queue.d.ts.map +1 -0
  21. package/dist/command-queue.js +184 -0
  22. package/dist/command-queue.js.map +1 -0
  23. package/dist/command-queue.test.d.ts +7 -0
  24. package/dist/command-queue.test.d.ts.map +1 -0
  25. package/dist/command-queue.test.js +220 -0
  26. package/dist/command-queue.test.js.map +1 -0
  27. package/dist/config.d.ts +25 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +103 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/conflict-resolver.d.ts +43 -0
  32. package/dist/conflict-resolver.d.ts.map +1 -0
  33. package/dist/conflict-resolver.js +91 -0
  34. package/dist/conflict-resolver.js.map +1 -0
  35. package/dist/conflict-resolver.test.d.ts +7 -0
  36. package/dist/conflict-resolver.test.d.ts.map +1 -0
  37. package/dist/conflict-resolver.test.js +123 -0
  38. package/dist/conflict-resolver.test.js.map +1 -0
  39. package/dist/discovery.d.ts +59 -0
  40. package/dist/discovery.d.ts.map +1 -0
  41. package/dist/discovery.js +180 -0
  42. package/dist/discovery.js.map +1 -0
  43. package/dist/discovery.test.d.ts +8 -0
  44. package/dist/discovery.test.d.ts.map +1 -0
  45. package/dist/discovery.test.js +132 -0
  46. package/dist/discovery.test.js.map +1 -0
  47. package/dist/hash.d.ts +20 -0
  48. package/dist/hash.d.ts.map +1 -0
  49. package/dist/hash.js +35 -0
  50. package/dist/hash.js.map +1 -0
  51. package/dist/hash.test.d.ts +7 -0
  52. package/dist/hash.test.d.ts.map +1 -0
  53. package/dist/hash.test.js +58 -0
  54. package/dist/hash.test.js.map +1 -0
  55. package/dist/index.d.ts +11 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +202 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/integration.test.d.ts +8 -0
  60. package/dist/integration.test.d.ts.map +1 -0
  61. package/dist/integration.test.js +37 -0
  62. package/dist/integration.test.js.map +1 -0
  63. package/dist/logger.d.ts +68 -0
  64. package/dist/logger.d.ts.map +1 -0
  65. package/dist/logger.js +159 -0
  66. package/dist/logger.js.map +1 -0
  67. package/dist/output-streamer.d.ts +27 -0
  68. package/dist/output-streamer.d.ts.map +1 -0
  69. package/dist/output-streamer.js +71 -0
  70. package/dist/output-streamer.js.map +1 -0
  71. package/dist/output-streamer.test.d.ts +7 -0
  72. package/dist/output-streamer.test.d.ts.map +1 -0
  73. package/dist/output-streamer.test.js +90 -0
  74. package/dist/output-streamer.test.js.map +1 -0
  75. package/dist/realtime-subscriber.d.ts +63 -0
  76. package/dist/realtime-subscriber.d.ts.map +1 -0
  77. package/dist/realtime-subscriber.js +201 -0
  78. package/dist/realtime-subscriber.js.map +1 -0
  79. package/dist/realtime-subscriber.test.d.ts +7 -0
  80. package/dist/realtime-subscriber.test.d.ts.map +1 -0
  81. package/dist/realtime-subscriber.test.js +183 -0
  82. package/dist/realtime-subscriber.test.js.map +1 -0
  83. package/dist/reconnection-manager.d.ts +88 -0
  84. package/dist/reconnection-manager.d.ts.map +1 -0
  85. package/dist/reconnection-manager.js +229 -0
  86. package/dist/reconnection-manager.js.map +1 -0
  87. package/dist/reconnection-manager.test.d.ts +8 -0
  88. package/dist/reconnection-manager.test.d.ts.map +1 -0
  89. package/dist/reconnection-manager.test.js +151 -0
  90. package/dist/reconnection-manager.test.js.map +1 -0
  91. package/dist/remote-sync-handler.d.ts +61 -0
  92. package/dist/remote-sync-handler.d.ts.map +1 -0
  93. package/dist/remote-sync-handler.js +197 -0
  94. package/dist/remote-sync-handler.js.map +1 -0
  95. package/dist/remote-sync-handler.test.d.ts +7 -0
  96. package/dist/remote-sync-handler.test.d.ts.map +1 -0
  97. package/dist/remote-sync-handler.test.js +212 -0
  98. package/dist/remote-sync-handler.test.js.map +1 -0
  99. package/dist/retry.d.ts +35 -0
  100. package/dist/retry.d.ts.map +1 -0
  101. package/dist/retry.js +63 -0
  102. package/dist/retry.js.map +1 -0
  103. package/dist/retry.test.d.ts +5 -0
  104. package/dist/retry.test.d.ts.map +1 -0
  105. package/dist/retry.test.js +84 -0
  106. package/dist/retry.test.js.map +1 -0
  107. package/dist/storage-client.d.ts +69 -0
  108. package/dist/storage-client.d.ts.map +1 -0
  109. package/dist/storage-client.js +168 -0
  110. package/dist/storage-client.js.map +1 -0
  111. package/dist/storage-client.test.d.ts +7 -0
  112. package/dist/storage-client.test.d.ts.map +1 -0
  113. package/dist/storage-client.test.js +126 -0
  114. package/dist/storage-client.test.js.map +1 -0
  115. package/dist/supabase.d.ts +82 -0
  116. package/dist/supabase.d.ts.map +1 -0
  117. package/dist/supabase.js +341 -0
  118. package/dist/supabase.js.map +1 -0
  119. package/dist/supabase.test.d.ts +7 -0
  120. package/dist/supabase.test.d.ts.map +1 -0
  121. package/dist/supabase.test.js +273 -0
  122. package/dist/supabase.test.js.map +1 -0
  123. package/dist/sync-engine.d.ts +84 -0
  124. package/dist/sync-engine.d.ts.map +1 -0
  125. package/dist/sync-engine.js +251 -0
  126. package/dist/sync-engine.js.map +1 -0
  127. package/dist/sync-engine.test.d.ts +7 -0
  128. package/dist/sync-engine.test.d.ts.map +1 -0
  129. package/dist/sync-engine.test.js +241 -0
  130. package/dist/sync-engine.test.js.map +1 -0
  131. package/dist/sync-state.d.ts +82 -0
  132. package/dist/sync-state.d.ts.map +1 -0
  133. package/dist/sync-state.js +145 -0
  134. package/dist/sync-state.js.map +1 -0
  135. package/dist/sync-state.test.d.ts +7 -0
  136. package/dist/sync-state.test.d.ts.map +1 -0
  137. package/dist/sync-state.test.js +129 -0
  138. package/dist/sync-state.test.js.map +1 -0
  139. package/dist/types.d.ts +148 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +8 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/types.test.d.ts +7 -0
  144. package/dist/types.test.d.ts.map +1 -0
  145. package/dist/types.test.js +73 -0
  146. package/dist/types.test.js.map +1 -0
  147. package/dist/watcher.d.ts +55 -0
  148. package/dist/watcher.d.ts.map +1 -0
  149. package/dist/watcher.js +214 -0
  150. package/dist/watcher.js.map +1 -0
  151. package/dist/watcher.test.d.ts +8 -0
  152. package/dist/watcher.test.d.ts.map +1 -0
  153. package/dist/watcher.test.js +164 -0
  154. package/dist/watcher.test.js.map +1 -0
  155. package/package.json +58 -0
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Tests for SupabaseClient
3
+ *
4
+ * Covers database operations and Storage integration for large files.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { SupabaseClient } from './supabase.js';
8
+ describe('SupabaseClient', () => {
9
+ let mockConfig;
10
+ let mockLogger;
11
+ let mockStorageClient;
12
+ let supabaseClient;
13
+ beforeEach(() => {
14
+ mockConfig = {
15
+ supabase_url: 'https://test.supabase.co',
16
+ supabase_key: 'test-key',
17
+ watch_dirs: [],
18
+ ignored_patterns: [],
19
+ debounce_ms: 300,
20
+ batch_size: 50,
21
+ log_level: 'INFO',
22
+ log_file: '/tmp/test.log',
23
+ log_rotation_days: 7
24
+ };
25
+ mockLogger = {
26
+ error: vi.fn(),
27
+ warn: vi.fn(),
28
+ info: vi.fn(),
29
+ debug: vi.fn()
30
+ };
31
+ mockStorageClient = {
32
+ uploadFile: vi.fn(),
33
+ downloadFile: vi.fn(),
34
+ deleteFile: vi.fn(),
35
+ getBucketInfo: vi.fn()
36
+ };
37
+ // Note: createSupabaseClient will be updated to accept StorageClient
38
+ // For now, we'll test the class directly
39
+ });
40
+ describe('syncFile with Storage integration', () => {
41
+ it('should use database for files ≤200KB', async () => {
42
+ const mockClient = {
43
+ from: vi.fn().mockReturnValue({
44
+ upsert: vi.fn().mockResolvedValue({ error: null })
45
+ })
46
+ };
47
+ const client = new SupabaseClient(mockConfig, mockLogger, mockStorageClient, 'test-user-id');
48
+ // @ts-ignore - accessing private field for testing
49
+ client.client = mockClient;
50
+ const event = {
51
+ id: 'file-1',
52
+ workspace_id: 'workspace-1',
53
+ file_path: '.planning/STATE.md',
54
+ event_type: 'change',
55
+ content_hash: 'hash123',
56
+ content: 'small content',
57
+ source: 'cli',
58
+ timestamp: '2026-03-27T01:00:00Z',
59
+ metadata: {
60
+ size: 100 * 1024, // 100KB
61
+ mtime: '2026-03-27T01:00:00Z',
62
+ agent_version: '0.1.0'
63
+ }
64
+ };
65
+ const result = await client.syncFile(event);
66
+ expect(result.success).toBe(true);
67
+ expect(mockClient.from).toHaveBeenCalledWith('files');
68
+ expect(mockStorageClient.uploadFile).not.toHaveBeenCalled();
69
+ });
70
+ it('should use Storage for files >200KB', async () => {
71
+ const mockClient = {
72
+ from: vi.fn().mockReturnValue({
73
+ upsert: vi.fn().mockResolvedValue({ error: null })
74
+ })
75
+ };
76
+ const client = new SupabaseClient(mockConfig, mockLogger, mockStorageClient, 'test-user-id');
77
+ // @ts-ignore - accessing private field for testing
78
+ client.client = mockClient;
79
+ const largeContent = 'x'.repeat(250 * 1024); // 250KB
80
+ const event = {
81
+ id: 'file-2',
82
+ workspace_id: 'workspace-1',
83
+ file_path: '.planning/LARGE.md',
84
+ event_type: 'change',
85
+ content_hash: 'hash456',
86
+ content: largeContent,
87
+ source: 'cli',
88
+ timestamp: '2026-03-27T01:00:00Z',
89
+ metadata: {
90
+ size: 250 * 1024, // 250KB
91
+ mtime: '2026-03-27T01:00:00Z',
92
+ agent_version: '0.1.0'
93
+ }
94
+ };
95
+ vi.mocked(mockStorageClient.uploadFile).mockResolvedValue({
96
+ success: true,
97
+ storage_url: 'https://storage.example.com/workspace-1/.planning/LARGE.md'
98
+ });
99
+ const result = await client.syncFile(event);
100
+ expect(result.success).toBe(true);
101
+ expect(mockStorageClient.uploadFile).toHaveBeenCalledWith('workspace-1', '.planning/LARGE.md', largeContent);
102
+ });
103
+ it('should store storage_url in database for large files', async () => {
104
+ const mockUpsert = vi.fn().mockResolvedValue({ error: null });
105
+ const mockClient = {
106
+ from: vi.fn().mockReturnValue({
107
+ upsert: mockUpsert
108
+ })
109
+ };
110
+ const client = new SupabaseClient(mockConfig, mockLogger, mockStorageClient, 'test-user-id');
111
+ // @ts-ignore - accessing private field for testing
112
+ client.client = mockClient;
113
+ const largeContent = 'x'.repeat(250 * 1024); // 250KB
114
+ const storageUrl = 'https://storage.example.com/workspace-1/.planning/LARGE.md';
115
+ const event = {
116
+ id: 'file-3',
117
+ workspace_id: 'workspace-1',
118
+ file_path: '.planning/LARGE.md',
119
+ event_type: 'change',
120
+ content_hash: 'hash789',
121
+ content: largeContent,
122
+ source: 'cli',
123
+ timestamp: '2026-03-27T01:00:00Z',
124
+ metadata: {
125
+ size: 250 * 1024,
126
+ mtime: '2026-03-27T01:00:00Z',
127
+ agent_version: '0.1.0'
128
+ }
129
+ };
130
+ vi.mocked(mockStorageClient.uploadFile).mockResolvedValue({
131
+ success: true,
132
+ storage_url: storageUrl
133
+ });
134
+ await client.syncFile(event);
135
+ expect(mockUpsert).toHaveBeenCalledWith({
136
+ id: 'file-3',
137
+ workspace_id: 'workspace-1',
138
+ file_path: '.planning/LARGE.md',
139
+ content: null,
140
+ content_hash: 'hash789',
141
+ size: 250 * 1024,
142
+ updated_at: '2026-03-27T01:00:00Z',
143
+ storage_url: storageUrl
144
+ });
145
+ });
146
+ it('should set content=null in database for large files', async () => {
147
+ const mockUpsert = vi.fn().mockResolvedValue({ error: null });
148
+ const mockClient = {
149
+ from: vi.fn().mockReturnValue({
150
+ upsert: mockUpsert
151
+ })
152
+ };
153
+ const client = new SupabaseClient(mockConfig, mockLogger, mockStorageClient, 'test-user-id');
154
+ // @ts-ignore - accessing private field for testing
155
+ client.client = mockClient;
156
+ const largeContent = 'x'.repeat(250 * 1024);
157
+ const event = {
158
+ id: 'file-4',
159
+ workspace_id: 'workspace-1',
160
+ file_path: '.planning/LARGE.md',
161
+ event_type: 'change',
162
+ content_hash: 'hash999',
163
+ content: largeContent,
164
+ source: 'cli',
165
+ timestamp: '2026-03-27T01:00:00Z',
166
+ metadata: {
167
+ size: 250 * 1024,
168
+ mtime: '2026-03-27T01:00:00Z',
169
+ agent_version: '0.1.0'
170
+ }
171
+ };
172
+ vi.mocked(mockStorageClient.uploadFile).mockResolvedValue({
173
+ success: true,
174
+ storage_url: 'https://storage.example.com/file'
175
+ });
176
+ await client.syncFile(event);
177
+ const upsertCall = mockUpsert.mock.calls[0][0];
178
+ expect(upsertCall.content).toBeNull();
179
+ });
180
+ it('should fall back to database if Storage upload fails and size <200KB', async () => {
181
+ const mockUpsert = vi.fn().mockResolvedValue({ error: null });
182
+ const mockClient = {
183
+ from: vi.fn().mockReturnValue({
184
+ upsert: mockUpsert
185
+ })
186
+ };
187
+ const client = new SupabaseClient(mockConfig, mockLogger, mockStorageClient, 'test-user-id');
188
+ // @ts-ignore - accessing private field for testing
189
+ client.client = mockClient;
190
+ const content = 'x'.repeat(150 * 1024); // 150KB (under 200KB threshold)
191
+ const event = {
192
+ id: 'file-5',
193
+ workspace_id: 'workspace-1',
194
+ file_path: '.planning/MEDIUM.md',
195
+ event_type: 'change',
196
+ content_hash: 'hash111',
197
+ content: content,
198
+ source: 'cli',
199
+ timestamp: '2026-03-27T01:00:00Z',
200
+ metadata: {
201
+ size: 150 * 1024,
202
+ mtime: '2026-03-27T01:00:00Z',
203
+ agent_version: '0.1.0'
204
+ }
205
+ };
206
+ // Storage upload fails
207
+ vi.mocked(mockStorageClient.uploadFile).mockResolvedValue({
208
+ success: false,
209
+ error: 'Storage error'
210
+ });
211
+ const result = await client.syncFile(event);
212
+ // Should fall back to database since size < 200KB
213
+ expect(result.success).toBe(true);
214
+ const upsertCall = mockUpsert.mock.calls[0][0];
215
+ expect(upsertCall.content).toBe(content);
216
+ expect(upsertCall.storage_url).toBeUndefined();
217
+ });
218
+ });
219
+ describe('syncFileBatch with Storage integration', () => {
220
+ it('should split large and small files into separate processing paths', async () => {
221
+ const mockUpsert = vi.fn().mockResolvedValue({ error: null });
222
+ const mockClient = {
223
+ from: vi.fn().mockReturnValue({
224
+ upsert: mockUpsert
225
+ })
226
+ };
227
+ const client = new SupabaseClient(mockConfig, mockLogger, mockStorageClient, 'test-user-id');
228
+ // @ts-ignore - accessing private field for testing
229
+ client.client = mockClient;
230
+ const smallFile = {
231
+ id: 'small-1',
232
+ workspace_id: 'workspace-1',
233
+ file_path: '.planning/SMALL.md',
234
+ event_type: 'change',
235
+ content_hash: 'hash-small',
236
+ content: 'small',
237
+ source: 'cli',
238
+ timestamp: '2026-03-27T01:00:00Z',
239
+ metadata: {
240
+ size: 50 * 1024, // 50KB
241
+ mtime: '2026-03-27T01:00:00Z',
242
+ agent_version: '0.1.0'
243
+ }
244
+ };
245
+ const largeFile = {
246
+ id: 'large-1',
247
+ workspace_id: 'workspace-1',
248
+ file_path: '.planning/LARGE.md',
249
+ event_type: 'change',
250
+ content_hash: 'hash-large',
251
+ content: 'x'.repeat(250 * 1024),
252
+ source: 'cli',
253
+ timestamp: '2026-03-27T01:00:00Z',
254
+ metadata: {
255
+ size: 250 * 1024, // 250KB
256
+ mtime: '2026-03-27T01:00:00Z',
257
+ agent_version: '0.1.0'
258
+ }
259
+ };
260
+ vi.mocked(mockStorageClient.uploadFile).mockResolvedValue({
261
+ success: true,
262
+ storage_url: 'https://storage.example.com/file'
263
+ });
264
+ const result = await client.syncFileBatch([smallFile, largeFile]);
265
+ expect(result.success).toBe(true);
266
+ // Small file goes to database with content
267
+ // Large file goes to Storage first, then database with storage_url
268
+ expect(mockStorageClient.uploadFile).toHaveBeenCalledTimes(1);
269
+ expect(mockUpsert).toHaveBeenCalledTimes(2);
270
+ });
271
+ });
272
+ });
273
+ //# sourceMappingURL=supabase.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase.test.js","sourceRoot":"","sources":["../src/supabase.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Sync Engine for GSD Agent
3
+ *
4
+ * Orchestrates filesystem watching and cloud sync with batching, debouncing,
5
+ * priority queue management, and event storm detection.
6
+ */
7
+ import { FileWatcher } from './watcher.js';
8
+ import { SupabaseClient } from './supabase.js';
9
+ import { Logger } from './logger.js';
10
+ import type { SyncConfig, AgentState } from './types.js';
11
+ /**
12
+ * SyncEngine orchestrates sync between filesystem and Supabase
13
+ *
14
+ * Features:
15
+ * - Batches multiple file changes into single transactions
16
+ * - Debounces rapid changes to prevent rate limit exhaustion
17
+ * - Prioritizes STATE.md and ROADMAP.md changes
18
+ * - Tracks synced file hashes to avoid duplicate syncs
19
+ * - Detects event storms (>100 events/sec) and pauses
20
+ * - Sends heartbeat to Supabase every 30 seconds
21
+ * - Flushes pending changes on graceful shutdown
22
+ */
23
+ export declare class SyncEngine {
24
+ private watcher;
25
+ private supabase;
26
+ private config;
27
+ private logger;
28
+ private state;
29
+ private debounceTimer;
30
+ private heartbeatTimer;
31
+ private syncedHashes;
32
+ private eventCount;
33
+ private eventWindowStart;
34
+ private isStormPaused;
35
+ constructor(watcher: FileWatcher, supabase: SupabaseClient, config: SyncConfig, logger: Logger);
36
+ /**
37
+ * Start sync engine and subscribe to watcher events
38
+ */
39
+ start(): void;
40
+ /**
41
+ * Stop sync engine and flush pending changes
42
+ */
43
+ stop(): Promise<void>;
44
+ /**
45
+ * Get current agent state for monitoring
46
+ */
47
+ getState(): AgentState;
48
+ /**
49
+ * Enqueue file change event for sync
50
+ *
51
+ * Implements:
52
+ * - Duplicate detection via content hash
53
+ * - Priority queue (STATE.md and ROADMAP.md first)
54
+ * - Debouncing to batch rapid changes
55
+ */
56
+ private enqueue;
57
+ /**
58
+ * Flush sync queue to Supabase
59
+ *
60
+ * Implements:
61
+ * - Batch size limiting (max batch_size files per batch)
62
+ * - Retry on failure (re-queue failed events)
63
+ * - Automatic continuation if more events in queue
64
+ */
65
+ private flush;
66
+ /**
67
+ * Check for event storm and pause if detected
68
+ *
69
+ * Event storm = >100 events per second
70
+ * When detected, pause for 5 seconds and log warning
71
+ *
72
+ * @returns True if currently in storm pause
73
+ */
74
+ private checkEventStorm;
75
+ /**
76
+ * Send heartbeat to Supabase to maintain connection
77
+ */
78
+ private sendHeartbeat;
79
+ }
80
+ /**
81
+ * Factory function to create sync engine
82
+ */
83
+ export declare function createSyncEngine(watcher: FileWatcher, supabase: SupabaseClient, config: SyncConfig, logger: Logger): SyncEngine;
84
+ //# sourceMappingURL=sync-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../src/sync-engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,KAAK,EAAE,UAAU,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAA;AAEzE;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAQ;IAGtB,OAAO,CAAC,KAAK,CAKZ;IAGD,OAAO,CAAC,aAAa,CAA8B;IAGnD,OAAO,CAAC,cAAc,CAA8B;IAGpD,OAAO,CAAC,YAAY,CAAiC;IAGrD,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,aAAa,CAAQ;gBAG3B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM;IAQhB;;OAEG;IACH,KAAK,IAAI,IAAI;IAqBb;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B;;OAEG;IACH,QAAQ,IAAI,UAAU;IAStB;;;;;;;OAOG;YACW,OAAO;IA6BrB;;;;;;;OAOG;YACW,KAAK;IAqDnB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAkCvB;;OAEG;YACW,aAAa;CAa5B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,UAAU,CAEZ"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Sync Engine for GSD Agent
3
+ *
4
+ * Orchestrates filesystem watching and cloud sync with batching, debouncing,
5
+ * priority queue management, and event storm detection.
6
+ */
7
+ /**
8
+ * SyncEngine orchestrates sync between filesystem and Supabase
9
+ *
10
+ * Features:
11
+ * - Batches multiple file changes into single transactions
12
+ * - Debounces rapid changes to prevent rate limit exhaustion
13
+ * - Prioritizes STATE.md and ROADMAP.md changes
14
+ * - Tracks synced file hashes to avoid duplicate syncs
15
+ * - Detects event storms (>100 events/sec) and pauses
16
+ * - Sends heartbeat to Supabase every 30 seconds
17
+ * - Flushes pending changes on graceful shutdown
18
+ */
19
+ export class SyncEngine {
20
+ watcher;
21
+ supabase;
22
+ config;
23
+ logger;
24
+ // Internal state
25
+ state = {
26
+ workspaces: new Map(),
27
+ sync_queue: [],
28
+ is_syncing: false,
29
+ last_heartbeat: new Date().toISOString()
30
+ };
31
+ // Debounce timer
32
+ debounceTimer = null;
33
+ // Heartbeat timer
34
+ heartbeatTimer = null;
35
+ // Track synced file hashes to avoid duplicates
36
+ syncedHashes = new Map();
37
+ // Event storm detection
38
+ eventCount = 0;
39
+ eventWindowStart = Date.now();
40
+ isStormPaused = false;
41
+ constructor(watcher, supabase, config, logger) {
42
+ this.watcher = watcher;
43
+ this.supabase = supabase;
44
+ this.config = config;
45
+ this.logger = logger;
46
+ }
47
+ /**
48
+ * Start sync engine and subscribe to watcher events
49
+ */
50
+ start() {
51
+ // Subscribe to file change events
52
+ this.watcher.on('change', async (event) => {
53
+ if (this.checkEventStorm()) {
54
+ // Drop events during storm
55
+ return;
56
+ }
57
+ await this.enqueue(event);
58
+ });
59
+ // Start watcher
60
+ this.watcher.start();
61
+ // Start heartbeat timer (every 30s)
62
+ this.heartbeatTimer = setInterval(() => {
63
+ void this.sendHeartbeat();
64
+ }, 30000);
65
+ this.logger.info('Sync engine started');
66
+ }
67
+ /**
68
+ * Stop sync engine and flush pending changes
69
+ */
70
+ async stop() {
71
+ this.logger.info('Stopping sync engine, flushing pending changes');
72
+ // Stop watcher
73
+ this.watcher.stop();
74
+ // Stop heartbeat
75
+ if (this.heartbeatTimer) {
76
+ clearInterval(this.heartbeatTimer);
77
+ this.heartbeatTimer = null;
78
+ }
79
+ // Clear debounce timer
80
+ if (this.debounceTimer) {
81
+ clearTimeout(this.debounceTimer);
82
+ this.debounceTimer = null;
83
+ }
84
+ // Flush remaining queue
85
+ while (this.state.sync_queue.length > 0) {
86
+ await this.flush();
87
+ }
88
+ this.logger.info('Sync engine stopped');
89
+ }
90
+ /**
91
+ * Get current agent state for monitoring
92
+ */
93
+ getState() {
94
+ return {
95
+ workspaces: new Map(this.state.workspaces),
96
+ sync_queue: [...this.state.sync_queue],
97
+ is_syncing: this.state.is_syncing,
98
+ last_heartbeat: this.state.last_heartbeat
99
+ };
100
+ }
101
+ /**
102
+ * Enqueue file change event for sync
103
+ *
104
+ * Implements:
105
+ * - Duplicate detection via content hash
106
+ * - Priority queue (STATE.md and ROADMAP.md first)
107
+ * - Debouncing to batch rapid changes
108
+ */
109
+ async enqueue(event) {
110
+ // Skip if already synced with same hash (duplicate detection)
111
+ const key = `${event.workspace_id}:${event.file_path}`;
112
+ if (this.syncedHashes.get(key) === event.content_hash) {
113
+ this.logger.debug('Skipping duplicate sync', { file_path: event.file_path });
114
+ return;
115
+ }
116
+ // Add to appropriate queue based on priority
117
+ if (event.file_path.endsWith('STATE.md') || event.file_path.endsWith('ROADMAP.md')) {
118
+ // High priority - add to front
119
+ this.state.sync_queue.unshift(event);
120
+ this.logger.debug('Enqueued high priority file', { file_path: event.file_path });
121
+ }
122
+ else {
123
+ // Normal priority - add to back
124
+ this.state.sync_queue.push(event);
125
+ this.logger.debug('Enqueued file', { file_path: event.file_path });
126
+ }
127
+ // Debounce: reset timer on each new event
128
+ if (this.debounceTimer) {
129
+ clearTimeout(this.debounceTimer);
130
+ }
131
+ this.debounceTimer = setTimeout(() => {
132
+ void this.flush();
133
+ }, this.config.debounce_ms);
134
+ }
135
+ /**
136
+ * Flush sync queue to Supabase
137
+ *
138
+ * Implements:
139
+ * - Batch size limiting (max batch_size files per batch)
140
+ * - Retry on failure (re-queue failed events)
141
+ * - Automatic continuation if more events in queue
142
+ */
143
+ async flush() {
144
+ if (this.state.is_syncing || this.state.sync_queue.length === 0) {
145
+ return;
146
+ }
147
+ this.state.is_syncing = true;
148
+ // Take batch from queue (max batch_size)
149
+ const rawBatch = this.state.sync_queue.splice(0, this.config.batch_size);
150
+ // Deduplicate: keep only the latest event for each workspace_id:file_path
151
+ const deduped = new Map();
152
+ for (const event of rawBatch) {
153
+ const key = `${event.workspace_id}:${event.file_path}`;
154
+ deduped.set(key, event); // Later events overwrite earlier ones
155
+ }
156
+ const batch = Array.from(deduped.values());
157
+ try {
158
+ const result = await this.supabase.syncFileBatch(batch);
159
+ if (result.success) {
160
+ // Update synced hashes
161
+ for (const event of batch) {
162
+ const key = `${event.workspace_id}:${event.file_path}`;
163
+ this.syncedHashes.set(key, event.content_hash);
164
+ }
165
+ this.logger.info(`Synced ${batch.length} files`, { batch_size: batch.length });
166
+ }
167
+ else {
168
+ // Sync failed - re-queue at front for retry
169
+ this.logger.error('Sync failed, re-queuing batch', {
170
+ error: result.error,
171
+ batch_size: batch.length
172
+ });
173
+ this.state.sync_queue.unshift(...batch);
174
+ }
175
+ }
176
+ catch (error) {
177
+ this.logger.error('Sync failed with exception', { error, batch_size: batch.length });
178
+ // Re-queue failed events at front for retry
179
+ this.state.sync_queue.unshift(...batch);
180
+ }
181
+ finally {
182
+ this.state.is_syncing = false;
183
+ // If more events in queue, schedule next flush
184
+ if (this.state.sync_queue.length > 0) {
185
+ setTimeout(() => {
186
+ void this.flush();
187
+ }, 100);
188
+ }
189
+ }
190
+ }
191
+ /**
192
+ * Check for event storm and pause if detected
193
+ *
194
+ * Event storm = >100 events per second
195
+ * When detected, pause for 5 seconds and log warning
196
+ *
197
+ * @returns True if currently in storm pause
198
+ */
199
+ checkEventStorm() {
200
+ const now = Date.now();
201
+ const elapsed = now - this.eventWindowStart;
202
+ if (elapsed >= 1000) {
203
+ // Reset window every second
204
+ this.eventCount = 0;
205
+ this.eventWindowStart = now;
206
+ this.isStormPaused = false;
207
+ return false;
208
+ }
209
+ this.eventCount++;
210
+ if (this.eventCount > 100 && !this.isStormPaused) {
211
+ this.logger.warn('Event storm detected, pausing for 5s', {
212
+ events_per_sec: this.eventCount
213
+ });
214
+ this.isStormPaused = true;
215
+ // Resume after 5 seconds
216
+ setTimeout(() => {
217
+ this.isStormPaused = false;
218
+ this.eventCount = 0;
219
+ this.eventWindowStart = Date.now();
220
+ this.logger.info('Event storm pause ended, resuming');
221
+ }, 5000);
222
+ return true;
223
+ }
224
+ return this.isStormPaused;
225
+ }
226
+ /**
227
+ * Send heartbeat to Supabase to maintain connection
228
+ */
229
+ async sendHeartbeat() {
230
+ try {
231
+ const result = await this.supabase.heartbeat();
232
+ if (result.success) {
233
+ this.state.last_heartbeat = new Date().toISOString();
234
+ this.logger.debug('Heartbeat sent', { timestamp: this.state.last_heartbeat });
235
+ }
236
+ else {
237
+ this.logger.error('Heartbeat failed', { error: result.error });
238
+ }
239
+ }
240
+ catch (error) {
241
+ this.logger.error('Heartbeat failed with exception', { error });
242
+ }
243
+ }
244
+ }
245
+ /**
246
+ * Factory function to create sync engine
247
+ */
248
+ export function createSyncEngine(watcher, supabase, config, logger) {
249
+ return new SyncEngine(watcher, supabase, config, logger);
250
+ }
251
+ //# sourceMappingURL=sync-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.js","sourceRoot":"","sources":["../src/sync-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Integration tests for SyncEngine
3
+ *
4
+ * Tests batching, debouncing, priority queue, duplicate detection, and event storm handling.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=sync-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.test.d.ts","sourceRoot":"","sources":["../src/sync-engine.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}