Skip to content

Add Github Integration & Syncing #13

Open
Ghvstcode wants to merge 17 commits intomainfrom
pr-lifecycle
Open

Add Github Integration & Syncing #13
Ghvstcode wants to merge 17 commits intomainfrom
pr-lifecycle

Conversation

@Ghvstcode
Copy link
Copy Markdown
Owner

No description provided.

Add a reusable ErrorBoundary class component with three visual levels
(root, route, widget) and wire it into the component tree at three
strategic points:

1. Root level — wraps the entire AppShell so catastrophic errors show
   a "Something went wrong, click to reload" screen instead of a
   white screen.

2. Route level — wraps TaskDetailView and TaskListView in MainContent
   so a crash in one view doesn't kill the sidebar/navigation. Uses
   key props to reset boundary state when switching tasks/repos.

3. Widget level — wraps TaskDiffViewer, which parses external git
   output and is the most likely crash source. Shows a compact
   inline fallback with a retry button.

Each boundary logs the error and component stack to the console via
componentDidCatch and shows a user-friendly fallback with contextual
heading, error message, and a retry/reload action.

- Add src/ui/components/ErrorBoundary.tsx (reusable, three levels)
- Wrap AppShell render in root-level ErrorBoundary
- Wrap TaskDetailView and TaskListView in route-level boundaries
- Wrap TaskDiffViewer in widget-level boundary

SUSTN-Task: 2192899f-1194-4f25-b94a-b5227d9374eb
Full lifecycle tracking for PRs opened by SUSTN — from creation
through review cycles to merge.

Core:
- GitHub API service (gh CLI) for reviews, comments, replies,
  re-request review
- PR lifecycle orchestration: poll GitHub every 2min, classify
  comments (actionable vs conversational), auto-address via Claude,
  reply to conversational, re-request review
- Heuristic comment classifier (actionable = change requests,
  conversational = praise/questions/nits)
- PR state machine: opened → in_review → changes_requested →
  addressing → re_review_requested → approved → merged
- Max review cycles guard (default 5, configurable) → flags
  needs_human_attention

Data model (migration 16):
- pr_state, pr_number, pr_review_cycles columns on tasks
- pr_reviews table (synced from GitHub)
- pr_comments table (synced from GitHub, with classification +
  addressed_in_commit tracking)

Rust:
- engine_address_review command (fresh Claude session with PR diff +
  review comments as context)
- run_gh_api / run_gh_api_post commands for GitHub API access

UI:
- PR state badge on TaskRow (color-coded by lifecycle state)
- PR state chip in TaskDetailHeader with review cycle counter
- PR Lifecycle settings in Integrations (toggle + max cycles)
- Auto-sets prState/prNumber when PRs are created (manual, auto,
  scheduler)

Hooks:
- usePrLifecyclePoller mounted in AppShell (2min polling interval)
- Event listeners for agent:review-addressed / review-address-failed

New files:
- src/core/services/github.ts
- src/core/services/pr-lifecycle.ts
- src/core/db/pr-lifecycle.ts
- src/core/api/usePrLifecycle.ts
Overlay synced PR review comments inline in the diff viewer at
their exact file/line positions. Comments appear in blue bubbles
(vs amber for local SUSTN comments) with:

- Reviewer name and classification badge (actionable/conversational)
- "Addressed" indicator when the agent has pushed a fix
- Reply button that posts via GitHub API using the user's token
- Reply text appears as a nested quote below the original comment

Also adds comment count indicator in diff header showing how many
comments come from the PR.

Modified:
- TaskDiffViewer: new GitHubPrComment type, GitHubCommentBubble
  component, merged annotation list, reply handler
- TaskDetailView: fetch pr_comments via usePrComments, pass to
  diff viewer with reply callback
Task no longer transitions to "done" when a PR is created — it stays
in "review" until the PR is approved or merged. This keeps the diff
viewer interactive (inline comments, reply to GH comments) throughout
the entire PR review cycle.

Changes:
- useEngine handleTaskResult: state stays "review" after auto-PR
- useScheduler: same — "review" not "done" after auto-PR
- TaskDetailView: remove handleUpdateState("done") after PR creation,
  update sidebar actions (View PR + Mark Done when PR exists,
  hide Request Changes since feedback comes from GitHub)
- pr-lifecycle service: set task state to "done" when prState becomes
  "approved" or "merged" (the only two terminal states)

The flow is now:
  pending → in_progress → review → [PR created, stays review] →
  PR approved/merged → done
The PR poller was not syncing comments when a reviewer left inline
comments without formally requesting changes. Comments were only
fetched inside the CHANGES_REQUESTED branch of the state machine.

Fixes:
- Sync PR comments on every poll tick, even without a formal review
- Backfill pr_state/pr_number for tasks created before lifecycle
  wiring (queries pr_url IS NOT NULL AND pr_state IS NULL on startup)
- Add verbose debug logging throughout the poller:
  - Tick start, settings check, active PR count
  - PR status fetch results (state, merged, reviewDecision)
  - Review count and details (reviewer, state, timestamp)
  - Comment sync count
  - Backfill operations
Two bugs fixed:

1. Comments were only synced when reviewer formally requested
   changes. Now comments are synced on every poll tick regardless
   of review state — plain comments, approvals, etc all get their
   inline comments pulled in.

2. Replies posted from SUSTN's diff viewer were getting synced back
   as separate comments on the next poll. Now we track reply bodies
   in the DB and skip GitHub comments whose body matches one of our
   recorded replies.

