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
package/dist/config.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Configuration management for GSD Agent
3
+ *
4
+ * Loads configuration from ~/.gsd-agent/config.json with fallback to sensible defaults.
5
+ * Handles missing files, invalid JSON, and partial configurations gracefully.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ /**
11
+ * Get the path to the agent configuration file
12
+ * @returns Absolute path to ~/.gsd-agent/config.json
13
+ */
14
+ export function getConfigPath() {
15
+ return path.join(os.homedir(), '.gsd-agent', 'config.json');
16
+ }
17
+ /**
18
+ * Expand ~ to home directory in paths
19
+ * @param filePath Path that may contain ~
20
+ * @returns Expanded absolute path
21
+ */
22
+ function expandPath(filePath) {
23
+ if (filePath.startsWith('~/')) {
24
+ return path.join(os.homedir(), filePath.slice(2));
25
+ }
26
+ return filePath;
27
+ }
28
+ /**
29
+ * Get default configuration values
30
+ * @returns Default SyncConfig
31
+ */
32
+ function getDefaults() {
33
+ const homeDir = os.homedir();
34
+ return {
35
+ supabase_url: '',
36
+ supabase_key: '',
37
+ watch_dirs: [
38
+ path.join(homeDir, 'Projects'),
39
+ path.join(homeDir, 'Code'),
40
+ path.join(homeDir, 'workspace')
41
+ ],
42
+ ignored_patterns: [
43
+ 'node_modules/',
44
+ '.git/',
45
+ 'dist/',
46
+ 'build/',
47
+ '*.log',
48
+ '.DS_Store'
49
+ ],
50
+ debounce_ms: 300,
51
+ batch_size: 50,
52
+ log_level: 'INFO',
53
+ log_file: path.join(homeDir, '.gsd-agent', 'logs', 'agent.log'),
54
+ log_rotation_days: 7
55
+ };
56
+ }
57
+ /**
58
+ * Load configuration from file or return defaults
59
+ *
60
+ * Handles various error cases:
61
+ * - Config file doesn't exist → return defaults
62
+ * - Config file invalid JSON → log warning, return defaults
63
+ * - Missing fields → merge with defaults
64
+ * - Paths with ~ → expand to home directory
65
+ *
66
+ * @returns Complete SyncConfig with all required fields
67
+ */
68
+ export function loadConfig() {
69
+ const configPath = getConfigPath();
70
+ const defaults = getDefaults();
71
+ // Config file doesn't exist - use defaults
72
+ if (!fs.existsSync(configPath)) {
73
+ return defaults;
74
+ }
75
+ try {
76
+ const fileContent = fs.readFileSync(configPath, 'utf-8');
77
+ const userConfig = JSON.parse(fileContent);
78
+ // Merge user config with defaults
79
+ const config = {
80
+ supabase_url: userConfig.supabase_url || defaults.supabase_url,
81
+ supabase_key: userConfig.supabase_key || defaults.supabase_key,
82
+ watch_dirs: userConfig.watch_dirs
83
+ ? userConfig.watch_dirs.map(expandPath)
84
+ : defaults.watch_dirs,
85
+ ignored_patterns: userConfig.ignored_patterns || defaults.ignored_patterns,
86
+ debounce_ms: userConfig.debounce_ms ?? defaults.debounce_ms,
87
+ batch_size: userConfig.batch_size ?? defaults.batch_size,
88
+ log_level: userConfig.log_level || defaults.log_level,
89
+ log_file: userConfig.log_file
90
+ ? expandPath(userConfig.log_file)
91
+ : defaults.log_file,
92
+ log_rotation_days: userConfig.log_rotation_days ?? defaults.log_rotation_days
93
+ };
94
+ return config;
95
+ }
96
+ catch (error) {
97
+ // Invalid JSON or read error - log warning and use defaults
98
+ console.warn(`[WARN] Failed to load config from ${configPath}:`, error);
99
+ console.warn('[WARN] Using default configuration');
100
+ return defaults;
101
+ }
102
+ }
103
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Conflict resolution utilities for GSD Agent
3
+ *
4
+ * Creates git-style conflict markers when local and remote versions differ.
5
+ * Commits conflicted files to git for manual resolution.
6
+ */
7
+ import type { ConflictEvent } from './types.js';
8
+ import type { Logger } from './logger.js';
9
+ /**
10
+ * Create git-style conflict markers for a conflict
11
+ * @param conflict - ConflictEvent with local and remote content
12
+ * @returns String with conflict markers ready to write to file
13
+ */
14
+ export declare function createConflictMarkers(conflict: ConflictEvent): string;
15
+ /**
16
+ * ConflictResolver handles conflict detection and resolution
17
+ *
18
+ * Creates git-style conflict markers and commits conflicted files.
19
+ * Per D-11: No auto-merge for planning documents (manual review safer).
20
+ */
21
+ export declare class ConflictResolver {
22
+ private logger;
23
+ constructor(logger: Logger);
24
+ /**
25
+ * Resolve conflict by creating git-style conflict markers
26
+ * @param conflict - ConflictEvent to resolve
27
+ * @returns String with conflict markers
28
+ */
29
+ resolveConflict(conflict: ConflictEvent): string;
30
+ /**
31
+ * Commit conflicted file to git
32
+ * @param workspace_root - Absolute path to workspace root
33
+ * @param file_path - File path relative to workspace root
34
+ */
35
+ commitConflict(workspace_root: string, file_path: string): Promise<void>;
36
+ }
37
+ /**
38
+ * Factory function to create ConflictResolver
39
+ * @param logger - Logger instance
40
+ * @returns ConflictResolver instance
41
+ */
42
+ export declare function createConflictResolver(logger: Logger): ConflictResolver;
43
+ //# sourceMappingURL=conflict-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.d.ts","sourceRoot":"","sources":["../src/conflict-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAUrE;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAQ;gBAEV,MAAM,EAAE,MAAM;IAI1B;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM;IAWhD;;;;OAIG;IACG,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC/E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAEvE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Conflict resolution utilities for GSD Agent
3
+ *
4
+ * Creates git-style conflict markers when local and remote versions differ.
5
+ * Commits conflicted files to git for manual resolution.
6
+ */
7
+ import { execSync } from 'child_process';
8
+ /**
9
+ * Create git-style conflict markers for a conflict
10
+ * @param conflict - ConflictEvent with local and remote content
11
+ * @returns String with conflict markers ready to write to file
12
+ */
13
+ export function createConflictMarkers(conflict) {
14
+ // Format timestamp as YYYY-MM-DD HH:mm:ss
15
+ const timestamp = conflict.detected_at.replace('T', ' ').substring(0, 19);
16
+ return `<<<<<<< LOCAL (IDE) - ${timestamp}
17
+ ${conflict.local_content}
18
+ =======
19
+ ${conflict.remote_content}
20
+ >>>>>>> REMOTE (Web UI) - ${timestamp}
21
+ `;
22
+ }
23
+ /**
24
+ * ConflictResolver handles conflict detection and resolution
25
+ *
26
+ * Creates git-style conflict markers and commits conflicted files.
27
+ * Per D-11: No auto-merge for planning documents (manual review safer).
28
+ */
29
+ export class ConflictResolver {
30
+ logger;
31
+ constructor(logger) {
32
+ this.logger = logger;
33
+ }
34
+ /**
35
+ * Resolve conflict by creating git-style conflict markers
36
+ * @param conflict - ConflictEvent to resolve
37
+ * @returns String with conflict markers
38
+ */
39
+ resolveConflict(conflict) {
40
+ this.logger.info('Resolving conflict with git-style markers', {
41
+ workspace_id: conflict.workspace_id,
42
+ file_path: conflict.file_path,
43
+ local_hash: conflict.local_hash,
44
+ remote_hash: conflict.remote_hash
45
+ });
46
+ return createConflictMarkers(conflict);
47
+ }
48
+ /**
49
+ * Commit conflicted file to git
50
+ * @param workspace_root - Absolute path to workspace root
51
+ * @param file_path - File path relative to workspace root
52
+ */
53
+ async commitConflict(workspace_root, file_path) {
54
+ try {
55
+ const filename = file_path.split('/').pop() || file_path;
56
+ this.logger.info('Committing conflict file', {
57
+ workspace_root,
58
+ file_path,
59
+ filename
60
+ });
61
+ // Per D-09: Commit with descriptive message
62
+ const commitMessage = `conflict: ${filename} edited in IDE and web UI simultaneously`;
63
+ // Stage and commit the conflicted file
64
+ execSync(`git add "${file_path}" && git commit -m "${commitMessage}"`, {
65
+ cwd: workspace_root,
66
+ encoding: 'utf-8'
67
+ });
68
+ this.logger.info('Conflict file committed', {
69
+ file_path,
70
+ commit_message: commitMessage
71
+ });
72
+ }
73
+ catch (error) {
74
+ // Log error but don't throw - git errors shouldn't block sync
75
+ this.logger.error('Failed to commit conflict file', {
76
+ workspace_root,
77
+ file_path,
78
+ error: error instanceof Error ? error.message : String(error)
79
+ });
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Factory function to create ConflictResolver
85
+ * @param logger - Logger instance
86
+ * @returns ConflictResolver instance
87
+ */
88
+ export function createConflictResolver(logger) {
89
+ return new ConflictResolver(logger);
90
+ }
91
+ //# sourceMappingURL=conflict-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.js","sourceRoot":"","sources":["../src/conflict-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAIxC;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAuB;IAC3D,0CAA0C;IAC1C,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEzE,OAAO,yBAAyB,SAAS;EACzC,QAAQ,CAAC,aAAa;;EAEtB,QAAQ,CAAC,cAAc;4BACG,SAAS;CACpC,CAAA;AACD,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAQ;IAEtB,YAAY,MAAc;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,QAAuB;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2CAA2C,EAAE;YAC5D,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC,CAAA;QAEF,OAAO,qBAAqB,CAAC,QAAQ,CAAC,CAAA;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,cAAsB,EAAE,SAAiB;QAC5D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAA;YAExD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;gBAC3C,cAAc;gBACd,SAAS;gBACT,QAAQ;aACT,CAAC,CAAA;YAEF,4CAA4C;YAC5C,MAAM,aAAa,GAAG,aAAa,QAAQ,0CAA0C,CAAA;YAErF,uCAAuC;YACvC,QAAQ,CAAC,YAAY,SAAS,uBAAuB,aAAa,GAAG,EAAE;gBACrE,GAAG,EAAE,cAAc;gBACnB,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAA;YAEF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBAC1C,SAAS;gBACT,cAAc,EAAE,aAAa;aAC9B,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,8DAA8D;YAC9D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE;gBAClD,cAAc;gBACd,SAAS;gBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAA;AACrC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for ConflictResolver
3
+ *
4
+ * Verifies git-style conflict marker generation and conflict file commits.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=conflict-resolver.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.test.d.ts","sourceRoot":"","sources":["../src/conflict-resolver.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Tests for ConflictResolver
3
+ *
4
+ * Verifies git-style conflict marker generation and conflict file commits.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ // Mock child_process at top level
8
+ vi.mock('child_process', () => ({
9
+ execSync: vi.fn()
10
+ }));
11
+ import { ConflictResolver, createConflictMarkers } from './conflict-resolver.js';
12
+ import { execSync } from 'child_process';
13
+ describe('createConflictMarkers', () => {
14
+ it('should return string with <<<<<<< LOCAL section', () => {
15
+ const conflict = {
16
+ workspace_id: 'ws-123',
17
+ file_path: '.planning/STATE.md',
18
+ local_hash: 'abc123',
19
+ remote_hash: 'def456',
20
+ local_content: 'Local version',
21
+ remote_content: 'Remote version',
22
+ detected_at: '2026-03-27T13:45:32.000Z'
23
+ };
24
+ const result = createConflictMarkers(conflict);
25
+ expect(result).toContain('<<<<<<< LOCAL (IDE)');
26
+ });
27
+ it('should include timestamps from ConflictEvent.detected_at', () => {
28
+ const conflict = {
29
+ workspace_id: 'ws-123',
30
+ file_path: '.planning/STATE.md',
31
+ local_hash: 'abc123',
32
+ remote_hash: 'def456',
33
+ local_content: 'Local version',
34
+ remote_content: 'Remote version',
35
+ detected_at: '2026-03-27T13:45:32.000Z'
36
+ };
37
+ const result = createConflictMarkers(conflict);
38
+ expect(result).toContain('2026-03-27 13:45:32');
39
+ });
40
+ it('should have ======= separator between local and remote', () => {
41
+ const conflict = {
42
+ workspace_id: 'ws-123',
43
+ file_path: '.planning/STATE.md',
44
+ local_hash: 'abc123',
45
+ remote_hash: 'def456',
46
+ local_content: 'Local version',
47
+ remote_content: 'Remote version',
48
+ detected_at: '2026-03-27T13:45:32.000Z'
49
+ };
50
+ const result = createConflictMarkers(conflict);
51
+ expect(result).toContain('=======');
52
+ });
53
+ it('should end with >>>>>>> REMOTE section', () => {
54
+ const conflict = {
55
+ workspace_id: 'ws-123',
56
+ file_path: '.planning/STATE.md',
57
+ local_hash: 'abc123',
58
+ remote_hash: 'def456',
59
+ local_content: 'Local version',
60
+ remote_content: 'Remote version',
61
+ detected_at: '2026-03-27T13:45:32.000Z'
62
+ };
63
+ const result = createConflictMarkers(conflict);
64
+ expect(result).toContain('>>>>>>> REMOTE (Web UI)');
65
+ });
66
+ it('should have local content before separator and remote content after', () => {
67
+ const conflict = {
68
+ workspace_id: 'ws-123',
69
+ file_path: '.planning/STATE.md',
70
+ local_hash: 'abc123',
71
+ remote_hash: 'def456',
72
+ local_content: 'Local version',
73
+ remote_content: 'Remote version',
74
+ detected_at: '2026-03-27T13:45:32.000Z'
75
+ };
76
+ const result = createConflictMarkers(conflict);
77
+ const separatorIndex = result.indexOf('=======');
78
+ const localIndex = result.indexOf('Local version');
79
+ const remoteIndex = result.indexOf('Remote version');
80
+ expect(localIndex).toBeLessThan(separatorIndex);
81
+ expect(remoteIndex).toBeGreaterThan(separatorIndex);
82
+ });
83
+ });
84
+ describe('ConflictResolver', () => {
85
+ let mockLogger;
86
+ beforeEach(() => {
87
+ mockLogger = {
88
+ info: vi.fn(),
89
+ warn: vi.fn(),
90
+ error: vi.fn(),
91
+ debug: vi.fn()
92
+ };
93
+ });
94
+ it('should call createConflictMarkers when resolveConflict is called', () => {
95
+ const resolver = new ConflictResolver(mockLogger);
96
+ const conflict = {
97
+ workspace_id: 'ws-123',
98
+ file_path: '.planning/STATE.md',
99
+ local_hash: 'abc123',
100
+ remote_hash: 'def456',
101
+ local_content: 'Local version',
102
+ remote_content: 'Remote version',
103
+ detected_at: '2026-03-27T13:45:32.000Z'
104
+ };
105
+ const result = resolver.resolveConflict(conflict);
106
+ expect(result).toContain('<<<<<<< LOCAL (IDE)');
107
+ expect(result).toContain('Local version');
108
+ expect(result).toContain('Remote version');
109
+ });
110
+ it('should commit conflict file with proper message', async () => {
111
+ const resolver = new ConflictResolver(mockLogger);
112
+ const workspace_root = '/home/user/projects/my-project';
113
+ const file_path = '.planning/STATE.md';
114
+ // Clear previous mock calls
115
+ vi.mocked(execSync).mockClear();
116
+ await resolver.commitConflict(workspace_root, file_path);
117
+ // Should log the commit attempt
118
+ expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Committing conflict file'), expect.any(Object));
119
+ // Should call git add and commit
120
+ expect(execSync).toHaveBeenCalledWith(expect.stringContaining('git add'), expect.objectContaining({ cwd: workspace_root }));
121
+ });
122
+ });
123
+ //# sourceMappingURL=conflict-resolver.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.test.js","sourceRoot":"","sources":["../src/conflict-resolver.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Workspace discovery for GSD Agent
3
+ *
4
+ * Scans configured directories for .planning/ folders and registers discovered
5
+ * GSD projects as workspaces. Handles ignored patterns, permission errors, and
6
+ * duplicate detection.
7
+ */
8
+ import { Workspace, SyncConfig } from './types.js';
9
+ import { Logger } from './logger.js';
10
+ /**
11
+ * WorkspaceDiscovery class handles finding and registering GSD workspaces
12
+ */
13
+ export declare class WorkspaceDiscovery {
14
+ private config;
15
+ private logger;
16
+ private discoveredPaths;
17
+ constructor(config: SyncConfig, logger: Logger);
18
+ /**
19
+ * Scan all configured watch directories for .planning/ folders
20
+ * @returns Array of discovered Workspace objects
21
+ */
22
+ scan(): Promise<Workspace[]>;
23
+ /**
24
+ * Recursively scan a directory for .planning/ folders
25
+ * @param dir Directory to scan
26
+ * @param depth Current recursion depth
27
+ * @param seenInThisScan Set to track paths seen in current scan
28
+ * @returns Array of discovered workspaces
29
+ */
30
+ private scanDirectory;
31
+ /**
32
+ * Check if a path should be ignored based on configured patterns
33
+ * @param filePath Path to check
34
+ * @returns True if path should be ignored
35
+ */
36
+ private shouldIgnore;
37
+ /**
38
+ * Register a discovered workspace
39
+ * @param rootPath Absolute path to workspace root (parent of .planning/)
40
+ * @returns Workspace object with metadata
41
+ */
42
+ registerWorkspace(rootPath: string): Promise<Workspace>;
43
+ /**
44
+ * Parse PROJECT.md to extract project name
45
+ * @param projectPath Path to PROJECT.md file
46
+ * @returns Object with extracted name
47
+ */
48
+ parseProjectFile(projectPath: string): {
49
+ name: string;
50
+ };
51
+ }
52
+ /**
53
+ * Scan for workspaces in configured directories
54
+ * @param config SyncConfig with watch_dirs
55
+ * @param logger Logger instance
56
+ * @returns Array of discovered workspaces
57
+ */
58
+ export declare function scanForWorkspaces(config: SyncConfig, logger: Logger): Promise<Workspace[]>;
59
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,eAAe,CAAa;gBAExB,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM;IAM9C;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAsBlC;;;;;;OAMG;YACW,aAAa;IAwD3B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAUpB;;;;OAIG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IA4B7D;;;;OAIG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;CAoBxD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,SAAS,EAAE,CAAC,CAGtB"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Workspace discovery for GSD Agent
3
+ *
4
+ * Scans configured directories for .planning/ folders and registers discovered
5
+ * GSD projects as workspaces. Handles ignored patterns, permission errors, and
6
+ * duplicate detection.
7
+ */
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { randomUUID } from 'crypto';
11
+ /**
12
+ * WorkspaceDiscovery class handles finding and registering GSD workspaces
13
+ */
14
+ export class WorkspaceDiscovery {
15
+ config;
16
+ logger;
17
+ discoveredPaths;
18
+ constructor(config, logger) {
19
+ this.config = config;
20
+ this.logger = logger;
21
+ this.discoveredPaths = new Set();
22
+ }
23
+ /**
24
+ * Scan all configured watch directories for .planning/ folders
25
+ * @returns Array of discovered Workspace objects
26
+ */
27
+ async scan() {
28
+ const workspaces = [];
29
+ const seenInThisScan = new Set();
30
+ for (const watchDir of this.config.watch_dirs) {
31
+ // Check if watch directory exists
32
+ if (!fs.existsSync(watchDir)) {
33
+ this.logger.warn(`Watch directory does not exist: ${watchDir}`);
34
+ continue;
35
+ }
36
+ try {
37
+ const found = await this.scanDirectory(watchDir, 0, seenInThisScan);
38
+ workspaces.push(...found);
39
+ }
40
+ catch (error) {
41
+ this.logger.warn(`Failed to scan directory: ${watchDir}`, { error });
42
+ }
43
+ }
44
+ return workspaces;
45
+ }
46
+ /**
47
+ * Recursively scan a directory for .planning/ folders
48
+ * @param dir Directory to scan
49
+ * @param depth Current recursion depth
50
+ * @param seenInThisScan Set to track paths seen in current scan
51
+ * @returns Array of discovered workspaces
52
+ */
53
+ async scanDirectory(dir, depth, seenInThisScan) {
54
+ const workspaces = [];
55
+ const maxDepth = 5;
56
+ // Limit recursion depth
57
+ if (depth > maxDepth) {
58
+ return workspaces;
59
+ }
60
+ try {
61
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
62
+ for (const entry of entries) {
63
+ if (!entry.isDirectory()) {
64
+ continue;
65
+ }
66
+ const fullPath = path.join(dir, entry.name);
67
+ // Check if this should be ignored
68
+ if (this.shouldIgnore(fullPath)) {
69
+ continue;
70
+ }
71
+ // Check if this is a .planning directory
72
+ if (entry.name === '.planning') {
73
+ const workspaceRoot = dir;
74
+ // Skip if already discovered in this scan or globally
75
+ if (seenInThisScan.has(workspaceRoot) || this.discoveredPaths.has(workspaceRoot)) {
76
+ continue;
77
+ }
78
+ try {
79
+ const workspace = await this.registerWorkspace(workspaceRoot);
80
+ workspaces.push(workspace);
81
+ seenInThisScan.add(workspaceRoot);
82
+ this.discoveredPaths.add(workspaceRoot);
83
+ this.logger.info(`Discovered workspace: ${workspaceRoot}`);
84
+ }
85
+ catch (error) {
86
+ this.logger.warn(`Failed to register workspace: ${workspaceRoot}`, { error });
87
+ }
88
+ }
89
+ else {
90
+ // Recurse into subdirectory
91
+ const subWorkspaces = await this.scanDirectory(fullPath, depth + 1, seenInThisScan);
92
+ workspaces.push(...subWorkspaces);
93
+ }
94
+ }
95
+ }
96
+ catch (error) {
97
+ // Permission denied or other read error - skip this directory
98
+ this.logger.debug(`Cannot read directory: ${dir}`, { error });
99
+ }
100
+ return workspaces;
101
+ }
102
+ /**
103
+ * Check if a path should be ignored based on configured patterns
104
+ * @param filePath Path to check
105
+ * @returns True if path should be ignored
106
+ */
107
+ shouldIgnore(filePath) {
108
+ for (const pattern of this.config.ignored_patterns) {
109
+ // Simple pattern matching - check if path contains the pattern
110
+ if (filePath.includes(pattern.replace(/\/$/, ''))) {
111
+ return true;
112
+ }
113
+ }
114
+ return false;
115
+ }
116
+ /**
117
+ * Register a discovered workspace
118
+ * @param rootPath Absolute path to workspace root (parent of .planning/)
119
+ * @returns Workspace object with metadata
120
+ */
121
+ async registerWorkspace(rootPath) {
122
+ const planningDir = path.join(rootPath, '.planning');
123
+ const projectFile = path.join(planningDir, 'PROJECT.md');
124
+ // Extract project name
125
+ let name;
126
+ if (fs.existsSync(projectFile)) {
127
+ const parsed = this.parseProjectFile(projectFile);
128
+ name = parsed.name;
129
+ }
130
+ else {
131
+ // Fallback to directory name
132
+ name = path.basename(rootPath);
133
+ }
134
+ const now = new Date().toISOString();
135
+ const workspace = {
136
+ id: randomUUID(),
137
+ root_path: rootPath,
138
+ name,
139
+ status: 'active',
140
+ last_sync: now,
141
+ discovered_at: now
142
+ };
143
+ return workspace;
144
+ }
145
+ /**
146
+ * Parse PROJECT.md to extract project name
147
+ * @param projectPath Path to PROJECT.md file
148
+ * @returns Object with extracted name
149
+ */
150
+ parseProjectFile(projectPath) {
151
+ try {
152
+ const content = fs.readFileSync(projectPath, 'utf-8');
153
+ // Extract first # heading
154
+ const lines = content.split('\n');
155
+ for (const line of lines) {
156
+ const match = line.match(/^#\s+(.+)$/);
157
+ if (match) {
158
+ return { name: match[1].trim() };
159
+ }
160
+ }
161
+ // No heading found - use filename
162
+ return { name: 'Untitled Project' };
163
+ }
164
+ catch (error) {
165
+ // File read error - return default
166
+ return { name: 'Untitled Project' };
167
+ }
168
+ }
169
+ }
170
+ /**
171
+ * Scan for workspaces in configured directories
172
+ * @param config SyncConfig with watch_dirs
173
+ * @param logger Logger instance
174
+ * @returns Array of discovered workspaces
175
+ */
176
+ export async function scanForWorkspaces(config, logger) {
177
+ const discovery = new WorkspaceDiscovery(config, logger);
178
+ return discovery.scan();
179
+ }
180
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tests for workspace discovery
3
+ *
4
+ * Verifies that the agent can find .planning/ directories, extract project names,
5
+ * and register workspaces with proper metadata.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=discovery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.test.d.ts","sourceRoot":"","sources":["../src/discovery.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}