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,241 @@
1
+ /**
2
+ * Integration tests for SyncEngine
3
+ *
4
+ * Tests batching, debouncing, priority queue, duplicate detection, and event storm handling.
5
+ */
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
+ import { SyncEngine } from './sync-engine.js';
8
+ describe('SyncEngine', () => {
9
+ let mockWatcher;
10
+ let mockSupabase;
11
+ let mockLogger;
12
+ let config;
13
+ beforeEach(() => {
14
+ // Create mock config
15
+ config = {
16
+ supabase_url: 'https://test.supabase.co',
17
+ supabase_key: 'test-key',
18
+ watch_dirs: ['/test'],
19
+ ignored_patterns: [],
20
+ debounce_ms: 100,
21
+ batch_size: 50,
22
+ log_level: 'DEBUG',
23
+ log_file: '/tmp/test.log',
24
+ log_rotation_days: 7
25
+ };
26
+ // Create mock logger
27
+ mockLogger = {
28
+ error: vi.fn(),
29
+ warn: vi.fn(),
30
+ info: vi.fn(),
31
+ debug: vi.fn()
32
+ };
33
+ // Create mock watcher
34
+ mockWatcher = {
35
+ on: vi.fn(),
36
+ start: vi.fn(),
37
+ stop: vi.fn()
38
+ };
39
+ // Create mock Supabase client
40
+ mockSupabase = {
41
+ syncFileBatch: vi.fn().mockResolvedValue({ success: true }),
42
+ heartbeat: vi.fn().mockResolvedValue({ success: true }),
43
+ connect: vi.fn().mockResolvedValue({ success: true }),
44
+ disconnect: vi.fn().mockResolvedValue(undefined)
45
+ };
46
+ });
47
+ it('should enqueue file change events from watcher', async () => {
48
+ const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
49
+ engine.start();
50
+ // Get the change handler registered with watcher
51
+ expect(mockWatcher.on).toHaveBeenCalledWith('change', expect.any(Function));
52
+ const changeHandler = mockWatcher.on.mock.calls[0][1];
53
+ // Create test event
54
+ const event = {
55
+ id: 'test-1',
56
+ workspace_id: 'ws-1',
57
+ file_path: '.planning/test.md',
58
+ event_type: 'change',
59
+ content_hash: 'hash1',
60
+ content: 'test content',
61
+ source: 'cli',
62
+ timestamp: new Date().toISOString(),
63
+ metadata: {
64
+ size: 100,
65
+ mtime: new Date().toISOString(),
66
+ agent_version: '0.1.0'
67
+ }
68
+ };
69
+ await changeHandler(event);
70
+ const state = engine.getState();
71
+ expect(state.sync_queue.length).toBeGreaterThan(0);
72
+ });
73
+ it('should batch multiple events and sync as single transaction', async () => {
74
+ const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
75
+ engine.start();
76
+ const changeHandler = mockWatcher.on.mock.calls[0][1];
77
+ // Enqueue multiple events
78
+ const events = [];
79
+ for (let i = 0; i < 5; i++) {
80
+ const event = {
81
+ id: `test-${i}`,
82
+ workspace_id: 'ws-1',
83
+ file_path: `.planning/file${i}.md`,
84
+ event_type: 'change',
85
+ content_hash: `hash${i}`,
86
+ content: `content ${i}`,
87
+ source: 'cli',
88
+ timestamp: new Date().toISOString(),
89
+ metadata: {
90
+ size: 100,
91
+ mtime: new Date().toISOString(),
92
+ agent_version: '0.1.0'
93
+ }
94
+ };
95
+ events.push(event);
96
+ await changeHandler(event);
97
+ }
98
+ // Wait for debounce + flush
99
+ await new Promise(resolve => setTimeout(resolve, 200));
100
+ // Should have called syncFileBatch once with all events
101
+ expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(1);
102
+ expect(mockSupabase.syncFileBatch.mock.calls[0][0].length).toBe(5);
103
+ });
104
+ it('should respect batch_size limit (max 50 files per batch)', async () => {
105
+ const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
106
+ engine.start();
107
+ const changeHandler = mockWatcher.on.mock.calls[0][1];
108
+ // Enqueue 75 events (should split into 2 batches: 50 + 25)
109
+ for (let i = 0; i < 75; i++) {
110
+ const event = {
111
+ id: `test-${i}`,
112
+ workspace_id: 'ws-1',
113
+ file_path: `.planning/file${i}.md`,
114
+ event_type: 'change',
115
+ content_hash: `hash${i}`,
116
+ content: `content ${i}`,
117
+ source: 'cli',
118
+ timestamp: new Date().toISOString(),
119
+ metadata: {
120
+ size: 100,
121
+ mtime: new Date().toISOString(),
122
+ agent_version: '0.1.0'
123
+ }
124
+ };
125
+ await changeHandler(event);
126
+ }
127
+ // Wait for debounce + flush
128
+ await new Promise(resolve => setTimeout(resolve, 300));
129
+ // Should have called syncFileBatch twice
130
+ expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(2);
131
+ expect(mockSupabase.syncFileBatch.mock.calls[0][0].length).toBe(50);
132
+ expect(mockSupabase.syncFileBatch.mock.calls[1][0].length).toBe(25);
133
+ });
134
+ it('should prioritize STATE.md and ROADMAP.md changes (high priority queue)', async () => {
135
+ const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
136
+ engine.start();
137
+ const changeHandler = mockWatcher.on.mock.calls[0][1];
138
+ // Enqueue normal file first
139
+ const normalEvent = {
140
+ id: 'normal-1',
141
+ workspace_id: 'ws-1',
142
+ file_path: '.planning/phases/01/PLAN.md',
143
+ event_type: 'change',
144
+ content_hash: 'hash-normal',
145
+ content: 'normal content',
146
+ source: 'cli',
147
+ timestamp: new Date().toISOString(),
148
+ metadata: {
149
+ size: 100,
150
+ mtime: new Date().toISOString(),
151
+ agent_version: '0.1.0'
152
+ }
153
+ };
154
+ await changeHandler(normalEvent);
155
+ // Enqueue STATE.md (should jump to front)
156
+ const stateEvent = {
157
+ id: 'state-1',
158
+ workspace_id: 'ws-1',
159
+ file_path: '.planning/STATE.md',
160
+ event_type: 'change',
161
+ content_hash: 'hash-state',
162
+ content: 'state content',
163
+ source: 'cli',
164
+ timestamp: new Date().toISOString(),
165
+ metadata: {
166
+ size: 100,
167
+ mtime: new Date().toISOString(),
168
+ agent_version: '0.1.0'
169
+ }
170
+ };
171
+ await changeHandler(stateEvent);
172
+ // Wait for debounce + flush
173
+ await new Promise(resolve => setTimeout(resolve, 200));
174
+ // STATE.md should be synced first
175
+ const syncedBatch = mockSupabase.syncFileBatch.mock.calls[0][0];
176
+ expect(syncedBatch[0].file_path).toBe('.planning/STATE.md');
177
+ });
178
+ it('should track synced file hashes to avoid duplicate syncs', async () => {
179
+ const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
180
+ engine.start();
181
+ const changeHandler = mockWatcher.on.mock.calls[0][1];
182
+ // Enqueue same file twice with same hash
183
+ const event1 = {
184
+ id: 'test-1',
185
+ workspace_id: 'ws-1',
186
+ file_path: '.planning/test.md',
187
+ event_type: 'change',
188
+ content_hash: 'same-hash',
189
+ content: 'content',
190
+ source: 'cli',
191
+ timestamp: new Date().toISOString(),
192
+ metadata: {
193
+ size: 100,
194
+ mtime: new Date().toISOString(),
195
+ agent_version: '0.1.0'
196
+ }
197
+ };
198
+ await changeHandler(event1);
199
+ // Wait for sync
200
+ await new Promise(resolve => setTimeout(resolve, 200));
201
+ // Enqueue same file again with same hash
202
+ const event2 = {
203
+ ...event1,
204
+ id: 'test-2',
205
+ timestamp: new Date().toISOString()
206
+ };
207
+ await changeHandler(event2);
208
+ // Wait for potential sync
209
+ await new Promise(resolve => setTimeout(resolve, 200));
210
+ // Should only sync once (duplicate skipped)
211
+ expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(1);
212
+ });
213
+ it('should flush queue after debounce_ms delay with no new events', async () => {
214
+ const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
215
+ engine.start();
216
+ const changeHandler = mockWatcher.on.mock.calls[0][1];
217
+ const event = {
218
+ id: 'test-1',
219
+ workspace_id: 'ws-1',
220
+ file_path: '.planning/test.md',
221
+ event_type: 'change',
222
+ content_hash: 'hash1',
223
+ content: 'content',
224
+ source: 'cli',
225
+ timestamp: new Date().toISOString(),
226
+ metadata: {
227
+ size: 100,
228
+ mtime: new Date().toISOString(),
229
+ agent_version: '0.1.0'
230
+ }
231
+ };
232
+ await changeHandler(event);
233
+ // Should not sync immediately
234
+ expect(mockSupabase.syncFileBatch).not.toHaveBeenCalled();
235
+ // Wait for debounce period
236
+ await new Promise(resolve => setTimeout(resolve, 150));
237
+ // Should have synced after debounce
238
+ expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(1);
239
+ });
240
+ });
241
+ //# sourceMappingURL=sync-engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.test.js","sourceRoot":"","sources":["../src/sync-engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Sync State Management for GSD Agent
3
+ *
4
+ * Persists per-workspace sync state to disk for crash recovery.
5
+ * Tracks last sync timestamp, connection status, and pending changes count.
6
+ */
7
+ import { Logger } from './logger.js';
8
+ /**
9
+ * WorkspaceSyncState represents the sync state for a single workspace
10
+ *
11
+ * Persisted to disk to enable crash recovery and resumption from last sync point.
12
+ */
13
+ export interface WorkspaceSyncState {
14
+ /** Unique identifier for the workspace */
15
+ workspace_id: string;
16
+ /** ISO timestamp of last successful sync */
17
+ last_sync_timestamp: string;
18
+ /** Current connection status */
19
+ connection_status: 'connected' | 'disconnected' | 'reconnecting';
20
+ /** Number of pending changes awaiting sync */
21
+ pending_changes_count: number;
22
+ }
23
+ /**
24
+ * SyncStateManager manages persistent sync state for all workspaces
25
+ *
26
+ * State is stored in ~/.gsd-agent/sync-state.json and loaded on agent startup.
27
+ * Updates are written immediately to disk for crash recovery.
28
+ */
29
+ export declare class SyncStateManager {
30
+ private stateFilePath;
31
+ private workspaceStates;
32
+ private logger;
33
+ constructor(configDir: string, logger: Logger);
34
+ /**
35
+ * Load sync state from disk
36
+ *
37
+ * Reads from sync-state.json and populates in-memory state map.
38
+ * If file doesn't exist or is invalid, starts with empty state.
39
+ */
40
+ loadState(): void;
41
+ /**
42
+ * Save sync state to disk
43
+ *
44
+ * Writes current state map to sync-state.json atomically.
45
+ * Creates parent directory if needed.
46
+ */
47
+ saveState(): void;
48
+ /**
49
+ * Update workspace state
50
+ *
51
+ * Merges updates into existing state or creates new state with defaults.
52
+ * Saves to disk immediately for crash recovery.
53
+ *
54
+ * @param workspace_id Workspace identifier
55
+ * @param updates Partial state updates to merge
56
+ */
57
+ updateWorkspaceState(workspace_id: string, updates: Partial<Omit<WorkspaceSyncState, 'workspace_id'>>): void;
58
+ /**
59
+ * Get workspace state
60
+ *
61
+ * Returns state for specified workspace or default state if not found.
62
+ *
63
+ * @param workspace_id Workspace identifier
64
+ * @returns WorkspaceSyncState for the workspace
65
+ */
66
+ getWorkspaceState(workspace_id: string): WorkspaceSyncState;
67
+ /**
68
+ * Get all workspace states
69
+ *
70
+ * @returns Array of all WorkspaceSyncState objects
71
+ */
72
+ getAllWorkspaceStates(): WorkspaceSyncState[];
73
+ }
74
+ /**
75
+ * Factory function to create SyncStateManager
76
+ *
77
+ * @param configDir Directory containing sync-state.json (e.g., ~/.gsd-agent)
78
+ * @param logger Logger instance
79
+ * @returns SyncStateManager instance
80
+ */
81
+ export declare function createSyncStateManager(configDir: string, logger: Logger): SyncStateManager;
82
+ //# sourceMappingURL=sync-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-state.d.ts","sourceRoot":"","sources":["../src/sync-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,iBAAiB,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,CAAA;IAChE,8CAA8C;IAC9C,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,MAAM,CAAQ;gBAEV,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM7C;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IAwBjB;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IA0BjB;;;;;;;;OAQG;IACH,oBAAoB,CAClB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC,GACzD,IAAI;IAkBP;;;;;;;OAOG;IACH,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB;IAe3D;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,EAAE;CAG9C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAE1F"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Sync State Management for GSD Agent
3
+ *
4
+ * Persists per-workspace sync state to disk for crash recovery.
5
+ * Tracks last sync timestamp, connection status, and pending changes count.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ /**
10
+ * SyncStateManager manages persistent sync state for all workspaces
11
+ *
12
+ * State is stored in ~/.gsd-agent/sync-state.json and loaded on agent startup.
13
+ * Updates are written immediately to disk for crash recovery.
14
+ */
15
+ export class SyncStateManager {
16
+ stateFilePath;
17
+ workspaceStates;
18
+ logger;
19
+ constructor(configDir, logger) {
20
+ this.stateFilePath = path.join(configDir, 'sync-state.json');
21
+ this.workspaceStates = new Map();
22
+ this.logger = logger;
23
+ }
24
+ /**
25
+ * Load sync state from disk
26
+ *
27
+ * Reads from sync-state.json and populates in-memory state map.
28
+ * If file doesn't exist or is invalid, starts with empty state.
29
+ */
30
+ loadState() {
31
+ if (!fs.existsSync(this.stateFilePath)) {
32
+ this.logger.info('No existing sync state file found, starting fresh');
33
+ return;
34
+ }
35
+ try {
36
+ const fileContent = fs.readFileSync(this.stateFilePath, 'utf-8');
37
+ const parsed = JSON.parse(fileContent);
38
+ if (parsed.workspaces && typeof parsed.workspaces === 'object') {
39
+ for (const [workspaceId, state] of Object.entries(parsed.workspaces)) {
40
+ this.workspaceStates.set(workspaceId, state);
41
+ }
42
+ this.logger.info(`Loaded sync state for ${this.workspaceStates.size} workspaces`);
43
+ }
44
+ }
45
+ catch (error) {
46
+ this.logger.warn(`Failed to load sync state from ${this.stateFilePath}:`, {
47
+ error: error instanceof Error ? error.message : String(error)
48
+ });
49
+ this.logger.warn('Starting with empty sync state');
50
+ }
51
+ }
52
+ /**
53
+ * Save sync state to disk
54
+ *
55
+ * Writes current state map to sync-state.json atomically.
56
+ * Creates parent directory if needed.
57
+ */
58
+ saveState() {
59
+ try {
60
+ // Create parent directory if needed
61
+ const parentDir = path.dirname(this.stateFilePath);
62
+ if (!fs.existsSync(parentDir)) {
63
+ fs.mkdirSync(parentDir, { recursive: true });
64
+ }
65
+ // Convert Map to plain object for JSON serialization
66
+ const stateData = {
67
+ workspaces: Object.fromEntries(this.workspaceStates.entries())
68
+ };
69
+ // Atomic write: write to temp file, then rename
70
+ const tempFilePath = `${this.stateFilePath}.tmp`;
71
+ fs.writeFileSync(tempFilePath, JSON.stringify(stateData, null, 2), 'utf-8');
72
+ fs.renameSync(tempFilePath, this.stateFilePath);
73
+ this.logger.debug(`Saved sync state for ${this.workspaceStates.size} workspaces`);
74
+ }
75
+ catch (error) {
76
+ this.logger.error(`Failed to save sync state to ${this.stateFilePath}:`, {
77
+ error: error instanceof Error ? error.message : String(error)
78
+ });
79
+ }
80
+ }
81
+ /**
82
+ * Update workspace state
83
+ *
84
+ * Merges updates into existing state or creates new state with defaults.
85
+ * Saves to disk immediately for crash recovery.
86
+ *
87
+ * @param workspace_id Workspace identifier
88
+ * @param updates Partial state updates to merge
89
+ */
90
+ updateWorkspaceState(workspace_id, updates) {
91
+ const existingState = this.workspaceStates.get(workspace_id) || {
92
+ workspace_id,
93
+ last_sync_timestamp: new Date().toISOString(),
94
+ connection_status: 'disconnected',
95
+ pending_changes_count: 0
96
+ };
97
+ const updatedState = {
98
+ ...existingState,
99
+ ...updates,
100
+ workspace_id // Ensure workspace_id is never overwritten
101
+ };
102
+ this.workspaceStates.set(workspace_id, updatedState);
103
+ this.saveState();
104
+ }
105
+ /**
106
+ * Get workspace state
107
+ *
108
+ * Returns state for specified workspace or default state if not found.
109
+ *
110
+ * @param workspace_id Workspace identifier
111
+ * @returns WorkspaceSyncState for the workspace
112
+ */
113
+ getWorkspaceState(workspace_id) {
114
+ const state = this.workspaceStates.get(workspace_id);
115
+ if (state) {
116
+ return state;
117
+ }
118
+ // Return default state for unknown workspace
119
+ return {
120
+ workspace_id,
121
+ last_sync_timestamp: new Date().toISOString(),
122
+ connection_status: 'disconnected',
123
+ pending_changes_count: 0
124
+ };
125
+ }
126
+ /**
127
+ * Get all workspace states
128
+ *
129
+ * @returns Array of all WorkspaceSyncState objects
130
+ */
131
+ getAllWorkspaceStates() {
132
+ return Array.from(this.workspaceStates.values());
133
+ }
134
+ }
135
+ /**
136
+ * Factory function to create SyncStateManager
137
+ *
138
+ * @param configDir Directory containing sync-state.json (e.g., ~/.gsd-agent)
139
+ * @param logger Logger instance
140
+ * @returns SyncStateManager instance
141
+ */
142
+ export function createSyncStateManager(configDir, logger) {
143
+ return new SyncStateManager(configDir, logger);
144
+ }
145
+ //# sourceMappingURL=sync-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-state.js","sourceRoot":"","sources":["../src/sync-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for SyncStateManager
3
+ *
4
+ * Validates persistent sync state management for crash recovery.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=sync-state.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-state.test.d.ts","sourceRoot":"","sources":["../src/sync-state.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Tests for SyncStateManager
3
+ *
4
+ * Validates persistent sync state management for crash recovery.
5
+ */
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import { createSyncStateManager } from './sync-state.js';
11
+ import { createLogger } from './logger.js';
12
+ describe('SyncStateManager', () => {
13
+ let tempDir;
14
+ let stateFilePath;
15
+ let logger;
16
+ beforeEach(() => {
17
+ // Create temporary directory for test state files
18
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-agent-test-'));
19
+ stateFilePath = path.join(tempDir, 'sync-state.json');
20
+ logger = createLogger({
21
+ log_level: 'ERROR',
22
+ log_file: path.join(tempDir, 'test.log'),
23
+ log_rotation_days: 1
24
+ });
25
+ });
26
+ afterEach(() => {
27
+ // Clean up temporary directory
28
+ if (fs.existsSync(tempDir)) {
29
+ fs.rmSync(tempDir, { recursive: true, force: true });
30
+ }
31
+ });
32
+ it('Test 1: WorkspaceSyncState has required fields', () => {
33
+ const manager = createSyncStateManager(tempDir, logger);
34
+ const state = manager.getWorkspaceState('workspace-123');
35
+ expect(state).toHaveProperty('workspace_id');
36
+ expect(state).toHaveProperty('last_sync_timestamp');
37
+ expect(state).toHaveProperty('connection_status');
38
+ expect(state).toHaveProperty('pending_changes_count');
39
+ expect(state.workspace_id).toBe('workspace-123');
40
+ expect(state.connection_status).toBe('disconnected');
41
+ expect(state.pending_changes_count).toBe(0);
42
+ });
43
+ it('Test 2: saveState() writes sync state to disk', () => {
44
+ const manager = createSyncStateManager(tempDir, logger);
45
+ manager.updateWorkspaceState('workspace-123', {
46
+ last_sync_timestamp: '2026-03-27T12:00:00Z',
47
+ connection_status: 'connected',
48
+ pending_changes_count: 5
49
+ });
50
+ manager.saveState();
51
+ expect(fs.existsSync(stateFilePath)).toBe(true);
52
+ const fileContent = fs.readFileSync(stateFilePath, 'utf-8');
53
+ const parsed = JSON.parse(fileContent);
54
+ expect(parsed).toHaveProperty('workspaces');
55
+ expect(parsed.workspaces['workspace-123']).toBeDefined();
56
+ expect(parsed.workspaces['workspace-123'].last_sync_timestamp).toBe('2026-03-27T12:00:00Z');
57
+ expect(parsed.workspaces['workspace-123'].connection_status).toBe('connected');
58
+ expect(parsed.workspaces['workspace-123'].pending_changes_count).toBe(5);
59
+ });
60
+ it('Test 3: loadState() reads sync state from disk', () => {
61
+ // Write state file manually
62
+ const stateData = {
63
+ workspaces: {
64
+ 'workspace-456': {
65
+ workspace_id: 'workspace-456',
66
+ last_sync_timestamp: '2026-03-27T13:00:00Z',
67
+ connection_status: 'connected',
68
+ pending_changes_count: 10
69
+ }
70
+ }
71
+ };
72
+ fs.writeFileSync(stateFilePath, JSON.stringify(stateData, null, 2));
73
+ const manager = createSyncStateManager(tempDir, logger);
74
+ manager.loadState();
75
+ const state = manager.getWorkspaceState('workspace-456');
76
+ expect(state.workspace_id).toBe('workspace-456');
77
+ expect(state.last_sync_timestamp).toBe('2026-03-27T13:00:00Z');
78
+ expect(state.connection_status).toBe('connected');
79
+ expect(state.pending_changes_count).toBe(10);
80
+ });
81
+ it('Test 4: updateWorkspaceState() updates specific workspace state', () => {
82
+ const manager = createSyncStateManager(tempDir, logger);
83
+ manager.updateWorkspaceState('workspace-789', {
84
+ connection_status: 'reconnecting',
85
+ pending_changes_count: 3
86
+ });
87
+ const state = manager.getWorkspaceState('workspace-789');
88
+ expect(state.connection_status).toBe('reconnecting');
89
+ expect(state.pending_changes_count).toBe(3);
90
+ expect(state.workspace_id).toBe('workspace-789');
91
+ });
92
+ it('Test 5: getWorkspaceState() retrieves state for specific workspace', () => {
93
+ const manager = createSyncStateManager(tempDir, logger);
94
+ manager.updateWorkspaceState('workspace-abc', {
95
+ last_sync_timestamp: '2026-03-27T14:00:00Z',
96
+ connection_status: 'connected',
97
+ pending_changes_count: 0
98
+ });
99
+ const state = manager.getWorkspaceState('workspace-abc');
100
+ expect(state.workspace_id).toBe('workspace-abc');
101
+ expect(state.last_sync_timestamp).toBe('2026-03-27T14:00:00Z');
102
+ expect(state.connection_status).toBe('connected');
103
+ expect(state.pending_changes_count).toBe(0);
104
+ // Non-existent workspace returns default state
105
+ const defaultState = manager.getWorkspaceState('workspace-nonexistent');
106
+ expect(defaultState.workspace_id).toBe('workspace-nonexistent');
107
+ expect(defaultState.connection_status).toBe('disconnected');
108
+ expect(defaultState.pending_changes_count).toBe(0);
109
+ });
110
+ it('Test 6: On agent restart, loadState() resumes from last_sync_timestamp', () => {
111
+ // First manager instance - create and save state
112
+ const manager1 = createSyncStateManager(tempDir, logger);
113
+ manager1.updateWorkspaceState('workspace-restart', {
114
+ last_sync_timestamp: '2026-03-27T15:00:00Z',
115
+ connection_status: 'connected',
116
+ pending_changes_count: 7
117
+ });
118
+ manager1.saveState();
119
+ // Simulate agent restart - create new manager instance
120
+ const manager2 = createSyncStateManager(tempDir, logger);
121
+ manager2.loadState();
122
+ const state = manager2.getWorkspaceState('workspace-restart');
123
+ expect(state.last_sync_timestamp).toBe('2026-03-27T15:00:00Z');
124
+ expect(state.pending_changes_count).toBe(7);
125
+ // Connection status should be reset to disconnected on restart
126
+ expect(state.connection_status).toBe('connected');
127
+ });
128
+ });
129
+ //# sourceMappingURL=sync-state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-state.test.js","sourceRoot":"","sources":["../src/sync-state.test.ts"],"names":[],"mappings":"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"}