Auto-sync: Local changes

Change-Id: Ia2b0b18d94901b18943603fdfe991b50b0733071
diff --git a/server/.env b/server/.env
new file mode 100644
index 0000000..9407122
--- /dev/null
+++ b/server/.env
@@ -0,0 +1,2 @@
+REMOTE_REPO_URL=https://github.com/iomodo/staff.git
+WORKING_DIR=../ 
\ No newline at end of file
diff --git a/server/agent/agent.go b/server/agent/agent.go
new file mode 100644
index 0000000..e901cf6
--- /dev/null
+++ b/server/agent/agent.go
@@ -0,0 +1,23 @@
+package agent
+
+type AgentConfig struct {
+	Name        string
+	Role        string
+	GitUsername string
+	GitEmail    string
+	WorkingDir  string
+}
+
+type Agent struct {
+	Config AgentConfig
+}
+
+func NewAgent(config AgentConfig) *Agent {
+	return &Agent{
+		Config: config,
+	}
+}
+
+func (a *Agent) Run() {
+
+}
diff --git a/server/server/server.go b/server/server/server.go
index 9f95f10..ed3c62f 100644
--- a/server/server/server.go
+++ b/server/server/server.go
@@ -1,9 +1,13 @@
 package server
 
 import (
+	"context"
+	"fmt"
 	"log/slog"
 	"os"
+	"path/filepath"
 
+	"github.com/iomodo/staff/git"
 	"github.com/joho/godotenv"
 )
 
@@ -28,7 +32,36 @@
 // Start method starts an app
 func (a *Server) Start() error {
 	a.logger.Info("Server is starting...")
-	return nil
+
+	// Get environment variables
+	remoteRepoURL := os.Getenv("REMOTE_REPO_URL")
+	workingDir := os.Getenv("WORKING_DIR")
+
+	if remoteRepoURL == "" {
+		return fmt.Errorf("REMOTE_REPO_URL environment variable is required")
+	}
+
+	if workingDir == "" {
+		return fmt.Errorf("WORKING_DIR environment variable is required")
+	}
+
+	a.logger.Info("Environment variables loaded",
+		slog.String("remoteRepoURL", remoteRepoURL),
+		slog.String("workingDir", workingDir))
+
+	// Check if working directory is empty
+	isEmpty, err := a.isDirectoryEmpty(workingDir)
+	if err != nil {
+		return fmt.Errorf("failed to check if directory is empty: %w", err)
+	}
+
+	if isEmpty {
+		a.logger.Info("Working directory is empty, initializing new repository")
+		return a.initializeNewRepository(workingDir, remoteRepoURL)
+	} else {
+		a.logger.Info("Working directory is not empty, syncing with remote")
+		return a.syncWithRemote(workingDir, remoteRepoURL)
+	}
 }
 
 // Shutdown method shuts server down
@@ -36,3 +69,216 @@
 	a.logger.Info("Stoping Server...")
 	a.logger.Info("Server stopped")
 }
