745 lines
29 KiB
Markdown
745 lines
29 KiB
Markdown
# 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 <number> --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 <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
|
|
|
|
```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
|