29 KiB
GitHub Integration Plan
Context
Kandev needs GitHub integration to support two automation workflows:
- 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.
- 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:
ghCLI primary (existinggh 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
// 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
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,mergeablegh pr view <number> --json reviews,commentsgh api repos/{owner}/{repo}/commits/{ref}/check-runsgh 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 <PAT>. 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
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):
-- 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):
- List all active
github_pr_watches - For each watch, fetch PR feedback (comments + checks) via client
- Compare with last known state (last_comment_at, last_check_status)
- If new comments or check state changes:
- Update the
github_task_prsrecord (review_state, checks_state, comment_count, etc.) - Publish
github.pr_feedbackevent to event bus (for UI notification, NOT auto-prompt) - Update watch timestamps
- Update the
- Also sync PR state (merged, closed) to
github_task_prs
Review Queue Loop (every 5 min, per-watch configurable):
- List all enabled
github_review_watches - For each watch, call
ListReviewRequestedPRsfiltered to the watch's repo - For each PR not already in
github_review_pr_tasks:- Publish
github.new_pr_to_reviewevent - Orchestrator handler creates the task + associates the PR
- Publish
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
// 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
Aheadgoes from>0to0with aRemoteBranchset, 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:
- Auto-create
PRWatchviagithubService.CreatePRWatch() - Auto-create
TaskPRassociation viagithubService.AssociatePRWithTask() - Publish
github.task_pr.updatedevent (so task list shows PR icon)
- Auto-create
Lookup chain:
repoStore.GetTaskSession(sessionID)→RepositoryIDrepoStore.GetRepository(repositoryID)→ProviderOwner,ProviderNameFindPRByBranch(providerOwner, providerName, branch)
4.3 Orchestrator GitHub event handlers
New file: apps/backend/internal/orchestrator/event_handlers_github.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
githubServicefield +SetGitHubService()setter - Add
pushTracker sync.Mapfor per-session ahead-count tracking - Subscribe to
github.pr_feedbackandgithub.new_pr_to_reviewevents inStart()
File: apps/backend/cmd/kandev/services.go
- Add GitHub service to
Servicesstruct, initialize viagithub.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
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 statusgithub.taskPRs.byTaskId- PR associations per task (for task list PR icon)github.prWatches.bySessionId- active PR watches per sessiongithub.reviewWatches.byWorkspaceId- review watch configs
6.4 Hooks: apps/web/hooks/domains/github/
use-github-status.ts- auth statususe-task-pr.ts- PR association for a task (or batch for task list)use-pr-feedback.ts- live PR feedback for session detailuse-review-watches.ts- CRUD for review watchesuse-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,
ghCLI 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
PromptTaskflow.
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-allfor 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):
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/uiCard, 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)— seeapps/backend/internal/secrets/provider.go - Event bus:
bus.EventBuspublish/subscribe — seeapps/backend/internal/events/types.go - Setter injection:
SetGitHubService()— seeorchestrator/service.goSetWorkflowStepGetter() - Repository sub-interfaces: Split interfaces in
apps/backend/internal/task/repository/interface.go - Task service Repos struct:
taskservice.Repos{}— seeapps/backend/cmd/kandev/services.go:38 - Git event handling:
handleGitStatusUpdate()inapps/backend/internal/orchestrator/event_handlers_git.go - Repository model provider fields:
Provider,ProviderOwner,ProviderNameinapps/backend/internal/task/models/models.go:285 - Secrets store for PAT:
apps/backend/internal/secrets/service.go(Reveal method) - Credential env provider:
GITHUB_TOKENalready listed inapps/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)
models.go- data modelsclient.go- interfacegh_client.go- gh CLI implementationpat_client.go- PAT fallbackfactory.go- client factory- Unit tests for client JSON parsing
Milestone 2: Persistence + Service (Backend)
store.go- SQLite tables + CRUDservice.go- business logicprovider.go- DI provider- Wire into
cmd/kandev/services.go
Milestone 3: API + Handlers (Backend)
controller.go- HTTP endpointshandlers.go- WS handlers- Add event types to
events/types.go - Wire routes in
cmd/kandev/main.go
Milestone 4: Poller + Orchestrator (Backend)
poller.go- background polling- Push detection in
event_handlers_git.go event_handlers_github.go- orchestrator GitHub event handlers- Wire GitHub service into orchestrator
- Integration tests
Milestone 5: Frontend Core
- TypeScript types
- API client
- Store slice
- Domain hooks
Milestone 6: Frontend UI
- Settings page (GitHub config + review watches)
- Task list PR icon
- Session detail PR panel (PR, comments, CI, approvals, "Send to Agent")
- Topbar refresh button
Milestone 7: Stats
stats.go- PR analytics queries (aggregations ongithub_task_prs)- Stats HTTP endpoint (
/api/v1/github/stats) - Frontend stats component (
pr-stats.tsx) with cards + charts - Integrate stats into GitHub settings page or dashboard
Verification
- Backend unit tests: Client JSON parsing, store CRUD, poller logic
- Integration:
make -C apps/backend test - Lint/typecheck:
make -C apps/backend lint+cd apps && pnpm --filter @kandev/web typecheck - Manual: Check
/api/v1/github/status, create review watch, trigger manual pull - 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