# GitHub Integration Plan ## Context Kandev needs GitHub integration to support two automation workflows: 1. **PR Monitor** - When an agent creates a PR during a session, poll GitHub for CI failures and review comments. The user manually reviews this data in the session UI and can choose to send specific feedback to the agent for fixing. 2. **PR Review Queue** - A background service that periodically polls GitHub for PRs awaiting the user's review, creates tasks in a workflow for each, and optionally auto-starts an agent to review them. **Additional requirements**: - PRs must be associated with tasks in the DB (show PR icon on task list) - Session detail UI: show PR, comments, CI checks, approval status, pending review count, time-pending-review - PR analytics/stats: PRs created, reviewed, comments, CI results **Design decisions** (confirmed with user): - Auth: `gh` CLI primary (existing `gh auth login`), PAT in secrets as fallback - PR detection: Auto-detect from git push events (no manual workflow config) - PR monitor: User manually reviews feedback, NOT auto-sent to agent - Review queue: Standalone background service with a manual "pull now" topbar button - Scope: Full stack (backend + frontend) --- ## Phase 1: GitHub Client Package **New package**: `apps/backend/internal/github/` ### 1.1 `models.go` - Data models ```go // PR represents a GitHub Pull Request type PR struct { Number int Title string URL string HTMLURL string State string // open, closed, merged HeadBranch string BaseBranch string AuthorLogin string RepoOwner string RepoName string Draft bool Mergeable bool Additions int Deletions int CreatedAt time.Time UpdatedAt time.Time MergedAt *time.Time ClosedAt *time.Time } // PRReview represents a review on a PR type PRReview struct { ID int64 Author string State string // APPROVED, CHANGES_REQUESTED, COMMENTED, PENDING, DISMISSED Body string CreatedAt time.Time } // PRComment represents a review comment on specific code type PRComment struct { ID int64 Author string Body string Path string Line int Side string // LEFT, RIGHT CreatedAt time.Time UpdatedAt time.Time InReplyTo *int64 } // CheckRun represents a CI check result type CheckRun struct { Name string Status string // queued, in_progress, completed Conclusion string // success, failure, neutral, cancelled, timed_out, action_required, skipped HTMLURL string Output string // summary text StartedAt *time.Time CompletedAt *time.Time } // PRFeedback aggregates all feedback for a PR (fetched live) type PRFeedback struct { PR *PR Reviews []PRReview Comments []PRComment Checks []CheckRun HasIssues bool // true if failing checks or changes_requested } // PRWatch tracks active PR monitoring (session → PR) type PRWatch struct { ID string SessionID string TaskID string Owner string Repo string PRNumber int Branch string LastCheckedAt *time.Time LastCommentAt *time.Time LastCheckStatus string CreatedAt time.Time UpdatedAt time.Time } // ReviewWatch configures periodic polling for PRs needing user's review type ReviewWatch struct { ID string WorkspaceID string WorkflowID string WorkflowStepID string RepoOwner string RepoName string AgentProfileID string ExecutorProfileID string Prompt string AutoStart bool Enabled bool PollIntervalSeconds int LastPolledAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // GitHubStatus represents GitHub connection status type GitHubStatus struct { Authenticated bool Username string AuthMethod string // "gh_cli", "pat", "none" } ``` ### 1.2 `client.go` - Interface ```go type Client interface { // Auth IsAuthenticated(ctx context.Context) (bool, error) GetAuthenticatedUser(ctx context.Context) (string, error) // PR queries GetPR(ctx context.Context, owner, repo string, number int) (*PR, error) FindPRByBranch(ctx context.Context, owner, repo, branch string) (*PR, error) ListAuthoredPRs(ctx context.Context, owner, repo string) ([]*PR, error) ListReviewRequestedPRs(ctx context.Context, username string) ([]*PR, error) // PR details ListPRReviews(ctx context.Context, owner, repo string, number int) ([]PRReview, error) ListPRComments(ctx context.Context, owner, repo string, number int, since *time.Time) ([]PRComment, error) ListCheckRuns(ctx context.Context, owner, repo, ref string) ([]CheckRun, error) // Aggregated GetPRFeedback(ctx context.Context, owner, repo string, number int) (*PRFeedback, error) } ``` ### 1.3 `gh_client.go` - Primary (gh CLI) Executes `gh` commands: - `gh pr list --json number,title,url,headRefName,baseRefName,author,state,createdAt,updatedAt,additions,deletions,isDraft,mergeable` - `gh pr view --json reviews,comments` - `gh api repos/{owner}/{repo}/commits/{ref}/check-runs` - `gh api search/issues?q=type:pr+review-requested:@me+state:open` Detect availability via `gh --version`. Parse JSON stdout. ### 1.4 `pat_client.go` - Fallback (PAT via net/http) Uses `GITHUB_TOKEN` secret from secrets store. `Authorization: token `. Rate limit header awareness. ### 1.5 `factory.go` - Client factory Try `gh` CLI first, fall back to PAT from secrets store. ### 1.6 `provider.go` - DI provider Standard `Provide(cfg, log, secretsSvc, db) (*Service, cleanup, error)` pattern. --- ## Phase 2: Service + Persistence ### 2.1 `service.go` - Business logic ```go type Service struct { client Client store *Store eventBus bus.EventBus logger *logger.Logger } // PR Watch operations (for PR monitoring) func (s *Service) CreatePRWatch(ctx, sessionID, taskID, owner, repo string, prNumber int, branch string) (*PRWatch, error) func (s *Service) DeletePRWatch(ctx, id string) error func (s *Service) GetPRWatchBySession(ctx, sessionID string) (*PRWatch, error) func (s *Service) ListActivePRWatches(ctx) ([]*PRWatch, error) // Task-PR association func (s *Service) AssociatePRWithTask(ctx, taskID, owner, repo string, prNumber int, prURL, prTitle, authorLogin string) (*TaskPR, error) func (s *Service) GetTaskPR(ctx, taskID string) (*TaskPR, error) func (s *Service) ListTaskPRs(ctx, taskIDs []string) (map[string]*TaskPR, error) func (s *Service) UpdateTaskPR(ctx, id string, state, mergeState string, ...) error // PR feedback (fetched live, not stored) func (s *Service) GetPRFeedback(ctx, owner, repo string, number int) (*PRFeedback, error) // Review Watch operations (for review queue) func (s *Service) CreateReviewWatch(ctx, req *CreateReviewWatchRequest) (*ReviewWatch, error) func (s *Service) UpdateReviewWatch(ctx, id string, req *UpdateReviewWatchRequest) error func (s *Service) DeleteReviewWatch(ctx, id string) error func (s *Service) ListReviewWatches(ctx, workspaceID string) ([]*ReviewWatch, error) // Polling func (s *Service) CheckPRWatch(ctx, watch *PRWatch) (*PRFeedback, bool, error) // returns hasNewFeedback func (s *Service) CheckReviewWatch(ctx, watch *ReviewWatch) ([]*PR, error) // returns new PRs // Manual fetch func (s *Service) TriggerReviewCheck(ctx, watchID string) ([]*PR, error) func (s *Service) TriggerAllReviewChecks(ctx, workspaceID string) (int, error) // returns count of new PRs found // Status func (s *Service) GetStatus(ctx) (*GitHubStatus, error) ``` ### 2.2 `store.go` - SQLite persistence Four tables (owned by github package): ```sql -- Tracks active PR monitoring (session → PR) CREATE TABLE IF NOT EXISTS github_pr_watches ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL UNIQUE, task_id TEXT NOT NULL, owner TEXT NOT NULL, repo TEXT NOT NULL, pr_number INTEGER NOT NULL, branch TEXT NOT NULL, last_checked_at DATETIME, last_comment_at DATETIME, last_check_status TEXT DEFAULT '', created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ); -- Associates a PR with a task (1:1 for now, could be 1:many later) -- Used for: PR icon on task list, session detail PR view, stats CREATE TABLE IF NOT EXISTS github_task_prs ( id TEXT PRIMARY KEY, task_id TEXT NOT NULL, owner TEXT NOT NULL, repo TEXT NOT NULL, pr_number INTEGER NOT NULL, pr_url TEXT NOT NULL, pr_title TEXT NOT NULL, head_branch TEXT NOT NULL, base_branch TEXT NOT NULL, author_login TEXT NOT NULL, state TEXT NOT NULL DEFAULT 'open', -- open, closed, merged review_state TEXT NOT NULL DEFAULT '', -- approved, changes_requested, pending, '' checks_state TEXT NOT NULL DEFAULT '', -- success, failure, pending, '' review_count INTEGER DEFAULT 0, pending_review_count INTEGER DEFAULT 0, comment_count INTEGER DEFAULT 0, additions INTEGER DEFAULT 0, deletions INTEGER DEFAULT 0, created_at DATETIME NOT NULL, -- PR creation time on GitHub merged_at DATETIME, closed_at DATETIME, last_synced_at DATETIME, updated_at DATETIME NOT NULL, UNIQUE(task_id, pr_number) ); -- Review watch configurations (workspace → repo polling) CREATE TABLE IF NOT EXISTS github_review_watches ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL, workflow_id TEXT NOT NULL, workflow_step_id TEXT NOT NULL, repo_owner TEXT NOT NULL, repo_name TEXT NOT NULL, agent_profile_id TEXT NOT NULL, executor_profile_id TEXT NOT NULL, prompt TEXT DEFAULT '', auto_start BOOLEAN DEFAULT 0, enabled BOOLEAN DEFAULT 1, poll_interval_seconds INTEGER DEFAULT 300, last_polled_at DATETIME, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ); -- Deduplication: which PRs we already created tasks for CREATE TABLE IF NOT EXISTS github_review_pr_tasks ( id TEXT PRIMARY KEY, review_watch_id TEXT NOT NULL, pr_number INTEGER NOT NULL, pr_url TEXT NOT NULL, task_id TEXT NOT NULL, created_at DATETIME NOT NULL, UNIQUE(review_watch_id, pr_number) ); ``` --- ## Phase 3: Background Poller ### 3.1 `poller.go` Two goroutine loops: **PR Monitor Loop** (every 2 min): 1. List all active `github_pr_watches` 2. For each watch, fetch PR feedback (comments + checks) via client 3. Compare with last known state (last_comment_at, last_check_status) 4. If new comments or check state changes: - Update the `github_task_prs` record (review_state, checks_state, comment_count, etc.) - Publish `github.pr_feedback` event to event bus (for UI notification, NOT auto-prompt) - Update watch timestamps 5. Also sync PR state (merged, closed) to `github_task_prs` **Review Queue Loop** (every 5 min, per-watch configurable): 1. List all enabled `github_review_watches` 2. For each watch, call `ListReviewRequestedPRs` filtered to the watch's repo 3. For each PR not already in `github_review_pr_tasks`: - Publish `github.new_pr_to_review` event - Orchestrator handler creates the task + associates the PR Methods: `Start(ctx)`, `Stop()`, `TriggerReviewCheck(ctx, watchID)`, `TriggerAllReviewChecks(ctx, workspaceID)`. --- ## Phase 4: Event Bus + Orchestrator Integration ### 4.1 New event types **File**: `apps/backend/internal/events/types.go` ```go // Event types for GitHub integration const ( GitHubPRFeedback = "github.pr_feedback" // PR has new feedback (UI notification only) GitHubPRStateChanged = "github.pr_state_changed" // PR state changed (merged, closed, etc.) GitHubNewReviewPR = "github.new_pr_to_review" // New PR found needing review GitHubWatchEvent = "github.watch.event" // Watch created/deleted GitHubTaskPRUpdated = "github.task_pr.updated" // TaskPR record updated (for UI refresh) ) ``` ### 4.2 PR auto-detection from git events **File**: `apps/backend/internal/orchestrator/event_handlers_git.go` In `handleGitStatusUpdate()`, detect push completion: - Track per-session state: when `Ahead` goes from `>0` to `0` with a `RemoteBranch` set, a push happened - After detecting push, call `githubService.Client().FindPRByBranch()` to check if a PR exists - If PR found and no watch exists for this session: 1. Auto-create `PRWatch` via `githubService.CreatePRWatch()` 2. Auto-create `TaskPR` association via `githubService.AssociatePRWithTask()` 3. Publish `github.task_pr.updated` event (so task list shows PR icon) **Lookup chain**: - `repoStore.GetTaskSession(sessionID)` → `RepositoryID` - `repoStore.GetRepository(repositoryID)` → `ProviderOwner`, `ProviderName` - `FindPRByBranch(providerOwner, providerName, branch)` ### 4.3 Orchestrator GitHub event handlers **New file**: `apps/backend/internal/orchestrator/event_handlers_github.go` ```go // handlePRFeedback forwards PR feedback events to the frontend via WS. // The user manually decides whether to send feedback to the agent. // This does NOT auto-prompt the agent. func (s *Service) handlePRFeedback(ctx context.Context, event *bus.Event) { // Extract sessionID, feedback summary from event // Forward to WS for session UI notification // Frontend shows updated PR data in session detail panel } // handleNewReviewPR creates a task for a PR needing review. func (s *Service) handleNewReviewPR(ctx context.Context, event *bus.Event) { // Extract PR data, ReviewWatch config // Create task in the watch's workflow at configured step // Associate PR with task via githubService.AssociatePRWithTask() // Record in github_review_pr_tasks for dedup // If auto_start, enqueue the task for agent execution } ``` ### 4.4 "Send to Agent" action The session detail UI will have a "Send to Agent" button that lets the user compose a message to the agent including selected PR comments and/or CI failures. This uses the existing `PromptTask()` flow — the frontend sends a user message with the selected feedback context embedded. ### 4.5 Orchestrator wiring **File**: `apps/backend/internal/orchestrator/service.go` - Add `githubService` field + `SetGitHubService()` setter - Add `pushTracker sync.Map` for per-session ahead-count tracking - Subscribe to `github.pr_feedback` and `github.new_pr_to_review` events in `Start()` **File**: `apps/backend/cmd/kandev/services.go` - Add GitHub service to `Services` struct, initialize via `github.Provide()` - Wire into orchestrator, start poller **File**: `apps/backend/cmd/kandev/main.go` - Register GitHub HTTP routes --- ## Phase 5: HTTP API ### 5.1 `controller.go` **Route group**: `/api/v1/github/` | Endpoint | Method | Description | |----------|--------|-------------| | `/status` | GET | Auth status (username, auth method) | | `/prs/authored` | GET | User's open PRs (query: owner, repo) | | `/prs/review-requested` | GET | PRs awaiting user's review | | `/prs/{owner}/{repo}/{number}` | GET | PR details with full feedback | | `/prs/{owner}/{repo}/{number}/checks` | GET | CI check status only | | `/prs/{owner}/{repo}/{number}/comments` | GET | PR comments only | | `/task-prs` | GET | List task-PR associations (query: task_ids) | | `/task-prs/{taskId}` | GET | Get PR association for a task | | `/watches/pr` | GET | List active PR watches | | `/watches/pr/{id}` | DELETE | Remove a PR watch | | `/watches/review` | GET | List review watches (query: workspace_id) | | `/watches/review` | POST | Create review watch | | `/watches/review/{id}` | PUT | Update review watch | | `/watches/review/{id}` | DELETE | Delete review watch | | `/watches/review/{id}/trigger` | POST | Manual "pull now" for one watch | | `/watches/review/trigger-all` | POST | Trigger all for workspace | | `/stats` | GET | PR stats (query: workspace_id, date range) | ### 5.2 `handlers.go` - WS handlers | Action | Description | |--------|-------------| | `github.status` | Auth status | | `github.task_prs.list` | Get task-PR associations (batch) | | `github.pr_feedback.get` | Get live PR feedback for session | | `github.review_watches.list` | List review watches | | `github.review_watches.create` | Create review watch | | `github.review_watches.update` | Update review watch | | `github.review_watches.delete` | Delete review watch | | `github.review_watches.trigger` | Trigger single check | | `github.review_watches.trigger_all` | Trigger all for workspace | --- ## Phase 6: Frontend ### 6.1 Types: `apps/web/lib/types/github.ts` ```typescript interface GitHubStatus { authenticated: boolean; username: string; authMethod: 'gh_cli' | 'pat' | 'none'; } interface GitHubPR { number: number; title: string; url: string; htmlUrl: string; state: 'open' | 'closed' | 'merged'; headBranch: string; baseBranch: string; authorLogin: string; repoOwner: string; repoName: string; draft: boolean; additions: number; deletions: number; createdAt: string; updatedAt: string; mergedAt: string | null; closedAt: string | null; } interface TaskPR { id: string; taskId: string; owner: string; repo: string; prNumber: number; prUrl: string; prTitle: string; headBranch: string; baseBranch: string; authorLogin: string; state: 'open' | 'closed' | 'merged'; reviewState: 'approved' | 'changes_requested' | 'pending' | ''; checksState: 'success' | 'failure' | 'pending' | ''; reviewCount: number; pendingReviewCount: number; commentCount: number; additions: number; deletions: number; createdAt: string; mergedAt: string | null; closedAt: string | null; lastSyncedAt: string | null; } interface PRReview { id: number; author: string; state: string; body: string; createdAt: string; } interface PRComment { id: number; author: string; body: string; path: string; line: number; side: string; createdAt: string; inReplyTo: number | null; } interface CheckRun { name: string; status: string; conclusion: string; htmlUrl: string; output: string; } interface PRFeedback { pr: GitHubPR; reviews: PRReview[]; comments: PRComment[]; checks: CheckRun[]; hasIssues: boolean; } interface PRWatch { id: string; sessionId: string; taskId: string; owner: string; repo: string; prNumber: number; branch: string; lastCheckedAt: string | null; } interface ReviewWatch { id: string; workspaceId: string; workflowId: string; workflowStepId: string; repoOwner: string; repoName: string; agentProfileId: string; executorProfileId: string; prompt: string; autoStart: boolean; enabled: boolean; pollIntervalSeconds: number; lastPolledAt: string | null; } ``` ### 6.2 API Client: `apps/web/lib/api/domains/github-api.ts` Methods mirroring all HTTP endpoints. ### 6.3 Store: `apps/web/lib/state/slices/github/github-slice.ts` Zustand slice: - `github.status` - auth status - `github.taskPRs.byTaskId` - PR associations per task (for task list PR icon) - `github.prWatches.bySessionId` - active PR watches per session - `github.reviewWatches.byWorkspaceId` - review watch configs ### 6.4 Hooks: `apps/web/hooks/domains/github/` - `use-github-status.ts` - auth status - `use-task-pr.ts` - PR association for a task (or batch for task list) - `use-pr-feedback.ts` - live PR feedback for session detail - `use-review-watches.ts` - CRUD for review watches - `use-pr-watch.ts` - PR monitoring status per session ### 6.5 Task List: PR Icon **File**: Modify task card/row components to show a PR icon (e.g., git-pull-request icon from lucide) when `taskPR` exists for that task. Color-coded: green (approved/passing), red (changes requested/failing), yellow (pending). ### 6.6 Settings Page: `apps/web/app/settings/general/github/page.tsx` - **Connection Status card**: Auth method, username, `gh` CLI availability - **Review Watches table**: List watches with repo, workflow, status, last polled - **Create/Edit Review Watch dialog**: Repo selector (from workspace repos with `provider: "github"`), workflow+step selector, agent profile, executor profile, custom prompt, auto-start toggle, poll interval, enable/disable ### 6.7 Session Detail: PR Info in Topbar + PR Panel **Session topbar** (`apps/web/components/github/pr-session-topbar.tsx`): In the session detail topbar (not the kanban topbar), show a compact PR summary when a PR is associated: - PR number + link (e.g., `#42`) - State badge (open/merged/closed) - CI status indicator (green check / red X / yellow spinner) - Review status (e.g., "2/3 approved", "changes requested") - Comment count badge - Clicking opens the full PR detail panel **Full PR detail panel** (`apps/web/components/github/pr-detail-panel.tsx`): Expandable/drawer panel accessible from the session topbar: - **PR header**: Title, number, link, state badge, draft indicator - **Stats bar**: +additions/-deletions, time since created, time pending review - **Reviews section**: List of reviews with state (approved/changes_requested), author, body - **Pending reviewers**: Who hasn't reviewed yet, how long pending - **CI Checks section**: List of check runs with status badge (pass/fail/pending), link to details - **Comments section**: Threaded review comments with file path, line number, author - **"Send to Agent" button**: Opens a dialog to compose a message to the agent, with checkboxes to include specific comments/CI failures as context. Sends via normal `PromptTask` flow. ### 6.8 Kanban Homepage Topbar: Manual Pull Button **Component**: `apps/web/components/github/refresh-reviews-button.tsx` Button in the **kanban/homepage topbar** (NOT the session detail topbar): - Triggers `POST /watches/review/trigger-all` for active workspace - Shows toast with results ("Found 3 new PRs to review") - Only visible when review watches exist for the workspace ### 6.9 Stats **Route**: `/api/v1/github/stats` (backend) + stats section in settings or dedicated page **Backend** (`internal/github/stats.go`): ```go type PRStats struct { // Counts TotalPRsCreated int // PRs authored by user (from github_task_prs) TotalPRsReviewed int // PRs reviewed (tasks from review watches) TotalComments int // Total review comments across all PRs TotalChecksRun int // Total CI check runs tracked // Rates CIPassRate float64 // % of PRs where all checks passed ApprovalRate float64 // % of PRs approved on first review cycle // Timing (averages) AvgTimeToFirstReview time.Duration // Time from PR creation to first review AvgTimeToMerge time.Duration // Time from PR creation to merge AvgTimePendingReview time.Duration // Time PRs spent waiting for review // By period (for charts) PRsByDay []DailyCount // PRs created per day ReviewsByDay []DailyCount // PRs reviewed per day CommentsByDay []DailyCount // Comments per day } type DailyCount struct { Date string `json:"date"` // "2026-02-22" Count int `json:"count"` } ``` Data sourced from `github_task_prs` table aggregations with date range filtering. **Frontend** (`apps/web/components/github/pr-stats.tsx`): - Stats cards: PRs created, reviewed, avg time to merge, CI pass rate - Line charts: PRs over time, comments over time - Uses `@kandev/ui` Card, Badge for layout - Could be embedded in GitHub settings page or as a section in a workspace dashboard --- ## Files to Create ``` apps/backend/internal/github/ models.go # Data models (PR, Review, Comment, Check, Watch, TaskPR) client.go # Client interface gh_client.go # gh CLI implementation pat_client.go # PAT HTTP implementation factory.go # Client factory (gh CLI → PAT fallback) store.go # SQLite persistence (watches, task_prs, dedup) service.go # Business logic poller.go # Background polling loops controller.go # HTTP endpoints handlers.go # WS handlers provider.go # DI provider stats.go # PR analytics queries apps/web/ lib/types/github.ts lib/api/domains/github-api.ts lib/state/slices/github/ index.ts github-slice.ts hooks/domains/github/ use-github-status.ts use-task-pr.ts use-pr-feedback.ts use-review-watches.ts use-pr-watch.ts app/settings/general/github/ page.tsx components/github/ github-status.tsx review-watch-table.tsx review-watch-dialog.tsx pr-detail-panel.tsx # Session detail PR view (expandable panel) pr-session-topbar.tsx # Compact PR info in session topbar pr-task-icon.tsx # PR icon for task list refresh-reviews-button.tsx # Topbar manual pull button send-to-agent-dialog.tsx # Compose feedback to send to agent pr-stats.tsx # PR analytics cards + charts ``` ## Files to Modify ``` apps/backend/internal/events/types.go # Add GitHub event constants apps/backend/internal/orchestrator/service.go # Add githubService field + setter + push tracker apps/backend/internal/orchestrator/event_handlers_git.go # PR auto-detection on push apps/backend/cmd/kandev/services.go # Wire GitHub service apps/backend/cmd/kandev/types.go # Add GitHub to Services struct apps/backend/cmd/kandev/main.go # Register GitHub routes apps/web/lib/state/store.ts # Add github slice apps/web/lib/state/hydration/merge-strategies.ts # Add github hydration apps/web/app/settings/general/ (layout or nav) # Add GitHub settings link apps/web/components/ (task card/row) # Add PR icon to task list apps/web/components/ (session detail panel) # Add PR detail panel ``` ## New file: `apps/backend/internal/orchestrator/event_handlers_github.go` Orchestrator event handlers for GitHub events (handlePRFeedback, handleNewReviewPR). ## Key Patterns to Reuse - **Provider pattern**: `Provide(cfg, log, ...) (*Service, cleanup, error)` — see `apps/backend/internal/secrets/provider.go` - **Event bus**: `bus.EventBus` publish/subscribe — see `apps/backend/internal/events/types.go` - **Setter injection**: `SetGitHubService()` — see `orchestrator/service.go` `SetWorkflowStepGetter()` - **Repository sub-interfaces**: Split interfaces in `apps/backend/internal/task/repository/interface.go` - **Task service Repos struct**: `taskservice.Repos{}` — see `apps/backend/cmd/kandev/services.go:38` - **Git event handling**: `handleGitStatusUpdate()` in `apps/backend/internal/orchestrator/event_handlers_git.go` - **Repository model provider fields**: `Provider`, `ProviderOwner`, `ProviderName` in `apps/backend/internal/task/models/models.go:285` - **Secrets store for PAT**: `apps/backend/internal/secrets/service.go` (Reveal method) - **Credential env provider**: `GITHUB_TOKEN` already listed in `apps/backend/internal/agent/credentials/env_provider.go:25` - **WS handler pattern**: See existing handlers in `apps/backend/internal/task/handlers/` - **Frontend store slices**: Domain pattern in `apps/web/lib/state/slices/` - **Frontend hooks**: Domain pattern in `apps/web/hooks/domains/` - **Settings page layout**: Existing pages at `apps/web/app/settings/general/{editors,secrets,notifications}/` - **Shadcn components**: Import from `@kandev/ui` — Table, Dialog, Button, Badge, etc. ## Implementation Order ### Milestone 1: GitHub Client (Backend) 1. `models.go` - data models 2. `client.go` - interface 3. `gh_client.go` - gh CLI implementation 4. `pat_client.go` - PAT fallback 5. `factory.go` - client factory 6. Unit tests for client JSON parsing ### Milestone 2: Persistence + Service (Backend) 7. `store.go` - SQLite tables + CRUD 8. `service.go` - business logic 9. `provider.go` - DI provider 10. Wire into `cmd/kandev/services.go` ### Milestone 3: API + Handlers (Backend) 11. `controller.go` - HTTP endpoints 12. `handlers.go` - WS handlers 13. Add event types to `events/types.go` 14. Wire routes in `cmd/kandev/main.go` ### Milestone 4: Poller + Orchestrator (Backend) 15. `poller.go` - background polling 16. Push detection in `event_handlers_git.go` 17. `event_handlers_github.go` - orchestrator GitHub event handlers 18. Wire GitHub service into orchestrator 19. Integration tests ### Milestone 5: Frontend Core 20. TypeScript types 21. API client 22. Store slice 23. Domain hooks ### Milestone 6: Frontend UI 24. Settings page (GitHub config + review watches) 25. Task list PR icon 26. Session detail PR panel (PR, comments, CI, approvals, "Send to Agent") 27. Topbar refresh button ### Milestone 7: Stats 28. `stats.go` - PR analytics queries (aggregations on `github_task_prs`) 29. Stats HTTP endpoint (`/api/v1/github/stats`) 30. Frontend stats component (`pr-stats.tsx`) with cards + charts 31. Integrate stats into GitHub settings page or dashboard ## Verification 1. **Backend unit tests**: Client JSON parsing, store CRUD, poller logic 2. **Integration**: `make -C apps/backend test` 3. **Lint/typecheck**: `make -C apps/backend lint` + `cd apps && pnpm --filter @kandev/web typecheck` 4. **Manual**: Check `/api/v1/github/status`, create review watch, trigger manual pull 5. **E2E**: Agent pushes → PR auto-detected → PR watch created → task shows PR icon → session detail shows PR panel → user reviews and sends feedback to agent