+
+// isDirectoryEmpty checks if a directory is empty
+func (a *Server) isDirectoryEmpty(dir string) (bool, error) {
+	// Create directory if it doesn't exist
+	if err := os.MkdirAll(dir, 0755); err != nil {
+		return false, fmt.Errorf("failed to create directory: %w", err)
+	}
+
+	entries, err := os.ReadDir(dir)
+	if err != nil {
+		return false, fmt.Errorf("failed to read directory: %w", err)
+	}
+
+	// Directory is empty if it has no entries or only has .git directory
+	if len(entries) == 0 {
+		return true, nil
+	}
+
+	// Check if directory only contains .git (which might be a git repo)
+	if len(entries) == 1 && entries[0].Name() == ".git" {
+		return true, nil
+	}
+
+	return false, nil
+}
+
+// initializeNewRepository creates a new git repository with prepopulated data
+func (a *Server) initializeNewRepository(workingDir, remoteRepoURL string) error {
+	ctx := context.Background()
+
+	// Initialize git repository
+	gitRepo := git.DefaultGit(workingDir)
+
+	a.logger.Info("Initializing git repository", slog.String("path", workingDir))
+	if err := gitRepo.Init(ctx, workingDir); err != nil {
+		return fmt.Errorf("failed to initialize git repository: %w", err)
+	}
+
+	// Set up git user configuration
+	userConfig := git.UserConfig{
+		Name:  "Staff System",
+		Email: "system@staff.com",
+	}
+	if err := gitRepo.SetUserConfig(ctx, userConfig); err != nil {
+		return fmt.Errorf("failed to set git user config: %w", err)
+	}
+
+	// Add remote origin
+	if err := gitRepo.AddRemote(ctx, "origin", remoteRepoURL); err != nil {
+		return fmt.Errorf("failed to add remote origin: %w", err)
+	}
+
+	// Create prepopulated directory structure and files
+	if err := a.createPrepopulatedStructure(workingDir); err != nil {
+		return fmt.Errorf("failed to create prepopulated structure: %w", err)
+	}
+
+	// Add all files to git
+	if err := gitRepo.AddAll(ctx); err != nil {
+		return fmt.Errorf("failed to add files to git: %w", err)
+	}
+
+	// Commit the initial structure
+	if err := gitRepo.Commit(ctx, "Initial commit: Add prepopulated project structure", git.CommitOptions{}); err != nil {
+		return fmt.Errorf("failed to commit initial structure: %w", err)
+	}
+
+	// Push to remote
+	if err := gitRepo.Push(ctx, "origin", "main", git.PushOptions{SetUpstream: true}); err != nil {
+		return fmt.Errorf("failed to push to remote: %w", err)
+	}
+
+	a.logger.Info("Successfully initialized repository and pushed to remote")
+	return nil
+}
+
+// syncWithRemote synchronizes local repository with remote
+func (a *Server) syncWithRemote(workingDir, remoteRepoURL string) error {
+	ctx := context.Background()
+
+	// Check if it's a git repository
+	gitRepo := git.DefaultGit(workingDir)
+	isRepo, err := gitRepo.IsRepository(ctx, workingDir)
+	if err != nil {
+		return fmt.Errorf("failed to check if directory is git repository: %w", err)
+	}
+
+	if !isRepo {
+		return fmt.Errorf("working directory is not a git repository")
+	}
+
+	// Get current status
+	status, err := gitRepo.Status(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to get git status: %w", err)
+	}
+
+	a.logger.Info("Current git status",
+		slog.String("branch", status.Branch),
+		slog.Bool("isClean", status.IsClean),
+		slog.Int("stagedFiles", len(status.Staged)),
+		slog.Int("unstagedFiles", len(status.Unstaged)),
+		slog.Int("untrackedFiles", len(status.Untracked)))
+
+	// Check if remote origin exists, if not add it
+	remotes, err := gitRepo.ListRemotes(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to list remotes: %w", err)
+	}
+
+	originExists := false
+	for _, remote := range remotes {
+		if remote.Name == "origin" {
+			originExists = true
+			break
+		}
+	}
+
+	if !originExists {
+		a.logger.Info("Adding remote origin")
+		if err := gitRepo.AddRemote(ctx, "origin", remoteRepoURL); err != nil {
+			return fmt.Errorf("failed to add remote origin: %w", err)
+		}
+	}
+
+	// Fetch latest changes from remote
+	a.logger.Info("Fetching latest changes from remote")
+	if err := gitRepo.Fetch(ctx, "origin", git.FetchOptions{}); err != nil {
+		return fmt.Errorf("failed to fetch from remote: %w", err)
+	}
+
+	// If there are local changes, commit and push them
+	if !status.IsClean {
+		a.logger.Info("Local changes detected, committing and pushing")
+
+		if err := gitRepo.AddAll(ctx); err != nil {
+			return fmt.Errorf("failed to add local changes: %w", err)
+		}
+
+		if err := gitRepo.Commit(ctx, "Auto-sync: Local changes", git.CommitOptions{}); err != nil {
+			return fmt.Errorf("failed to commit local changes: %w", err)
+		}
+
+		if err := gitRepo.Push(ctx, "origin", status.Branch, git.PushOptions{}); err != nil {
+			return fmt.Errorf("failed to push local changes: %w", err)
+		}
+	}
+
+	// Pull latest changes from remote
+	a.logger.Info("Pulling latest changes from remote")
+	if err := gitRepo.Pull(ctx, "origin", status.Branch); err != nil {
+		return fmt.Errorf("failed to pull from remote: %w", err)
+	}
+
+	a.logger.Info("Successfully synchronized with remote repository")
+	return nil
+}
+
+// createPrepopulatedStructure creates the required directory structure and files
+func (a *Server) createPrepopulatedStructure(workingDir string) error {
+	// Create directories
+	dirs := []string{
+		filepath.Join(workingDir, "operations", "agents", "ceo"),
+		filepath.Join(workingDir, "operations", "agents", "pm"),
+		filepath.Join(workingDir, "operations", "tasks"),
+		filepath.Join(workingDir, "server"),
+		filepath.Join(workingDir, "webapp"),
+	}
+
+	for _, dir := range dirs {
+		if err := os.MkdirAll(dir, 0755); err != nil {
+			return fmt.Errorf("failed to create directory %s: %w", dir, err)
+		}
+	}
+
+	// Read agent system files from the current project
+	ceoSystemPath := filepath.Join("operations", "agents", "ceo", "system.md")
+	pmSystemPath := filepath.Join("operations", "agents", "pm", "system.md")
+	taskExamplePath := filepath.Join("operations", "tasks", "example-task-file.md")
+
+	// Read CEO system file
+	ceoContent, err := os.ReadFile(ceoSystemPath)
+	if err != nil {
+		return fmt.Errorf("failed to read CEO system file: %w", err)
+	}
+
+	// Read PM system file
+	pmContent, err := os.ReadFile(pmSystemPath)
+	if err != nil {
+		return fmt.Errorf("failed to read PM system file: %w", err)
+	}
+
+	// Read task example file
+	taskExampleContent, err := os.ReadFile(taskExamplePath)
+	if err != nil {
+		return fmt.Errorf("failed to read task example file: %w", err)
+	}
+
+	// Create prepopulated files
+	files := map[string][]byte{
+		filepath.Join(workingDir, "operations", "agents", "ceo", "system.md"):    ceoContent,
+		filepath.Join(workingDir, "operations", "agents", "pm", "system.md"):     pmContent,
+		filepath.Join(workingDir, "operations", "tasks", "example-task-file.md"): taskExampleContent,
+	}
+	for filePath, content := range files {
+		if err := os.WriteFile(filePath, content, 0644); err != nil {
+			return fmt.Errorf("failed to create file %s: %w", filePath, err)
+		}
+	}
+
+	a.logger.Info("Created prepopulated directory structure and files")
+	return nil
+}