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.
- package/README.md +221 -0
- package/bin/cli.js +313 -0
- package/dist/auth-flow.d.ts +50 -0
- package/dist/auth-flow.d.ts.map +1 -0
- package/dist/auth-flow.js +233 -0
- package/dist/auth-flow.js.map +1 -0
- package/dist/auth.d.ts +42 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +117 -0
- package/dist/auth.js.map +1 -0
- package/dist/command-executor.d.ts +44 -0
- package/dist/command-executor.d.ts.map +1 -0
- package/dist/command-executor.js +193 -0
- package/dist/command-executor.js.map +1 -0
- package/dist/command-executor.test.d.ts +8 -0
- package/dist/command-executor.test.d.ts.map +1 -0
- package/dist/command-executor.test.js +87 -0
- package/dist/command-executor.test.js.map +1 -0
- package/dist/command-queue.d.ts +44 -0
- package/dist/command-queue.d.ts.map +1 -0
- package/dist/command-queue.js +184 -0
- package/dist/command-queue.js.map +1 -0
- package/dist/command-queue.test.d.ts +7 -0
- package/dist/command-queue.test.d.ts.map +1 -0
- package/dist/command-queue.test.js +220 -0
- package/dist/command-queue.test.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +103 -0
- package/dist/config.js.map +1 -0
- package/dist/conflict-resolver.d.ts +43 -0
- package/dist/conflict-resolver.d.ts.map +1 -0
- package/dist/conflict-resolver.js +91 -0
- package/dist/conflict-resolver.js.map +1 -0
- package/dist/conflict-resolver.test.d.ts +7 -0
- package/dist/conflict-resolver.test.d.ts.map +1 -0
- package/dist/conflict-resolver.test.js +123 -0
- package/dist/conflict-resolver.test.js.map +1 -0
- package/dist/discovery.d.ts +59 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +180 -0
- package/dist/discovery.js.map +1 -0
- package/dist/discovery.test.d.ts +8 -0
- package/dist/discovery.test.d.ts.map +1 -0
- package/dist/discovery.test.js +132 -0
- package/dist/discovery.test.js.map +1 -0
- package/dist/hash.d.ts +20 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +35 -0
- package/dist/hash.js.map +1 -0
- package/dist/hash.test.d.ts +7 -0
- package/dist/hash.test.d.ts.map +1 -0
- package/dist/hash.test.js +58 -0
- package/dist/hash.test.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +202 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.test.d.ts +8 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +37 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/logger.d.ts +68 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +159 -0
- package/dist/logger.js.map +1 -0
- package/dist/output-streamer.d.ts +27 -0
- package/dist/output-streamer.d.ts.map +1 -0
- package/dist/output-streamer.js +71 -0
- package/dist/output-streamer.js.map +1 -0
- package/dist/output-streamer.test.d.ts +7 -0
- package/dist/output-streamer.test.d.ts.map +1 -0
- package/dist/output-streamer.test.js +90 -0
- package/dist/output-streamer.test.js.map +1 -0
- package/dist/realtime-subscriber.d.ts +63 -0
- package/dist/realtime-subscriber.d.ts.map +1 -0
- package/dist/realtime-subscriber.js +201 -0
- package/dist/realtime-subscriber.js.map +1 -0
- package/dist/realtime-subscriber.test.d.ts +7 -0
- package/dist/realtime-subscriber.test.d.ts.map +1 -0
- package/dist/realtime-subscriber.test.js +183 -0
- package/dist/realtime-subscriber.test.js.map +1 -0
- package/dist/reconnection-manager.d.ts +88 -0
- package/dist/reconnection-manager.d.ts.map +1 -0
- package/dist/reconnection-manager.js +229 -0
- package/dist/reconnection-manager.js.map +1 -0
- package/dist/reconnection-manager.test.d.ts +8 -0
- package/dist/reconnection-manager.test.d.ts.map +1 -0
- package/dist/reconnection-manager.test.js +151 -0
- package/dist/reconnection-manager.test.js.map +1 -0
- package/dist/remote-sync-handler.d.ts +61 -0
- package/dist/remote-sync-handler.d.ts.map +1 -0
- package/dist/remote-sync-handler.js +197 -0
- package/dist/remote-sync-handler.js.map +1 -0
- package/dist/remote-sync-handler.test.d.ts +7 -0
- package/dist/remote-sync-handler.test.d.ts.map +1 -0
- package/dist/remote-sync-handler.test.js +212 -0
- package/dist/remote-sync-handler.test.js.map +1 -0
- package/dist/retry.d.ts +35 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +63 -0
- package/dist/retry.js.map +1 -0
- package/dist/retry.test.d.ts +5 -0
- package/dist/retry.test.d.ts.map +1 -0
- package/dist/retry.test.js +84 -0
- package/dist/retry.test.js.map +1 -0
- package/dist/storage-client.d.ts +69 -0
- package/dist/storage-client.d.ts.map +1 -0
- package/dist/storage-client.js +168 -0
- package/dist/storage-client.js.map +1 -0
- package/dist/storage-client.test.d.ts +7 -0
- package/dist/storage-client.test.d.ts.map +1 -0
- package/dist/storage-client.test.js +126 -0
- package/dist/storage-client.test.js.map +1 -0
- package/dist/supabase.d.ts +82 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +341 -0
- package/dist/supabase.js.map +1 -0
- package/dist/supabase.test.d.ts +7 -0
- package/dist/supabase.test.d.ts.map +1 -0
- package/dist/supabase.test.js +273 -0
- package/dist/supabase.test.js.map +1 -0
- package/dist/sync-engine.d.ts +84 -0
- package/dist/sync-engine.d.ts.map +1 -0
- package/dist/sync-engine.js +251 -0
- package/dist/sync-engine.js.map +1 -0
- package/dist/sync-engine.test.d.ts +7 -0
- package/dist/sync-engine.test.d.ts.map +1 -0
- package/dist/sync-engine.test.js +241 -0
- package/dist/sync-engine.test.js.map +1 -0
- package/dist/sync-state.d.ts +82 -0
- package/dist/sync-state.d.ts.map +1 -0
- package/dist/sync-state.js +145 -0
- package/dist/sync-state.js.map +1 -0
- package/dist/sync-state.test.d.ts +7 -0
- package/dist/sync-state.test.d.ts.map +1 -0
- package/dist/sync-state.test.js +129 -0
- package/dist/sync-state.test.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/types.test.d.ts +7 -0
- package/dist/types.test.d.ts.map +1 -0
- package/dist/types.test.js +73 -0
- package/dist/types.test.js.map +1 -0
- package/dist/watcher.d.ts +55 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +214 -0
- package/dist/watcher.js.map +1 -0
- package/dist/watcher.test.d.ts +8 -0
- package/dist/watcher.test.d.ts.map +1 -0
- package/dist/watcher.test.js +164 -0
- package/dist/watcher.test.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.test.d.ts","sourceRoot":"","sources":["../src/discovery.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|