blob: 515fa51803faaed64266754a8438aa34d37bb3c0 [file] [log] [blame]
iomodo3b89bdf2025-07-25 15:19:22 +04001package git_tm
2
3import (
4 "context"
5 "fmt"
iomodo0c203b12025-07-26 19:44:57 +04006 "log/slog"
iomodo3b89bdf2025-07-25 15:19:22 +04007 "os"
8 "path/filepath"
9 "sort"
10 "strings"
11 "time"
12
13 "github.com/google/uuid"
14 "github.com/iomodo/staff/git"
15 "github.com/iomodo/staff/tm"
16 "gopkg.in/yaml.v3"
17)
18
iomodo6cf9dda2025-07-27 15:18:07 +040019const (
20 // File system constants
21 DefaultFileMode = 0755
22 TaskFileMode = 0644
23 TaskFileExt = ".md"
iomodo50598c62025-07-27 22:06:32 +040024
iomodo6cf9dda2025-07-27 15:18:07 +040025 // Frontmatter constants
26 FrontmatterSeparator = "---\n"
iomodo50598c62025-07-27 22:06:32 +040027
iomodo6cf9dda2025-07-27 15:18:07 +040028 // Task ID format
29 TaskIDPrefix = "task-"
30)
31
32// UserService defines interface for user-related operations
33type UserService interface {
34 GetUserName(userID string) (string, error)
35}
36
37// DefaultUserService provides a simple implementation that uses userID as name
38type DefaultUserService struct{}
39
40func (dus *DefaultUserService) GetUserName(userID string) (string, error) {
41 // For now, just return the userID as the name
42 // This can be enhanced to lookup from a proper user service
43 return userID, nil
44}
45
iomodo3b89bdf2025-07-25 15:19:22 +040046// GitTaskManager implements TaskManager interface using git as the source of truth
47type GitTaskManager struct {
iomodo6cf9dda2025-07-27 15:18:07 +040048 git git.GitInterface
49 repoPath string
50 tasksDir string
51 logger *slog.Logger
52 userService UserService
iomodo3b89bdf2025-07-25 15:19:22 +040053}
54
55// NewGitTaskManager creates a new GitTaskManager instance
iomodo0c203b12025-07-26 19:44:57 +040056func NewGitTaskManager(git git.GitInterface, repoPath string, logger *slog.Logger) *GitTaskManager {
iomodo3b89bdf2025-07-25 15:19:22 +040057 return &GitTaskManager{
iomodo6cf9dda2025-07-27 15:18:07 +040058 git: git,
59 repoPath: repoPath,
60 tasksDir: filepath.Join(repoPath, "tasks"),
61 logger: logger,
62 userService: &DefaultUserService{},
iomodo0c203b12025-07-26 19:44:57 +040063 }
64}
65
66// NewGitTaskManagerWithLogger creates a new GitTaskManager instance with a custom logger
67func NewGitTaskManagerWithLogger(git git.GitInterface, repoPath string, logger *slog.Logger) *GitTaskManager {
68 if logger == nil {
69 logger = slog.Default()
70 }
71 return &GitTaskManager{
iomodo6cf9dda2025-07-27 15:18:07 +040072 git: git,
73 repoPath: repoPath,
74 tasksDir: filepath.Join(repoPath, "tasks"),
75 logger: logger,
76 userService: &DefaultUserService{},
77 }
78}
79
80// NewGitTaskManagerWithUserService creates a new GitTaskManager with custom user service
81func NewGitTaskManagerWithUserService(git git.GitInterface, repoPath string, logger *slog.Logger, userService UserService) *GitTaskManager {
82 if logger == nil {
83 logger = slog.Default()
84 }
85 if userService == nil {
86 userService = &DefaultUserService{}
87 }
88 return &GitTaskManager{
89 git: git,
90 repoPath: repoPath,
91 tasksDir: filepath.Join(repoPath, "tasks"),
92 logger: logger,
93 userService: userService,
iomodo3b89bdf2025-07-25 15:19:22 +040094 }
95}
96
97// ensureTasksDir ensures the tasks directory exists
98func (gtm *GitTaskManager) ensureTasksDir() error {
iomodo6cf9dda2025-07-27 15:18:07 +040099 if err := os.MkdirAll(gtm.tasksDir, DefaultFileMode); err != nil {
iomodo3b89bdf2025-07-25 15:19:22 +0400100 return fmt.Errorf("failed to create tasks directory: %w", err)
101 }
102 return nil
103}
104
105// generateTaskID generates a unique task ID
106func (gtm *GitTaskManager) generateTaskID() string {
107 timestamp := time.Now().Unix()
108 random := uuid.New().String()[:8]
iomodo6cf9dda2025-07-27 15:18:07 +0400109 return fmt.Sprintf("%s%d-%s", TaskIDPrefix, timestamp, random)
iomodo3b89bdf2025-07-25 15:19:22 +0400110}
111
112// taskToMarkdown converts a Task to markdown format
113func (gtm *GitTaskManager) taskToMarkdown(task *tm.Task) (string, error) {
114 // Create frontmatter data
115 frontmatter := map[string]interface{}{
116 "id": task.ID,
117 "title": task.Title,
118 "description": task.Description,
119 "owner_id": task.Owner.ID,
120 "owner_name": task.Owner.Name,
user5a7d60d2025-07-27 21:22:04 +0400121 "assignee": task.Assignee,
iomodo3b89bdf2025-07-25 15:19:22 +0400122 "status": task.Status,
123 "priority": task.Priority,
124 "created_at": task.CreatedAt.Format(time.RFC3339),
125 "updated_at": task.UpdatedAt.Format(time.RFC3339),
126 }
127
128 if task.DueDate != nil {
129 frontmatter["due_date"] = task.DueDate.Format(time.RFC3339)
130 }
131 if task.CompletedAt != nil {
132 frontmatter["completed_at"] = task.CompletedAt.Format(time.RFC3339)
133 }
134 if task.ArchivedAt != nil {
135 frontmatter["archived_at"] = task.ArchivedAt.Format(time.RFC3339)
136 }
137
138 // Marshal frontmatter to YAML
139 yamlData, err := yaml.Marshal(frontmatter)
140 if err != nil {
141 return "", fmt.Errorf("failed to marshal frontmatter: %w", err)
142 }
143
144 // Build markdown content
145 var content strings.Builder
iomodo6cf9dda2025-07-27 15:18:07 +0400146 content.WriteString(FrontmatterSeparator)
iomodo3b89bdf2025-07-25 15:19:22 +0400147 content.Write(yamlData)
iomodo6cf9dda2025-07-27 15:18:07 +0400148 content.WriteString(FrontmatterSeparator)
149 content.WriteString("\n")
iomodo3b89bdf2025-07-25 15:19:22 +0400150
151 if task.Description != "" {
152 content.WriteString("# Task Description\n\n")
153 content.WriteString(task.Description)
154 content.WriteString("\n\n")
155 }
156
157 return content.String(), nil
158}
159
160// parseTaskFromMarkdown parses a Task from markdown format
161func (gtm *GitTaskManager) parseTaskFromMarkdown(content string) (*tm.Task, error) {
162 // Split content into frontmatter and body
iomodo6cf9dda2025-07-27 15:18:07 +0400163 parts := strings.SplitN(content, FrontmatterSeparator, 3)
iomodo3b89bdf2025-07-25 15:19:22 +0400164 if len(parts) < 3 {
165 return nil, fmt.Errorf("invalid markdown format: missing frontmatter")
166 }
167
168 // Parse YAML frontmatter
169 var frontmatter map[string]interface{}
170 if err := yaml.Unmarshal([]byte(parts[1]), &frontmatter); err != nil {
171 return nil, fmt.Errorf("failed to parse frontmatter: %w", err)
172 }
173
174 // Extract task data
175 task := &tm.Task{}
176
177 if id, ok := frontmatter["id"].(string); ok {
178 task.ID = id
179 }
180 if title, ok := frontmatter["title"].(string); ok {
181 task.Title = title
182 }
183 if description, ok := frontmatter["description"].(string); ok {
184 task.Description = description
185 }
186 if ownerID, ok := frontmatter["owner_id"].(string); ok {
187 task.Owner.ID = ownerID
188 }
189 if ownerName, ok := frontmatter["owner_name"].(string); ok {
190 task.Owner.Name = ownerName
191 }
user5a7d60d2025-07-27 21:22:04 +0400192 if assignee, ok := frontmatter["assignee"].(string); ok {
193 task.Assignee = assignee
194 }
iomodo3b89bdf2025-07-25 15:19:22 +0400195 if status, ok := frontmatter["status"].(string); ok {
196 task.Status = tm.TaskStatus(status)
197 }
198 if priority, ok := frontmatter["priority"].(string); ok {
199 task.Priority = tm.TaskPriority(priority)
200 }
201
202 // Parse timestamps
203 if createdAt, ok := frontmatter["created_at"].(string); ok {
204 if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
205 task.CreatedAt = t
206 }
207 }
208 if updatedAt, ok := frontmatter["updated_at"].(string); ok {
209 if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
210 task.UpdatedAt = t
211 }
212 }
213 if dueDate, ok := frontmatter["due_date"].(string); ok {
214 if t, err := time.Parse(time.RFC3339, dueDate); err == nil {
215 task.DueDate = &t
216 }
217 }
218 if completedAt, ok := frontmatter["completed_at"].(string); ok {
219 if t, err := time.Parse(time.RFC3339, completedAt); err == nil {
220 task.CompletedAt = &t
221 }
222 }
223 if archivedAt, ok := frontmatter["archived_at"].(string); ok {
224 if t, err := time.Parse(time.RFC3339, archivedAt); err == nil {
225 task.ArchivedAt = &t
226 }
227 }
228
229 return task, nil
230}
231
232// readTaskFile reads a task from a file
iomodo50598c62025-07-27 22:06:32 +0400233func (gtm *GitTaskManager) readTaskFile(taskFile string) (*tm.Task, error) {
234 filePath := filepath.Join(gtm.tasksDir, taskFile+TaskFileExt)
iomodo3b89bdf2025-07-25 15:19:22 +0400235
236 content, err := os.ReadFile(filePath)
237 if err != nil {
238 if os.IsNotExist(err) {
239 return nil, tm.ErrTaskNotFound
240 }
241 return nil, fmt.Errorf("failed to read task file: %w", err)
242 }
243
244 return gtm.parseTaskFromMarkdown(string(content))
245}
246
247// writeTaskFile writes a task to a file
248func (gtm *GitTaskManager) writeTaskFile(task *tm.Task) error {
249 content, err := gtm.taskToMarkdown(task)
250 if err != nil {
251 return fmt.Errorf("failed to convert task to markdown: %w", err)
252 }
253
iomodo6cf9dda2025-07-27 15:18:07 +0400254 filePath := filepath.Join(gtm.tasksDir, task.ID+TaskFileExt)
255 if err := os.WriteFile(filePath, []byte(content), TaskFileMode); err != nil {
iomodo3b89bdf2025-07-25 15:19:22 +0400256 return fmt.Errorf("failed to write task file: %w", err)
257 }
258
259 return nil
260}
261
262// listTaskFiles returns all task file paths
263func (gtm *GitTaskManager) listTaskFiles() ([]string, error) {
264 entries, err := os.ReadDir(gtm.tasksDir)
265 if err != nil {
266 if os.IsNotExist(err) {
267 return []string{}, nil
268 }
269 return nil, fmt.Errorf("failed to read tasks directory: %w", err)
270 }
271
272 var taskFiles []string
273 for _, entry := range entries {
iomodo6cf9dda2025-07-27 15:18:07 +0400274 if !entry.IsDir() && strings.HasSuffix(entry.Name(), TaskFileExt) {
275 taskID := strings.TrimSuffix(entry.Name(), TaskFileExt)
iomodo3b89bdf2025-07-25 15:19:22 +0400276 taskFiles = append(taskFiles, taskID)
277 }
278 }
279
280 return taskFiles, nil
281}
282
283// commitTaskChange commits a task change to git
iomodo97555d02025-07-27 15:07:14 +0400284func (gtm *GitTaskManager) commitTaskChange(taskID, operation string, owner tm.Owner) error {
iomodo3b89bdf2025-07-25 15:19:22 +0400285 ctx := context.Background()
286
287 // Add the task file
iomodo6cf9dda2025-07-27 15:18:07 +0400288 if err := gtm.git.Add(ctx, []string{filepath.Join(gtm.tasksDir, taskID+TaskFileExt)}); err != nil {
iomodo3b89bdf2025-07-25 15:19:22 +0400289 return fmt.Errorf("failed to add task file: %w", err)
290 }
291
292 // Commit the change
293 message := fmt.Sprintf("task: %s - %s", taskID, operation)
iomodo97555d02025-07-27 15:07:14 +0400294 if err := gtm.git.Commit(ctx, message, git.CommitOptions{
295 Author: &git.Author{
296 Name: owner.Name,
297 },
298 }); err != nil {
iomodo3b89bdf2025-07-25 15:19:22 +0400299 return fmt.Errorf("failed to commit task change: %w", err)
300 }
301
302 return nil
303}
304
305// CreateTask creates a new task
306func (gtm *GitTaskManager) CreateTask(ctx context.Context, req *tm.TaskCreateRequest) (*tm.Task, error) {
307 if err := gtm.ensureTasksDir(); err != nil {
308 return nil, err
309 }
310
311 // Validate request
312 if req.Title == "" {
313 return nil, tm.ErrInvalidTaskData
314 }
315 if req.OwnerID == "" {
316 return nil, tm.ErrInvalidOwner
317 }
318
319 // Generate task ID
320 taskID := gtm.generateTaskID()
321 now := time.Now()
322
iomodo6cf9dda2025-07-27 15:18:07 +0400323 // Get owner name from user service
324 ownerName, err := gtm.userService.GetUserName(req.OwnerID)
325 if err != nil {
326 gtm.logger.Warn("Failed to get owner name, using ID", slog.String("ownerID", req.OwnerID), slog.String("error", err.Error()))
327 ownerName = req.OwnerID
328 }
329
iomodo3b89bdf2025-07-25 15:19:22 +0400330 // Create task
331 task := &tm.Task{
332 ID: taskID,
333 Title: req.Title,
334 Description: req.Description,
335 Owner: tm.Owner{
336 ID: req.OwnerID,
iomodo6cf9dda2025-07-27 15:18:07 +0400337 Name: ownerName,
iomodo3b89bdf2025-07-25 15:19:22 +0400338 },
339 Status: tm.StatusToDo,
340 Priority: req.Priority,
341 CreatedAt: now,
342 UpdatedAt: now,
343 DueDate: req.DueDate,
344 }
345
346 // Write task file
347 if err := gtm.writeTaskFile(task); err != nil {
348 return nil, err
349 }
350
351 // Commit to git
iomodo97555d02025-07-27 15:07:14 +0400352 if err := gtm.commitTaskChange(taskID, "created", task.Owner); err != nil {
iomodo3b89bdf2025-07-25 15:19:22 +0400353 return nil, err
354 }
355
356 return task, nil
357}
358
359// GetTask retrieves a task by ID
user5a7d60d2025-07-27 21:22:04 +0400360func (gtm *GitTaskManager) GetTask(id string) (*tm.Task, error) {
iomodo3b89bdf2025-07-25 15:19:22 +0400361 return gtm.readTaskFile(id)
362}
363
364// UpdateTask updates an existing task
user5a7d60d2025-07-27 21:22:04 +0400365func (gtm *GitTaskManager) UpdateTask(task *tm.Task) error {
366 // Set update time
367 task.UpdatedAt = time.Now()
iomodo50598c62025-07-27 22:06:32 +0400368
user5a7d60d2025-07-27 21:22:04 +0400369 // Write task to file
370 return gtm.writeTaskFile(task)
371}
372
373// readAllTasks reads all task files from disk
374func (gtm *GitTaskManager) readAllTasks() ([]*tm.Task, error) {
375 taskFiles, err := gtm.listTaskFiles()
iomodo3b89bdf2025-07-25 15:19:22 +0400376 if err != nil {
377 return nil, err
378 }
iomodo50598c62025-07-27 22:06:32 +0400379
user5a7d60d2025-07-27 21:22:04 +0400380 var tasks []*tm.Task
381 for _, taskFile := range taskFiles {
user5a7d60d2025-07-27 21:22:04 +0400382 filename := filepath.Base(taskFile)
iomodo50598c62025-07-27 22:06:32 +0400383 if strings.HasPrefix(filename, "task-") {
384 task, err := gtm.readTaskFile(taskFile)
user5a7d60d2025-07-27 21:22:04 +0400385 if err != nil {
386 gtm.logger.Warn("Failed to read task file", slog.String("file", taskFile), slog.String("error", err.Error()))
387 continue
iomodo3b89bdf2025-07-25 15:19:22 +0400388 }
user5a7d60d2025-07-27 21:22:04 +0400389 tasks = append(tasks, task)
iomodo3b89bdf2025-07-25 15:19:22 +0400390 }
391 }
iomodo50598c62025-07-27 22:06:32 +0400392
user5a7d60d2025-07-27 21:22:04 +0400393 return tasks, nil
394}
iomodo3b89bdf2025-07-25 15:19:22 +0400395
user5a7d60d2025-07-27 21:22:04 +0400396// GetTasksByAssignee retrieves tasks assigned to a specific agent (MVP method)
397func (gtm *GitTaskManager) GetTasksByAssignee(assignee string) ([]*tm.Task, error) {
398 // Read all tasks and filter by assignee
399 tasks, err := gtm.readAllTasks()
400 if err != nil {
iomodo3b89bdf2025-07-25 15:19:22 +0400401 return nil, err
402 }
iomodo50598c62025-07-27 22:06:32 +0400403
user5a7d60d2025-07-27 21:22:04 +0400404 var assignedTasks []*tm.Task
405 for _, task := range tasks {
406 if task.Assignee == assignee {
407 assignedTasks = append(assignedTasks, task)
408 }
iomodo3b89bdf2025-07-25 15:19:22 +0400409 }
iomodo50598c62025-07-27 22:06:32 +0400410
user5a7d60d2025-07-27 21:22:04 +0400411 return assignedTasks, nil
iomodo3b89bdf2025-07-25 15:19:22 +0400412}
413
414// ArchiveTask archives a task
415func (gtm *GitTaskManager) ArchiveTask(ctx context.Context, id string) error {
user5a7d60d2025-07-27 21:22:04 +0400416 task, err := gtm.GetTask(id)
417 if err != nil {
418 return err
iomodo3b89bdf2025-07-25 15:19:22 +0400419 }
iomodo50598c62025-07-27 22:06:32 +0400420
user5a7d60d2025-07-27 21:22:04 +0400421 task.Status = tm.StatusArchived
422 now := time.Now()
423 task.ArchivedAt = &now
iomodo50598c62025-07-27 22:06:32 +0400424
user5a7d60d2025-07-27 21:22:04 +0400425 return gtm.UpdateTask(task)
iomodo3b89bdf2025-07-25 15:19:22 +0400426}
427
428// ListTasks lists tasks with filtering and pagination
429func (gtm *GitTaskManager) ListTasks(ctx context.Context, filter *tm.TaskFilter, page, pageSize int) (*tm.TaskList, error) {
430 // Get all task files
431 taskFiles, err := gtm.listTaskFiles()
432 if err != nil {
433 return nil, err
434 }
435
436 // Read all tasks
437 var tasks []*tm.Task
438 for _, taskID := range taskFiles {
439 task, err := gtm.readTaskFile(taskID)
440 if err != nil {
441 continue // Skip corrupted files
442 }
443 tasks = append(tasks, task)
444 }
445
446 // Apply filters
447 if filter != nil {
448 tasks = gtm.filterTasks(tasks, filter)
449 }
450
451 // Sort by creation date (newest first)
452 sort.Slice(tasks, func(i, j int) bool {
453 return tasks[i].CreatedAt.After(tasks[j].CreatedAt)
454 })
455
456 // Apply pagination
457 totalCount := len(tasks)
458 start := page * pageSize
459 end := start + pageSize
460
461 if start >= totalCount {
462 return &tm.TaskList{
463 Tasks: []*tm.Task{},
464 TotalCount: totalCount,
465 Page: page,
466 PageSize: pageSize,
467 HasMore: false,
468 }, nil
469 }
470
471 if end > totalCount {
472 end = totalCount
473 }
474
475 return &tm.TaskList{
476 Tasks: tasks[start:end],
477 TotalCount: totalCount,
478 Page: page,
479 PageSize: pageSize,
480 HasMore: end < totalCount,
481 }, nil
482}
483
484// filterTasks applies filters to a list of tasks
485func (gtm *GitTaskManager) filterTasks(tasks []*tm.Task, filter *tm.TaskFilter) []*tm.Task {
486 var filtered []*tm.Task
487
488 for _, task := range tasks {
489 if gtm.taskMatchesFilter(task, filter) {
490 filtered = append(filtered, task)
491 }
492 }
493
494 return filtered
495}
496
497// taskMatchesFilter checks if a task matches the given filter
498func (gtm *GitTaskManager) taskMatchesFilter(task *tm.Task, filter *tm.TaskFilter) bool {
499 if filter.OwnerID != nil && task.Owner.ID != *filter.OwnerID {
500 return false
501 }
502 if filter.Status != nil && task.Status != *filter.Status {
503 return false
504 }
505 if filter.Priority != nil && task.Priority != *filter.Priority {
506 return false
507 }
508 if filter.DueBefore != nil && (task.DueDate == nil || !task.DueDate.Before(*filter.DueBefore)) {
509 return false
510 }
511 if filter.DueAfter != nil && (task.DueDate == nil || !task.DueDate.After(*filter.DueAfter)) {
512 return false
513 }
514 if filter.CreatedAfter != nil && !task.CreatedAt.After(*filter.CreatedAfter) {
515 return false
516 }
517 if filter.CreatedBefore != nil && !task.CreatedAt.Before(*filter.CreatedBefore) {
518 return false
519 }
520
521 return true
522}
523
524// StartTask starts a task (changes status to in_progress)
525func (gtm *GitTaskManager) StartTask(ctx context.Context, id string) (*tm.Task, error) {
user5a7d60d2025-07-27 21:22:04 +0400526 task, err := gtm.GetTask(id)
527 if err != nil {
528 return nil, err
iomodo3b89bdf2025-07-25 15:19:22 +0400529 }
iomodo50598c62025-07-27 22:06:32 +0400530
user5a7d60d2025-07-27 21:22:04 +0400531 task.Status = tm.StatusInProgress
iomodo50598c62025-07-27 22:06:32 +0400532
user5a7d60d2025-07-27 21:22:04 +0400533 err = gtm.UpdateTask(task)
534 if err != nil {
535 return nil, err
536 }
iomodo50598c62025-07-27 22:06:32 +0400537
user5a7d60d2025-07-27 21:22:04 +0400538 return task, nil
iomodo3b89bdf2025-07-25 15:19:22 +0400539}
540
541// CompleteTask completes a task (changes status to completed)
542func (gtm *GitTaskManager) CompleteTask(ctx context.Context, id string) (*tm.Task, error) {
user5a7d60d2025-07-27 21:22:04 +0400543 task, err := gtm.GetTask(id)
544 if err != nil {
545 return nil, err
iomodo3b89bdf2025-07-25 15:19:22 +0400546 }
iomodo50598c62025-07-27 22:06:32 +0400547
user5a7d60d2025-07-27 21:22:04 +0400548 task.Status = tm.StatusCompleted
549 now := time.Now()
550 task.CompletedAt = &now
iomodo50598c62025-07-27 22:06:32 +0400551
user5a7d60d2025-07-27 21:22:04 +0400552 err = gtm.UpdateTask(task)
553 if err != nil {
554 return nil, err
555 }
iomodo50598c62025-07-27 22:06:32 +0400556
user5a7d60d2025-07-27 21:22:04 +0400557 return task, nil
iomodo3b89bdf2025-07-25 15:19:22 +0400558}
559
560// GetTasksByOwner gets tasks for a specific owner
561func (gtm *GitTaskManager) GetTasksByOwner(ctx context.Context, ownerID string, page, pageSize int) (*tm.TaskList, error) {
562 filter := &tm.TaskFilter{
563 OwnerID: &ownerID,
564 }
565 return gtm.ListTasks(ctx, filter, page, pageSize)
566}
567
568// GetTasksByStatus gets tasks with a specific status
569func (gtm *GitTaskManager) GetTasksByStatus(ctx context.Context, status tm.TaskStatus, page, pageSize int) (*tm.TaskList, error) {
570 filter := &tm.TaskFilter{
571 Status: &status,
572 }
573 return gtm.ListTasks(ctx, filter, page, pageSize)
574}
575
576// GetTasksByPriority gets tasks with a specific priority
577func (gtm *GitTaskManager) GetTasksByPriority(ctx context.Context, priority tm.TaskPriority, page, pageSize int) (*tm.TaskList, error) {
578 filter := &tm.TaskFilter{
579 Priority: &priority,
580 }
581 return gtm.ListTasks(ctx, filter, page, pageSize)
582}
583
584// Ensure GitTaskManager implements TaskManager interface
585var _ tm.TaskManager = (*GitTaskManager)(nil)