Also refactored processTaskPr to:
- Sync + classify comments BEFORE branching on review state
- Use DB-sourced comments for actionable/conversational decisions
  (instead of raw GitHub comments) so classification is persisted
- Disabled auto-reply for conversational comments (heuristic not
  reliable enough) — user can reply manually from diff viewer
Re-enables fully automated replies to conversational review comments.
The heuristic classifier drafts a reply (or falls back to "Thanks for
the feedback!"), posts it via gh api, and records it in pr_comments
so it won't be re-synced as a duplicate.
Three fixes:

1. Classifier now defaults to actionable — only marks comments as
   conversational if they're clearly praise ("nice!", "lgtm", "👍")
   or explicit nits ("nit: ..."). Everything else (questions,
   suggestions, concerns) is actionable.

2. Comments are now processed (classified, replied to, addressed)
   even without a formal CHANGES_REQUESTED review. Previously the
   agent only acted when a reviewer clicked "Request changes" —
   now inline comments alone trigger the full lifecycle.

3. Removed the classification badge from the diff viewer UI —
   it was noise. Only the "Addressed" indicator remains.
…ssion

Drop the regex-based comment classifier entirely. All review comments
now go to Claude in a single session that resumes the original task
session, preserving the agent's reasoning context.

The prompt asks Claude to handle EVERY comment:
- Code changes needed → make them and commit
- Question about reasoning → explain (Claude has context from the
  original session that wrote the code)
- Praise/acknowledgment → draft a brief thanks

Claude returns structured JSON with per-comment replies and whether
code was changed, which the poller uses to:
- Post replies to GitHub via gh api
- Mark comments as addressed in the DB
- Push code changes and re-request review

Rust changes:
- engine_address_review now accepts resume_session_id parameter
- Updated prompt to request unified classification + action
- Session ID saved back to task for future review cycles

This means the session accumulates context across review cycles —
each round builds on the previous one.
Claude was returning comment_id: null because the prompt wasn't
explicit enough about requiring the IDs back. Two fixes:

1. Updated prompt to use COMMENT_ID tag format, emphasize "MUST
   return the numeric ID", add example with real-looking number,
   and add "Do NOT use null" instruction

2. Added fallback in TS — if Claude still returns null IDs but the
   reply count matches the comment count, zip them positionally.
   Logs when this happens so we can track reliability.

Also updated the comment format from [Comment ID: X] to
[COMMENT_ID: X] to match the prompt's CRITICAL instruction.
…ctly

The worker's execute_task() overrides the prompt with its own resume
template when resume_session_id is set — it formats a generic
"address the feedback" prompt and asks for files_modified/summary
JSON, completely ignoring our carefully crafted prompt that asks for
per-comment replies with comment_ids.

Fix: engine_address_review now calls invoke_claude_cli directly,
managing branch checkout and SHA retrieval itself. This ensures
Claude sees our exact prompt with COMMENT_ID tags and returns the
replies format we need to post per-comment responses on GitHub.
Three improvements:

1. Remove the "Addressed N review comment(s) in commit..." summary
   comment posted to GitHub after addressing reviews. Per-comment
   replies already explain what was done — the summary was noise.

2. PR state changes in the activity timeline now show human-readable
   labels ("PR moved to In Review", "PR moved to Addressing Feedback")
   instead of raw "pr_state_change" text. Uses GitPullRequest icon.

3. Redesigned GitHub comment bubbles in the diff viewer:
   - Card-based layout with header/body/footer sections
   - Reviewer avatar initial + name in header bar
   - Green "Resolved" badge with checkmark when addressed
   - Reply shown in a dedicated footer section with reply icon
   - Reply form with keyboard shortcut hint, matching the local
     comment form style
   - Proper visual hierarchy and spacing
Complete redesign of the PR review comment overlay:

- GitHub-style threaded layout with avatar → card → timeline line
- Reviewer avatar with colored gradient (blue for reviewer, green
  for SUSTN reply)
- Comment card with header bar (name + resolved badge) and body
  with proper text sizing and word wrapping
- Reply shown as a separate threaded card connected by a vertical
  timeline line, labeled "You via SUSTN"
- Full text is always visible (break-words, no truncation)
- Stronger visual boundaries: rounded-lg border, bg-background
  body, muted header
- Reply form: larger textarea, proper focus ring, keyboard hint
The max-w-xl on the comment container was leaving dead space on the
right side. Now comments fill the available width. Also tightened
gaps between avatar and card (2.5 → 2), reduced reply card padding,
and used rounded-md instead of rounded-lg for crisper edges.
Replace the oversized threaded layout with a compact single-card
design: reviewer header, comment body, and reply all in one
contained card (max 480px). No avatars, no timeline lines, no
separate reply cards.

- 12px base text, tight padding (2.5/2px)
- Reply nested inline as a bordered section within the same card
- Resolved state shown with green header + checkmark badge
- Reply form compact with 2-row textarea
Replace cryptic "5 (5 from PR)" with clear "5 GitHub comments".
Also drops the local comment count from the label since inline
SUSTN comments are already visible in the diff.
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sustn Ready Ready Preview, Comment Mar 30, 2026 8:17am

Added verbose logging after engine_address_review returns to trace
exactly what happens: summary length, JSON parse result, reply count,
push result. This will show in the console why the state gets stuck.

Also wrapped the outer catch in a nested try/catch so the task state
ALWAYS transitions out of "addressing" even if the DB update itself
fails.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant