Add git implementation of task manager

Change-Id: I1e0925e54fa167af9459eceea7a2cae082bc4504
diff --git a/server/go.mod b/server/go.mod
index 621072a..d2079f1 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -3,11 +3,16 @@
 go 1.24.4
 
 require (
+	github.com/google/uuid v1.6.0
 	github.com/joho/godotenv v1.5.1
 	github.com/spf13/cobra v1.9.1
+	github.com/stretchr/testify v1.10.0
+	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/spf13/pflag v1.0.6 // indirect
 )
diff --git a/server/go.sum b/server/go.sum
index d2f5463..12bafed 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -1,12 +1,22 @@
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/server/tm/git_tm/example.go b/server/tm/git_tm/example.go
new file mode 100644
index 0000000..39541fa
--- /dev/null
+++ b/server/tm/git_tm/example.go
@@ -0,0 +1,114 @@
+package git_tm
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/iomodo/staff/git"
+	"github.com/iomodo/staff/tm"
+)
+
+// Example demonstrates how to use the GitTaskManager
+func Example() {
+	// Initialize git interface
+	gitInterface := git.DefaultGit("./tasks-repo")
+
+	// Create task manager
+	taskManager := NewGitTaskManager(gitInterface, "./tasks-repo")
+
+	// Create a new task
+	ctx := context.Background()
+	dueDate := time.Now().AddDate(0, 0, 7) // Due in 7 days
+
+	createReq := &tm.TaskCreateRequest{
+		Title:       "Implement user authentication",
+		Description: "Add login/logout functionality with JWT tokens",
+		OwnerID:     "john.doe",
+		Priority:    tm.PriorityHigh,
+		DueDate:     &dueDate,
+	}
+
+	task, err := taskManager.CreateTask(ctx, createReq)
+	if err != nil {
+		log.Fatalf("Failed to create task: %v", err)
+	}
+
+	fmt.Printf("Created task: %s\n", task.ID)
+
+	// Get the task
+	retrievedTask, err := taskManager.GetTask(ctx, task.ID)
+	if err != nil {
+		log.Fatalf("Failed to get task: %v", err)
+	}
+
+	fmt.Printf("Retrieved task: %s - %s\n", retrievedTask.ID, retrievedTask.Title)
+
+	// Start the task
+	startedTask, err := taskManager.StartTask(ctx, task.ID)
+	if err != nil {
+		log.Fatalf("Failed to start task: %v", err)
+	}
+
+	fmt.Printf("Started task: %s (status: %s)\n", startedTask.ID, startedTask.Status)
+
+	// List all tasks
+	taskList, err := taskManager.ListTasks(ctx, nil, 0, 10)
+	if err != nil {
+		log.Fatalf("Failed to list tasks: %v", err)
+	}
+
+	fmt.Printf("Total tasks: %d\n", taskList.TotalCount)
+	for _, t := range taskList.Tasks {
+		fmt.Printf("- %s: %s (%s)\n", t.ID, t.Title, t.Status)
+	}
+
+	// Complete the task
+	completedTask, err := taskManager.CompleteTask(ctx, task.ID)
+	if err != nil {
+		log.Fatalf("Failed to complete task: %v", err)
+	}
+
+	fmt.Printf("Completed task: %s (completed at: %s)\n",
+		completedTask.ID, completedTask.CompletedAt.Format(time.RFC3339))
+}
+
+// ExampleTaskFile shows the format of a task file
+func ExampleTaskFile() {
+	fmt.Print(`Example task file (tasks/task-1704067200-abc123.md):
+
+---
+id: task-1704067200-abc123
+title: Implement user authentication
+description: Add login/logout functionality with JWT tokens
+owner_id: john.doe
+owner_name: John Doe
+status: in_progress
+priority: high
+created_at: 2024-01-01T10:00:00Z
+updated_at: 2024-01-01T15:30:00Z
+due_date: 2024-01-08T17:00:00Z
+completed_at: null
+archived_at: null
+---
+
+# Task Description
+
+Add login/logout functionality with JWT tokens for the web application.
+
+## Requirements
+
+- User registration and login forms
+- JWT token generation and validation
+- Password hashing with bcrypt
+- Session management
+- Logout functionality
+
+## Notes
+
+- Consider using bcrypt for password hashing
+- Implement refresh token mechanism
+- Add rate limiting for login attempts
+`)
+}
diff --git a/server/tm/git_tm/git_task_manager.go b/server/tm/git_tm/git_task_manager.go
new file mode 100644
index 0000000..02d5197
--- /dev/null
+++ b/server/tm/git_tm/git_task_manager.go
@@ -0,0 +1,509 @@
+package git_tm
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"time"
+
+	"github.com/google/uuid"
+	"github.com/iomodo/staff/git"
+	"github.com/iomodo/staff/tm"
+	"gopkg.in/yaml.v3"
+)
+
+// GitTaskManager implements TaskManager interface using git as the source of truth
+type GitTaskManager struct {
+	git      git.GitInterface
+	repoPath string
+	tasksDir string
+}
+
+// NewGitTaskManager creates a new GitTaskManager instance
+func NewGitTaskManager(git git.GitInterface, repoPath string) *GitTaskManager {
+	return &GitTaskManager{
+		git:      git,
+		repoPath: repoPath,
+		tasksDir: filepath.Join(repoPath, "tasks"),
+	}
+}
+
+// ensureTasksDir ensures the tasks directory exists
+func (gtm *GitTaskManager) ensureTasksDir() error {
+	if err := os.MkdirAll(gtm.tasksDir, 0755); err != nil {
+		return fmt.Errorf("failed to create tasks directory: %w", err)
+	}
+	return nil
+}
+
+// generateTaskID generates a unique task ID
+func (gtm *GitTaskManager) generateTaskID() string {
+	timestamp := time.Now().Unix()
+	random := uuid.New().String()[:8]
+	return fmt.Sprintf("task-%d-%s", timestamp, random)
+}
+
+// taskToMarkdown converts a Task to markdown format
+func (gtm *GitTaskManager) taskToMarkdown(task *tm.Task) (string, error) {
+	// Create frontmatter data
+	frontmatter := map[string]interface{}{
+		"id":          task.ID,
+		"title":       task.Title,
+		"description": task.Description,
+		"owner_id":    task.Owner.ID,
+		"owner_name":  task.Owner.Name,
+		"status":      task.Status,
+		"priority":    task.Priority,
+		"created_at":  task.CreatedAt.Format(time.RFC3339),
+		"updated_at":  task.UpdatedAt.Format(time.RFC3339),
+	}
+
+	if task.DueDate != nil {
+		frontmatter["due_date"] = task.DueDate.Format(time.RFC3339)
+	}
+	if task.CompletedAt != nil {
+		frontmatter["completed_at"] = task.CompletedAt.Format(time.RFC3339)
+	}
+	if task.ArchivedAt != nil {
+		frontmatter["archived_at"] = task.ArchivedAt.Format(time.RFC3339)
+	}
+
+	// Marshal frontmatter to YAML
+	yamlData, err := yaml.Marshal(frontmatter)
+	if err != nil {
+		return "", fmt.Errorf("failed to marshal frontmatter: %w", err)
+	}
+
+	// Build markdown content
+	var content strings.Builder
+	content.WriteString("---\n")
+	content.Write(yamlData)
+	content.WriteString("---\n\n")
+
+	if task.Description != "" {
+		content.WriteString("# Task Description\n\n")
+		content.WriteString(task.Description)
+		content.WriteString("\n\n")
+	}
+
+	return content.String(), nil
+}
+
+// parseTaskFromMarkdown parses a Task from markdown format
+func (gtm *GitTaskManager) parseTaskFromMarkdown(content string) (*tm.Task, error) {
+	// Split content into frontmatter and body
+	parts := strings.SplitN(content, "---\n", 3)
+	if len(parts) < 3 {
+		return nil, fmt.Errorf("invalid markdown format: missing frontmatter")
+	}
+
+	// Parse YAML frontmatter
+	var frontmatter map[string]interface{}
+	if err := yaml.Unmarshal([]byte(parts[1]), &frontmatter); err != nil {
+		return nil, fmt.Errorf("failed to parse frontmatter: %w", err)
+	}
+
+	// Extract task data
+	task := &tm.Task{}
+
+	if id, ok := frontmatter["id"].(string); ok {
+		task.ID = id
+	}
+	if title, ok := frontmatter["title"].(string); ok {
+		task.Title = title
+	}
+	if description, ok := frontmatter["description"].(string); ok {
+		task.Description = description
+	}
+	if ownerID, ok := frontmatter["owner_id"].(string); ok {
+		task.Owner.ID = ownerID
+	}
+	if ownerName, ok := frontmatter["owner_name"].(string); ok {
+		task.Owner.Name = ownerName
+	}
+	if status, ok := frontmatter["status"].(string); ok {
+		task.Status = tm.TaskStatus(status)
+	}
+	if priority, ok := frontmatter["priority"].(string); ok {
+		task.Priority = tm.TaskPriority(priority)
+	}
+
+	// Parse timestamps
+	if createdAt, ok := frontmatter["created_at"].(string); ok {
+		if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
+			task.CreatedAt = t
+		}
+	}
+	if updatedAt, ok := frontmatter["updated_at"].(string); ok {
+		if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
+			task.UpdatedAt = t
+		}
+	}
+	if dueDate, ok := frontmatter["due_date"].(string); ok {
+		if t, err := time.Parse(time.RFC3339, dueDate); err == nil {
+			task.DueDate = &t
+		}
+	}
+	if completedAt, ok := frontmatter["completed_at"].(string); ok {
+		if t, err := time.Parse(time.RFC3339, completedAt); err == nil {
+			task.CompletedAt = &t
+		}
+	}
+	if archivedAt, ok := frontmatter["archived_at"].(string); ok {
+		if t, err := time.Parse(time.RFC3339, archivedAt); err == nil {
+			task.ArchivedAt = &t
+		}
+	}
+
+	return task, nil
+}
+
+// readTaskFile reads a task from a file
+func (gtm *GitTaskManager) readTaskFile(taskID string) (*tm.Task, error) {
+	filePath := filepath.Join(gtm.tasksDir, taskID+".md")
+
+	content, err := os.ReadFile(filePath)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil, tm.ErrTaskNotFound
+		}
+		return nil, fmt.Errorf("failed to read task file: %w", err)
+	}
+
+	return gtm.parseTaskFromMarkdown(string(content))
+}
+
+// writeTaskFile writes a task to a file
+func (gtm *GitTaskManager) writeTaskFile(task *tm.Task) error {
+	content, err := gtm.taskToMarkdown(task)
+	if err != nil {
+		return fmt.Errorf("failed to convert task to markdown: %w", err)
+	}
+
+	filePath := filepath.Join(gtm.tasksDir, task.ID+".md")
+	if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
+		return fmt.Errorf("failed to write task file: %w", err)
+	}
+
+	return nil
+}
+
+// listTaskFiles returns all task file paths
+func (gtm *GitTaskManager) listTaskFiles() ([]string, error) {
+	entries, err := os.ReadDir(gtm.tasksDir)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return []string{}, nil
+		}
+		return nil, fmt.Errorf("failed to read tasks directory: %w", err)
+	}
+
+	var taskFiles []string
+	for _, entry := range entries {
+		if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".md") {
+			taskID := strings.TrimSuffix(entry.Name(), ".md")
+			taskFiles = append(taskFiles, taskID)
+		}
+	}
+
+	return taskFiles, nil
+}
+
+// commitTaskChange commits a task change to git
+func (gtm *GitTaskManager) commitTaskChange(taskID, operation string) error {
+	ctx := context.Background()
+
+	// Add the task file
+	if err := gtm.git.Add(ctx, []string{filepath.Join("tasks", taskID+".md")}); err != nil {
+		return fmt.Errorf("failed to add task file: %w", err)
+	}
+
+	// Commit the change
+	message := fmt.Sprintf("task: %s - %s", taskID, operation)
+	if err := gtm.git.Commit(ctx, message, git.CommitOptions{}); err != nil {
+		return fmt.Errorf("failed to commit task change: %w", err)
+	}
+
+	return nil
+}
+
+// CreateTask creates a new task
+func (gtm *GitTaskManager) CreateTask(ctx context.Context, req *tm.TaskCreateRequest) (*tm.Task, error) {
+	if err := gtm.ensureTasksDir(); err != nil {
+		return nil, err
+	}
+
+	// Validate request
+	if req.Title == "" {
+		return nil, tm.ErrInvalidTaskData
+	}
+	if req.OwnerID == "" {
+		return nil, tm.ErrInvalidOwner
+	}
+
+	// Generate task ID
+	taskID := gtm.generateTaskID()
+	now := time.Now()
+
+	// Create task
+	task := &tm.Task{
+		ID:          taskID,
+		Title:       req.Title,
+		Description: req.Description,
+		Owner: tm.Owner{
+			ID:   req.OwnerID,
+			Name: req.OwnerID, // TODO: Look up owner name from a user service
+		},
+		Status:    tm.StatusToDo,
+		Priority:  req.Priority,
+		CreatedAt: now,
+		UpdatedAt: now,
+		DueDate:   req.DueDate,
+	}
+
+	// Write task file
+	if err := gtm.writeTaskFile(task); err != nil {
+		return nil, err
+	}
+
+	// Commit to git
+	if err := gtm.commitTaskChange(taskID, "created"); err != nil {
+		return nil, err
+	}
+
+	return task, nil
+}
+
+// GetTask retrieves a task by ID
+func (gtm *GitTaskManager) GetTask(ctx context.Context, id string) (*tm.Task, error) {
+	return gtm.readTaskFile(id)
+}
+
+// UpdateTask updates an existing task
+func (gtm *GitTaskManager) UpdateTask(ctx context.Context, id string, req *tm.TaskUpdateRequest) (*tm.Task, error) {
+	// Read existing task
+	task, err := gtm.readTaskFile(id)
+	if err != nil {
+		return nil, err
+	}
+
+	// Update fields
+	updated := false
+	if req.Title != nil {
+		task.Title = *req.Title
+		updated = true
+	}
+	if req.Description != nil {
+		task.Description = *req.Description
+		updated = true
+	}
+	if req.OwnerID != nil {
+		task.Owner.ID = *req.OwnerID
+		task.Owner.Name = *req.OwnerID // TODO: Look up owner name from a user service
+		updated = true
+	}
+	if req.Status != nil {
+		task.Status = *req.Status
+		updated = true
+	}
+	if req.Priority != nil {
+		task.Priority = *req.Priority
+		updated = true
+	}
+	if req.DueDate != nil {
+		task.DueDate = req.DueDate
+		updated = true
+	}
+
+	if !updated {
+		return task, nil
+	}
+
+	// Update timestamps
+	task.UpdatedAt = time.Now()
+
+	// Handle status-specific timestamps
+	if req.Status != nil {
+		switch *req.Status {
+		case tm.StatusCompleted:
+			if task.CompletedAt == nil {
+				now := time.Now()
+				task.CompletedAt = &now
+			}
+		case tm.StatusArchived:
+			if task.ArchivedAt == nil {
+				now := time.Now()
+				task.ArchivedAt = &now
+			}
+		}
+	}
+
+	// Write updated task
+	if err := gtm.writeTaskFile(task); err != nil {
+		return nil, err
+	}
+
+	// Commit to git
+	if err := gtm.commitTaskChange(id, "updated"); err != nil {
+		return nil, err
+	}
+
+	return task, nil
+}
+
+// ArchiveTask archives a task
+func (gtm *GitTaskManager) ArchiveTask(ctx context.Context, id string) error {
+	status := tm.StatusArchived
+	req := &tm.TaskUpdateRequest{
+		Status: &status,
+	}
+
+	_, err := gtm.UpdateTask(ctx, id, req)
+	return err
+}
+
+// ListTasks lists tasks with filtering and pagination
+func (gtm *GitTaskManager) ListTasks(ctx context.Context, filter *tm.TaskFilter, page, pageSize int) (*tm.TaskList, error) {
+	// Get all task files
+	taskFiles, err := gtm.listTaskFiles()
+	if err != nil {
+		return nil, err
+	}
+
+	// Read all tasks
+	var tasks []*tm.Task
+	for _, taskID := range taskFiles {
+		task, err := gtm.readTaskFile(taskID)
+		if err != nil {
+			continue // Skip corrupted files
+		}
+		tasks = append(tasks, task)
+	}
+
+	// Apply filters
+	if filter != nil {
+		tasks = gtm.filterTasks(tasks, filter)
+	}
+
+	// Sort by creation date (newest first)
+	sort.Slice(tasks, func(i, j int) bool {
+		return tasks[i].CreatedAt.After(tasks[j].CreatedAt)
+	})
+
+	// Apply pagination
+	totalCount := len(tasks)
+	start := page * pageSize
+	end := start + pageSize
+
+	if start >= totalCount {
+		return &tm.TaskList{
+			Tasks:      []*tm.Task{},
+			TotalCount: totalCount,
+			Page:       page,
+			PageSize:   pageSize,
+			HasMore:    false,
+		}, nil
+	}
+
+	if end > totalCount {
+		end = totalCount
+	}
+
+	return &tm.TaskList{
+		Tasks:      tasks[start:end],
+		TotalCount: totalCount,
+		Page:       page,
+		PageSize:   pageSize,
+		HasMore:    end < totalCount,
+	}, nil
+}
+
+// filterTasks applies filters to a list of tasks
+func (gtm *GitTaskManager) filterTasks(tasks []*tm.Task, filter *tm.TaskFilter) []*tm.Task {
+	var filtered []*tm.Task
+
+	for _, task := range tasks {
+		if gtm.taskMatchesFilter(task, filter) {
+			filtered = append(filtered, task)
+		}
+	}
+
+	return filtered
+}
+
+// taskMatchesFilter checks if a task matches the given filter
+func (gtm *GitTaskManager) taskMatchesFilter(task *tm.Task, filter *tm.TaskFilter) bool {
+	if filter.OwnerID != nil && task.Owner.ID != *filter.OwnerID {
+		return false
+	}
+	if filter.Status != nil && task.Status != *filter.Status {
+		return false
+	}
+	if filter.Priority != nil && task.Priority != *filter.Priority {
+		return false
+	}
+	if filter.DueBefore != nil && (task.DueDate == nil || !task.DueDate.Before(*filter.DueBefore)) {
+		return false
+	}
+	if filter.DueAfter != nil && (task.DueDate == nil || !task.DueDate.After(*filter.DueAfter)) {
+		return false
+	}
+	if filter.CreatedAfter != nil && !task.CreatedAt.After(*filter.CreatedAfter) {
+		return false
+	}
+	if filter.CreatedBefore != nil && !task.CreatedAt.Before(*filter.CreatedBefore) {
+		return false
+	}
+
+	return true
+}
+
+// StartTask starts a task (changes status to in_progress)
+func (gtm *GitTaskManager) StartTask(ctx context.Context, id string) (*tm.Task, error) {
+	status := tm.StatusInProgress
+	req := &tm.TaskUpdateRequest{
+		Status: &status,
+	}
+
+	return gtm.UpdateTask(ctx, id, req)
+}
+
+// CompleteTask completes a task (changes status to completed)
+func (gtm *GitTaskManager) CompleteTask(ctx context.Context, id string) (*tm.Task, error) {
+	status := tm.StatusCompleted
+	req := &tm.TaskUpdateRequest{
+		Status: &status,
+	}
+
+	return gtm.UpdateTask(ctx, id, req)
+}
+
+// GetTasksByOwner gets tasks for a specific owner
+func (gtm *GitTaskManager) GetTasksByOwner(ctx context.Context, ownerID string, page, pageSize int) (*tm.TaskList, error) {
+	filter := &tm.TaskFilter{
+		OwnerID: &ownerID,
+	}
+	return gtm.ListTasks(ctx, filter, page, pageSize)
+}
+
+// GetTasksByStatus gets tasks with a specific status
+func (gtm *GitTaskManager) GetTasksByStatus(ctx context.Context, status tm.TaskStatus, page, pageSize int) (*tm.TaskList, error) {
+	filter := &tm.TaskFilter{
+		Status: &status,
+	}
+	return gtm.ListTasks(ctx, filter, page, pageSize)
+}
+
+// GetTasksByPriority gets tasks with a specific priority
+func (gtm *GitTaskManager) GetTasksByPriority(ctx context.Context, priority tm.TaskPriority, page, pageSize int) (*tm.TaskList, error) {
+	filter := &tm.TaskFilter{
+		Priority: &priority,
+	}
+	return gtm.ListTasks(ctx, filter, page, pageSize)
+}
+
+// Ensure GitTaskManager implements TaskManager interface
+var _ tm.TaskManager = (*GitTaskManager)(nil)
diff --git a/server/tm/git_tm/git_task_manager_test.go b/server/tm/git_tm/git_task_manager_test.go
new file mode 100644
index 0000000..cd37838
--- /dev/null
+++ b/server/tm/git_tm/git_task_manager_test.go
@@ -0,0 +1,1021 @@
+package git_tm
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"github.com/iomodo/staff/git"
+	"github.com/iomodo/staff/tm"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// Test helper functions
+func setupTestDir(t *testing.T) (string, func()) {
+	tempDir, err := os.MkdirTemp("", "git-task-manager-test")
+	require.NoError(t, err)
+
+	cleanup := func() {
+		os.RemoveAll(tempDir)
+	}
+
+	return tempDir, cleanup
+}
+
+func createTestTaskManager(t *testing.T, repoPath string) (*GitTaskManager, git.GitInterface) {
+	// Initialize git repository
+	gitImpl := git.DefaultGit(repoPath)
+	ctx := context.Background()
+
+	err := gitImpl.Init(ctx, repoPath)
+	require.NoError(t, err)
+
+	// Set up git user config for commits
+	userConfig := git.UserConfig{
+		Name:  "Test User",
+		Email: "test@example.com",
+	}
+	err = gitImpl.SetUserConfig(ctx, userConfig)
+	require.NoError(t, err)
+
+	gtm := NewGitTaskManager(gitImpl, repoPath)
+	return gtm, gitImpl
+}
+
+// Test cases
+func TestNewGitTaskManager(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gitImpl := git.DefaultGit(tempDir)
+	gtm := NewGitTaskManager(gitImpl, tempDir)
+
+	assert.NotNil(t, gtm)
+	assert.Equal(t, gitImpl, gtm.git)
+	assert.Equal(t, tempDir, gtm.repoPath)
+	assert.Equal(t, filepath.Join(tempDir, "tasks"), gtm.tasksDir)
+}
+
+func TestEnsureTasksDir(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Test creating tasks directory
+	err := gtm.ensureTasksDir()
+	assert.NoError(t, err)
+
+	// Verify directory exists
+	_, err = os.Stat(gtm.tasksDir)
+	assert.NoError(t, err)
+
+	// Test creating again (should not error)
+	err = gtm.ensureTasksDir()
+	assert.NoError(t, err)
+}
+
+func TestGenerateTaskID(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	id1 := gtm.generateTaskID()
+	id2 := gtm.generateTaskID()
+
+	assert.NotEmpty(t, id1)
+	assert.NotEmpty(t, id2)
+	assert.NotEqual(t, id1, id2)
+	assert.Contains(t, id1, "task-")
+}
+
+func TestTaskToMarkdown(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	now := time.Now()
+	dueDate := now.Add(24 * time.Hour)
+	completedAt := now.Add(12 * time.Hour)
+
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "This is a test task",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:      tm.StatusToDo,
+		Priority:    tm.PriorityHigh,
+		CreatedAt:   now,
+		UpdatedAt:   now,
+		DueDate:     &dueDate,
+		CompletedAt: &completedAt,
+	}
+
+	markdown, err := gtm.taskToMarkdown(task)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, markdown)
+	assert.Contains(t, markdown, "---")
+	assert.Contains(t, markdown, "id: test-task-123")
+	assert.Contains(t, markdown, "title: Test Task")
+	assert.Contains(t, markdown, "description: This is a test task")
+	assert.Contains(t, markdown, "owner_id: user123")
+	assert.Contains(t, markdown, "owner_name: Test User")
+	assert.Contains(t, markdown, "status: todo")
+	assert.Contains(t, markdown, "priority: high")
+	assert.Contains(t, markdown, "# Task Description")
+	assert.Contains(t, markdown, "This is a test task")
+}
+
+func TestParseTaskFromMarkdown(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	markdown := `---
+id: test-task-123
+title: Test Task
+description: This is a test task
+owner_id: user123
+owner_name: Test User
+status: todo
+priority: high
+created_at: 2023-01-01T00:00:00Z
+updated_at: 2023-01-01T00:00:00Z
+due_date: 2023-01-02T00:00:00Z
+completed_at: 2023-01-01T12:00:00Z
+---
+
+# Task Description
+
+This is a test task
+`
+
+	task, err := gtm.parseTaskFromMarkdown(markdown)
+	assert.NoError(t, err)
+	assert.NotNil(t, task)
+	assert.Equal(t, "test-task-123", task.ID)
+	assert.Equal(t, "Test Task", task.Title)
+	assert.Equal(t, "This is a test task", task.Description)
+	assert.Equal(t, "user123", task.Owner.ID)
+	assert.Equal(t, "Test User", task.Owner.Name)
+	assert.Equal(t, tm.StatusToDo, task.Status)
+	assert.Equal(t, tm.PriorityHigh, task.Priority)
+}
+
+func TestParseTaskFromMarkdownInvalid(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Test invalid markdown format
+	invalidMarkdown := "This is not valid markdown"
+
+	task, err := gtm.parseTaskFromMarkdown(invalidMarkdown)
+	assert.Error(t, err)
+	assert.Nil(t, task)
+	assert.Contains(t, err.Error(), "invalid markdown format")
+}
+
+func TestWriteAndReadTaskFile(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Ensure tasks directory exists
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+
+	// Create test task
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "This is a test task",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityHigh,
+		CreatedAt: time.Now(),
+		UpdatedAt: time.Now(),
+	}
+
+	// Write task file
+	err = gtm.writeTaskFile(task)
+	assert.NoError(t, err)
+
+	// Verify file exists
+	filePath := filepath.Join(gtm.tasksDir, task.ID+".md")
+	_, err = os.Stat(filePath)
+	assert.NoError(t, err)
+
+	// Read task file
+	readTask, err := gtm.readTaskFile(task.ID)
+	assert.NoError(t, err)
+	assert.NotNil(t, readTask)
+	assert.Equal(t, task.ID, readTask.ID)
+	assert.Equal(t, task.Title, readTask.Title)
+	assert.Equal(t, task.Description, readTask.Description)
+	assert.Equal(t, task.Owner.ID, readTask.Owner.ID)
+	assert.Equal(t, task.Owner.Name, readTask.Owner.Name)
+	assert.Equal(t, task.Status, readTask.Status)
+	assert.Equal(t, task.Priority, readTask.Priority)
+}
+
+func TestReadTaskFileNotFound(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Try to read non-existent task
+	task, err := gtm.readTaskFile("non-existent-task")
+	assert.Error(t, err)
+	assert.Nil(t, task)
+	assert.Equal(t, tm.ErrTaskNotFound, err)
+}
+
+func TestListTaskFiles(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Ensure tasks directory exists
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+
+	// Create some test task files
+	taskIDs := []string{"task-1", "task-2", "task-3"}
+	for _, id := range taskIDs {
+		task := &tm.Task{
+			ID:          id,
+			Title:       "Test Task " + id,
+			Description: "Test task description",
+			Owner: tm.Owner{
+				ID:   "user123",
+				Name: "Test User",
+			},
+			Status:    tm.StatusToDo,
+			Priority:  tm.PriorityMedium,
+			CreatedAt: time.Now(),
+			UpdatedAt: time.Now(),
+		}
+		err = gtm.writeTaskFile(task)
+		require.NoError(t, err)
+	}
+
+	// Create a non-task file
+	nonTaskFile := filepath.Join(gtm.tasksDir, "readme.txt")
+	err = os.WriteFile(nonTaskFile, []byte("This is not a task"), 0644)
+	require.NoError(t, err)
+
+	// List task files
+	taskFiles, err := gtm.listTaskFiles()
+	assert.NoError(t, err)
+	assert.Len(t, taskFiles, 3)
+
+	// Verify all task IDs are present
+	for _, id := range taskIDs {
+		assert.Contains(t, taskFiles, id)
+	}
+}
+
+func TestListTaskFilesEmpty(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// List task files in non-existent directory
+	taskFiles, err := gtm.listTaskFiles()
+	assert.NoError(t, err)
+	assert.Empty(t, taskFiles)
+}
+
+func TestCommitTaskChange(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, gitImpl := createTestTaskManager(t, tempDir)
+
+	// Create a test task file first
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now(),
+		UpdatedAt: time.Now(),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(task)
+	require.NoError(t, err)
+
+	// Test successful commit
+	err = gtm.commitTaskChange("test-task-123", "created")
+	assert.NoError(t, err)
+
+	// Verify commit was created
+	ctx := context.Background()
+	commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 1})
+	assert.NoError(t, err)
+	if len(commits) > 0 {
+		assert.Contains(t, commits[0].Message, "test-task-123")
+		assert.Contains(t, commits[0].Message, "created")
+	}
+}
+
+func TestCreateTask(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, gitImpl := createTestTaskManager(t, tempDir)
+
+	ctx := context.Background()
+	req := &tm.TaskCreateRequest{
+		Title:       "New Test Task",
+		Description: "This is a new test task",
+		OwnerID:     "user123",
+		Priority:    tm.PriorityHigh,
+	}
+
+	task, err := gtm.CreateTask(ctx, req)
+	assert.NoError(t, err)
+	assert.NotNil(t, task)
+
+	// Verify task properties
+	assert.NotEmpty(t, task.ID)
+	assert.Contains(t, task.ID, "task-")
+	assert.Equal(t, req.Title, task.Title)
+	assert.Equal(t, req.Description, task.Description)
+	assert.Equal(t, req.OwnerID, task.Owner.ID)
+	assert.Equal(t, req.OwnerID, task.Owner.Name) // TODO: Should look up actual name
+	assert.Equal(t, tm.StatusToDo, task.Status)
+	assert.Equal(t, req.Priority, task.Priority)
+	assert.False(t, task.CreatedAt.IsZero())
+	assert.False(t, task.UpdatedAt.IsZero())
+
+	// Verify git commit was created
+	commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 1})
+	assert.NoError(t, err)
+	if len(commits) > 0 {
+		assert.Contains(t, commits[0].Message, task.ID)
+		assert.Contains(t, commits[0].Message, "created")
+	}
+}
+
+func TestCreateTaskInvalidData(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	ctx := context.Background()
+
+	// Test empty title
+	req := &tm.TaskCreateRequest{
+		Title:   "",
+		OwnerID: "user123",
+	}
+
+	task, err := gtm.CreateTask(ctx, req)
+	assert.Error(t, err)
+	assert.Nil(t, task)
+	assert.Equal(t, tm.ErrInvalidTaskData, err)
+
+	// Test empty owner ID
+	req = &tm.TaskCreateRequest{
+		Title:   "Valid Title",
+		OwnerID: "",
+	}
+
+	task, err = gtm.CreateTask(ctx, req)
+	assert.Error(t, err)
+	assert.Nil(t, task)
+	assert.Equal(t, tm.ErrInvalidOwner, err)
+}
+
+func TestGetTask(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test task description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now(),
+		UpdatedAt: time.Now(),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(task)
+	require.NoError(t, err)
+
+	// Get the task
+	ctx := context.Background()
+	retrievedTask, err := gtm.GetTask(ctx, task.ID)
+	assert.NoError(t, err)
+	assert.NotNil(t, retrievedTask)
+	assert.Equal(t, task.ID, retrievedTask.ID)
+	assert.Equal(t, task.Title, retrievedTask.Title)
+}
+
+func TestGetTaskNotFound(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	ctx := context.Background()
+	task, err := gtm.GetTask(ctx, "non-existent-task")
+	assert.Error(t, err)
+	assert.Nil(t, task)
+	assert.Equal(t, tm.ErrTaskNotFound, err)
+}
+
+func TestUpdateTask(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, gitImpl := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	originalTask := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Original Title",
+		Description: "Original description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Original User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityLow,
+		CreatedAt: time.Now().Add(-time.Hour),
+		UpdatedAt: time.Now().Add(-time.Hour),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(originalTask)
+	require.NoError(t, err)
+
+	// Commit the initial task
+	err = gtm.commitTaskChange(originalTask.ID, "created")
+	require.NoError(t, err)
+
+	// Update the task
+	ctx := context.Background()
+	newTitle := "Updated Title"
+	newDescription := "Updated description"
+	newStatus := tm.StatusInProgress
+	newPriority := tm.PriorityHigh
+	newOwnerID := "user456"
+
+	req := &tm.TaskUpdateRequest{
+		Title:       &newTitle,
+		Description: &newDescription,
+		Status:      &newStatus,
+		Priority:    &newPriority,
+		OwnerID:     &newOwnerID,
+	}
+
+	updatedTask, err := gtm.UpdateTask(ctx, originalTask.ID, req)
+	assert.NoError(t, err)
+	assert.NotNil(t, updatedTask)
+
+	// Verify updated properties
+	assert.Equal(t, newTitle, updatedTask.Title)
+	assert.Equal(t, newDescription, updatedTask.Description)
+	assert.Equal(t, newStatus, updatedTask.Status)
+	assert.Equal(t, newPriority, updatedTask.Priority)
+	assert.Equal(t, newOwnerID, updatedTask.Owner.ID)
+	assert.Equal(t, newOwnerID, updatedTask.Owner.Name)
+
+	// Verify timestamps were updated
+	assert.True(t, updatedTask.UpdatedAt.After(originalTask.UpdatedAt))
+
+	// Verify git commit was created
+	commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 2})
+	assert.NoError(t, err)
+	if len(commits) > 0 {
+		assert.Contains(t, commits[0].Message, "updated")
+	}
+}
+
+func TestUpdateTaskNotFound(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	ctx := context.Background()
+	newTitle := "Updated Title"
+	req := &tm.TaskUpdateRequest{
+		Title: &newTitle,
+	}
+
+	task, err := gtm.UpdateTask(ctx, "non-existent-task", req)
+	assert.Error(t, err)
+	assert.Nil(t, task)
+	assert.Equal(t, tm.ErrTaskNotFound, err)
+}
+
+func TestUpdateTaskNoChanges(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	originalTask := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now().Add(-time.Hour),
+		UpdatedAt: time.Now().Add(-time.Hour),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(originalTask)
+	require.NoError(t, err)
+
+	// Update with no changes
+	ctx := context.Background()
+	req := &tm.TaskUpdateRequest{}
+
+	updatedTask, err := gtm.UpdateTask(ctx, originalTask.ID, req)
+	assert.NoError(t, err)
+	assert.NotNil(t, updatedTask)
+
+	// Verify no changes were made
+	assert.Equal(t, originalTask.Title, updatedTask.Title)
+	assert.Equal(t, originalTask.Description, updatedTask.Description)
+	assert.Equal(t, originalTask.Status, updatedTask.Status)
+	assert.Equal(t, originalTask.Priority, updatedTask.Priority)
+	assert.Equal(t, originalTask.Owner.ID, updatedTask.Owner.ID)
+}
+
+func TestUpdateTaskStatusTimestamps(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now().Add(-time.Hour),
+		UpdatedAt: time.Now().Add(-time.Hour),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(task)
+	require.NoError(t, err)
+
+	ctx := context.Background()
+
+	// Test completing a task
+	completedStatus := tm.StatusCompleted
+	req := &tm.TaskUpdateRequest{
+		Status: &completedStatus,
+	}
+
+	updatedTask, err := gtm.UpdateTask(ctx, task.ID, req)
+	assert.NoError(t, err)
+	assert.NotNil(t, updatedTask)
+	assert.Equal(t, tm.StatusCompleted, updatedTask.Status)
+	assert.NotNil(t, updatedTask.CompletedAt)
+
+	// Test archiving a task
+	archivedStatus := tm.StatusArchived
+	req = &tm.TaskUpdateRequest{
+		Status: &archivedStatus,
+	}
+
+	updatedTask, err = gtm.UpdateTask(ctx, task.ID, req)
+	assert.NoError(t, err)
+	assert.NotNil(t, updatedTask)
+	assert.Equal(t, tm.StatusArchived, updatedTask.Status)
+	assert.NotNil(t, updatedTask.ArchivedAt)
+}
+
+func TestArchiveTask(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now().Add(-time.Hour),
+		UpdatedAt: time.Now().Add(-time.Hour),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(task)
+	require.NoError(t, err)
+
+	// Archive the task
+	ctx := context.Background()
+	err = gtm.ArchiveTask(ctx, task.ID)
+	assert.NoError(t, err)
+
+	// Verify task was archived
+	archivedTask, err := gtm.GetTask(ctx, task.ID)
+	assert.NoError(t, err)
+	assert.Equal(t, tm.StatusArchived, archivedTask.Status)
+	assert.NotNil(t, archivedTask.ArchivedAt)
+}
+
+func TestStartTask(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusToDo,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now().Add(-time.Hour),
+		UpdatedAt: time.Now().Add(-time.Hour),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(task)
+	require.NoError(t, err)
+
+	// Start the task
+	ctx := context.Background()
+	startedTask, err := gtm.StartTask(ctx, task.ID)
+	assert.NoError(t, err)
+	assert.NotNil(t, startedTask)
+	assert.Equal(t, tm.StatusInProgress, startedTask.Status)
+}
+
+func TestCompleteTask(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create a test task
+	task := &tm.Task{
+		ID:          "test-task-123",
+		Title:       "Test Task",
+		Description: "Test description",
+		Owner: tm.Owner{
+			ID:   "user123",
+			Name: "Test User",
+		},
+		Status:    tm.StatusInProgress,
+		Priority:  tm.PriorityMedium,
+		CreatedAt: time.Now().Add(-time.Hour),
+		UpdatedAt: time.Now().Add(-time.Hour),
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+	err = gtm.writeTaskFile(task)
+	require.NoError(t, err)
+
+	// Complete the task
+	ctx := context.Background()
+	completedTask, err := gtm.CompleteTask(ctx, task.ID)
+	assert.NoError(t, err)
+	assert.NotNil(t, completedTask)
+	assert.Equal(t, tm.StatusCompleted, completedTask.Status)
+	assert.NotNil(t, completedTask.CompletedAt)
+}
+
+func TestListTasks(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create test tasks
+	tasks := []*tm.Task{
+		{
+			ID:          "task-1",
+			Title:       "Task 1",
+			Description: "First task",
+			Owner:       tm.Owner{ID: "user1", Name: "User 1"},
+			Status:      tm.StatusToDo,
+			Priority:    tm.PriorityHigh,
+			CreatedAt:   time.Now().Add(-2 * time.Hour),
+			UpdatedAt:   time.Now().Add(-2 * time.Hour),
+		},
+		{
+			ID:          "task-2",
+			Title:       "Task 2",
+			Description: "Second task",
+			Owner:       tm.Owner{ID: "user2", Name: "User 2"},
+			Status:      tm.StatusInProgress,
+			Priority:    tm.PriorityMedium,
+			CreatedAt:   time.Now().Add(-1 * time.Hour),
+			UpdatedAt:   time.Now().Add(-1 * time.Hour),
+		},
+		{
+			ID:          "task-3",
+			Title:       "Task 3",
+			Description: "Third task",
+			Owner:       tm.Owner{ID: "user1", Name: "User 1"},
+			Status:      tm.StatusCompleted,
+			Priority:    tm.PriorityLow,
+			CreatedAt:   time.Now(),
+			UpdatedAt:   time.Now(),
+		},
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+
+	for _, task := range tasks {
+		err = gtm.writeTaskFile(task)
+		require.NoError(t, err)
+	}
+
+	ctx := context.Background()
+
+	// Test listing all tasks
+	taskList, err := gtm.ListTasks(ctx, nil, 0, 10)
+	assert.NoError(t, err)
+	assert.NotNil(t, taskList)
+	assert.Len(t, taskList.Tasks, 3)
+	assert.Equal(t, 3, taskList.TotalCount)
+	assert.Equal(t, 0, taskList.Page)
+	assert.Equal(t, 10, taskList.PageSize)
+	assert.False(t, taskList.HasMore)
+
+	// Test pagination
+	taskList, err = gtm.ListTasks(ctx, nil, 0, 2)
+	assert.NoError(t, err)
+	assert.Len(t, taskList.Tasks, 2)
+	assert.Equal(t, 3, taskList.TotalCount)
+	assert.True(t, taskList.HasMore)
+
+	// Test filtering by owner
+	ownerFilter := &tm.TaskFilter{OwnerID: stringPtr("user1")}
+	taskList, err = gtm.ListTasks(ctx, ownerFilter, 0, 10)
+	assert.NoError(t, err)
+	assert.Len(t, taskList.Tasks, 2)
+
+	// Test filtering by status
+	statusFilter := &tm.TaskFilter{Status: taskStatusPtr(tm.StatusToDo)}
+	taskList, err = gtm.ListTasks(ctx, statusFilter, 0, 10)
+	assert.NoError(t, err)
+	assert.Len(t, taskList.Tasks, 1)
+	assert.Equal(t, "task-1", taskList.Tasks[0].ID)
+
+	// Test filtering by priority
+	priorityFilter := &tm.TaskFilter{Priority: taskPriorityPtr(tm.PriorityHigh)}
+	taskList, err = gtm.ListTasks(ctx, priorityFilter, 0, 10)
+	assert.NoError(t, err)
+	assert.Len(t, taskList.Tasks, 1)
+	assert.Equal(t, "task-1", taskList.Tasks[0].ID)
+}
+
+func TestGetTasksByOwner(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create test tasks
+	tasks := []*tm.Task{
+		{
+			ID:        "task-1",
+			Title:     "Task 1",
+			Owner:     tm.Owner{ID: "user1", Name: "User 1"},
+			Status:    tm.StatusToDo,
+			Priority:  tm.PriorityHigh,
+			CreatedAt: time.Now().Add(-2 * time.Hour),
+			UpdatedAt: time.Now().Add(-2 * time.Hour),
+		},
+		{
+			ID:        "task-2",
+			Title:     "Task 2",
+			Owner:     tm.Owner{ID: "user2", Name: "User 2"},
+			Status:    tm.StatusInProgress,
+			Priority:  tm.PriorityMedium,
+			CreatedAt: time.Now().Add(-1 * time.Hour),
+			UpdatedAt: time.Now().Add(-1 * time.Hour),
+		},
+		{
+			ID:        "task-3",
+			Title:     "Task 3",
+			Owner:     tm.Owner{ID: "user1", Name: "User 1"},
+			Status:    tm.StatusCompleted,
+			Priority:  tm.PriorityLow,
+			CreatedAt: time.Now(),
+			UpdatedAt: time.Now(),
+		},
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+
+	for _, task := range tasks {
+		err = gtm.writeTaskFile(task)
+		require.NoError(t, err)
+	}
+
+	ctx := context.Background()
+
+	// Get tasks by owner
+	taskList, err := gtm.GetTasksByOwner(ctx, "user1", 0, 10)
+	assert.NoError(t, err)
+	assert.NotNil(t, taskList)
+	assert.Len(t, taskList.Tasks, 2)
+
+	for _, task := range taskList.Tasks {
+		assert.Equal(t, "user1", task.Owner.ID)
+	}
+}
+
+func TestGetTasksByStatus(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create test tasks
+	tasks := []*tm.Task{
+		{
+			ID:        "task-1",
+			Title:     "Task 1",
+			Owner:     tm.Owner{ID: "user1", Name: "User 1"},
+			Status:    tm.StatusToDo,
+			Priority:  tm.PriorityHigh,
+			CreatedAt: time.Now().Add(-2 * time.Hour),
+			UpdatedAt: time.Now().Add(-2 * time.Hour),
+		},
+		{
+			ID:        "task-2",
+			Title:     "Task 2",
+			Owner:     tm.Owner{ID: "user2", Name: "User 2"},
+			Status:    tm.StatusInProgress,
+			Priority:  tm.PriorityMedium,
+			CreatedAt: time.Now().Add(-1 * time.Hour),
+			UpdatedAt: time.Now().Add(-1 * time.Hour),
+		},
+		{
+			ID:        "task-3",
+			Title:     "Task 3",
+			Owner:     tm.Owner{ID: "user1", Name: "User 1"},
+			Status:    tm.StatusCompleted,
+			Priority:  tm.PriorityLow,
+			CreatedAt: time.Now(),
+			UpdatedAt: time.Now(),
+		},
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+
+	for _, task := range tasks {
+		err = gtm.writeTaskFile(task)
+		require.NoError(t, err)
+	}
+
+	ctx := context.Background()
+
+	// Get tasks by status
+	taskList, err := gtm.GetTasksByStatus(ctx, tm.StatusToDo, 0, 10)
+	assert.NoError(t, err)
+	assert.NotNil(t, taskList)
+	assert.Len(t, taskList.Tasks, 1)
+	assert.Equal(t, tm.StatusToDo, taskList.Tasks[0].Status)
+}
+
+func TestGetTasksByPriority(t *testing.T) {
+	tempDir, cleanup := setupTestDir(t)
+	defer cleanup()
+
+	gtm, _ := createTestTaskManager(t, tempDir)
+
+	// Create test tasks
+	tasks := []*tm.Task{
+		{
+			ID:        "task-1",
+			Title:     "Task 1",
+			Owner:     tm.Owner{ID: "user1", Name: "User 1"},
+			Status:    tm.StatusToDo,
+			Priority:  tm.PriorityHigh,
+			CreatedAt: time.Now().Add(-2 * time.Hour),
+			UpdatedAt: time.Now().Add(-2 * time.Hour),
+		},
+		{
+			ID:        "task-2",
+			Title:     "Task 2",
+			Owner:     tm.Owner{ID: "user2", Name: "User 2"},
+			Status:    tm.StatusInProgress,
+			Priority:  tm.PriorityMedium,
+			CreatedAt: time.Now().Add(-1 * time.Hour),
+			UpdatedAt: time.Now().Add(-1 * time.Hour),
+		},
+		{
+			ID:        "task-3",
+			Title:     "Task 3",
+			Owner:     tm.Owner{ID: "user1", Name: "User 1"},
+			Status:    tm.StatusCompleted,
+			Priority:  tm.PriorityLow,
+			CreatedAt: time.Now(),
+			UpdatedAt: time.Now(),
+		},
+	}
+
+	err := gtm.ensureTasksDir()
+	require.NoError(t, err)
+
+	for _, task := range tasks {
+		err = gtm.writeTaskFile(task)
+		require.NoError(t, err)
+	}
+
+	ctx := context.Background()
+
+	// Get tasks by priority
+	taskList, err := gtm.GetTasksByPriority(ctx, tm.PriorityHigh, 0, 10)
+	assert.NoError(t, err)
+	assert.NotNil(t, taskList)
+	assert.Len(t, taskList.Tasks, 1)
+	assert.Equal(t, tm.PriorityHigh, taskList.Tasks[0].Priority)
+}
+
+// Helper functions for creating pointers to string, TaskStatus, and TaskPriority
+func stringPtr(s string) *string {
+	return &s
+}
+
+func taskStatusPtr(status tm.TaskStatus) *tm.TaskStatus {
+	return &status
+}
+
+func taskPriorityPtr(priority tm.TaskPriority) *tm.TaskPriority {
+	return &priority
+}