Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ The login flow requests `scope=repo read:org`:
| `repo` | Read issues, PRs, check runs, workflow runs (includes private repos) |
| `read:org` | `GET /user/orgs` — list user's organizations for the org selector |

**Note:** The `repo` scope grants write access to repositories. An Octokit request guard blocks all REST write operations (POST/PUT/PATCH/DELETE) and all GraphQL mutations except explicit user-confirmed PR approvals (`addPullRequestReview` with `event: APPROVE`).
**Note:** The `repo` scope grants write access to repositories. An Octokit request guard blocks all REST write operations (POST/PUT/PATCH/DELETE) and all GraphQL mutations except explicit user-confirmed PR approvals (`addPullRequestReview` with `event: APPROVE`) and marking draft PRs ready (`markPullRequestReadyForReview`).

### Local development OAuth App

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Open issues where you're the creator, assignee, or mentioned. A scope filter let

### Pull Requests

Open PRs with CI status dots (green/yellow/red), review decision badges, a read-only `auto` badge when GitHub auto-merge is enabled (with merge method tooltip), size badges (XS–XL by lines changed), draft indicators, and a two-step inline `Approve` button on PR rows requiring approval. A "blocked" filter catches PRs where checks are failing or a review requested changes. The scope filter works here too. Reviewer avatars stack for multiple reviewers.
Open PRs with CI status dots (green/yellow/red), review decision badges, a read-only `auto` badge when GitHub auto-merge is enabled (with merge method tooltip), size badges (XS–XL by lines changed), clickable two-step `Draft` → `Ready?` badges on your own draft PRs (read-only draft indicators on others'), and a two-step inline `Approve` button on PR rows requiring approval (hidden when CI checks are failing). A "blocked" filter catches PRs where checks are failing or a review requested changes. The scope filter works here too. Reviewer avatars stack for multiple reviewers.

### Actions

Expand Down Expand Up @@ -81,7 +81,7 @@ Star counts appear in repo group headers, fetched as part of the standard data r

### Dependencies Tab

An auto-detected built-in tab that appears when dependency bot PRs are found in your tracked repos. Dependency PRs are identified via a multi-layer bot detection pipeline (PR author login, branch name prefix, configurable label). Items are grouped by status — **Needs Review** (CI passing, not yet approved), **Waiting** (CI pending or draft), and **Stale** (open more than 14 days) — rather than by repo. Abandoned dependency pills link directly to the Renovate Dashboard issue for bulk resolution. Configurable via **Settings > Dependencies**.
An auto-detected built-in tab that appears when dependency bot PRs are found in your tracked repos. Dependency PRs are identified via a multi-layer bot detection pipeline (PR author login, branch name prefix, configurable label). Items are grouped by status — **Mergeable** (CI passing or no CI configured, not a draft, and no changes requested), **Pending Rebase**, **Needs Action** (with explicit reason badges for failing/running checks, merge conflicts, drafts, or requested changes), and **Stale** (not updated in over 14 days) — rather than by repo. Abandoned dependency pills link directly to the Renovate Dashboard issue for bulk resolution. Configurable via **Settings > Dependencies**.

### Custom Tabs

Expand Down Expand Up @@ -128,8 +128,8 @@ src/
layout/ # Header, TabBar, FilterBar
onboarding/ # OnboardingWizard, OrgSelector, RepoSelector
settings/ # SettingsPage, TrackedUsersSection, ThemePicker, Section, SettingRow
shared/ # 28 shared components: FilterInput, FilterChips, StatusDot,
# ReviewBadge, AutoMergeBadge, SizeBadge, RoleBadge, SortDropdown, PaginationControls,
shared/ # 29 shared components: FilterInput, FilterChips, StatusDot,
# ReviewBadge, AutoMergeBadge, DraftBadge, SizeBadge, RoleBadge, SortDropdown, PaginationControls,
# LoadingSpinner, SkeletonRows, ToastContainer, NotificationDrawer,
# RepoLockControls, UserAvatarBadge, ExpandCollapseButtons,
# RepoGitHubLink, ChevronIcon, ExternalLinkIcon, Tooltip/InfoTooltip
Expand Down Expand Up @@ -170,7 +170,7 @@ pnpm run screenshot # Capture dashboard screenshot

## Security

OAuth tokens are stored in `localStorage` under an app-specific key — this is standard for single-user personal dashboards and matches the threat model here. CSP headers block script injection via Cloudflare (`script-src 'self'` with a SHA-256 exception for the dark-mode initialization script only). An Octokit hook blocks all non-GET REST requests and all GraphQL mutations except the single user-confirmed PR approval mutation (addPullRequestReview with event: APPROVE) — the repo scope is required for private repo access, and all other write operations are rejected client-side. OAuth state is generated with `crypto.getRandomValues` and verified on callback. Token validation runs on every page load via `GET /user`; a 401 clears the stored token immediately.
OAuth tokens are stored in `localStorage` under an app-specific key — this is standard for single-user personal dashboards and matches the threat model here. CSP headers block script injection via Cloudflare (`script-src 'self'` with a SHA-256 exception for the dark-mode initialization script only). An Octokit hook blocks all non-GET REST requests and all GraphQL mutations except explicit user-confirmed PR approval (`addPullRequestReview` with `event: APPROVE`) and marking draft PRs ready (`markPullRequestReadyForReview`) — the repo scope is required for private repo access, and all other write operations are rejected client-side. OAuth state is generated with `crypto.getRandomValues` and verified on callback. Token validation runs on every page load via `GET /user`; a 401 clears the stored token immediately.

## Deployment

Expand Down
22 changes: 15 additions & 7 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ If you prefer not to use OAuth, you can sign in with a GitHub Personal Access To
Two token formats are accepted:

- **Classic tokens** (starts with `ghp_`) — recommended. Works across all organizations you belong to. Required scopes: `repo`, `read:org` (under admin:org).
- **Fine-grained tokens** (starts with `github_pat_`) — also work, but only access one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs from the dashboard, or read-only for viewing only).
- **Fine-grained tokens** (starts with `github_pat_`) — also work, but only access one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs or mark draft PRs ready from the dashboard, or read-only for viewing only).

The token is validated against the GitHub API before being stored. It is saved permanently in your browser's `localStorage` — you will not need to re-enter it on revisit.

Expand Down Expand Up @@ -227,9 +227,17 @@ The dot links to the PR's checks page on GitHub.

When GitHub auto-merge is enabled on a pull request, a read-only `auto` badge appears next to the review badge (in both comfortable and compact density across the Pull Requests, Dependencies, and Tracked tabs). Hover or focus the badge to view the configured merge method (`merge`, `squash`, or `rebase`).

#### Draft Badge & Mark Ready Action

Draft pull requests display a `Draft` badge (`D` in compact mode) across the Pull Requests, Dependencies, and Tracked tabs. When you are the author of an open draft PR, the badge is interactive and uses a two-step confirmation to mark the PR ready for review on GitHub:
1. Click **Draft** (or **D**) once to arm confirmation (**Ready?**). The confirmation state resets automatically after 4 seconds, on Escape, or when focus leaves the badge.
2. Click **Ready?** to execute `markPullRequestReadyForReview` on GitHub and transition the PR out of draft status.

Draft PRs authored by other users render as a read-only indicator.

#### Inline Approve Action

Open, non-draft pull requests not authored by you show an **Approve** button next to the pull request title on hover when branch protection requires a review (`Review needed`) or when a review has been requested of you or your team (unless you have already approved the PR without a fresh re-request).
Open, non-draft pull requests not authored by you show an **Approve** button next to the pull request title on hover when branch protection requires a review (`Review needed`) or when a review has been requested of you or your team (unless you have already approved the PR without a fresh re-request). The **Approve** button is hidden when CI checks are failing (`checkStatus === "failure"`).

Approval uses a two-step inline confirmation to prevent accidental clicks:
1. Click **Approve** once to arm confirmation (**Confirm?**, or **Confirm (auto)** when auto-merge is enabled on the PR). The confirmation state resets automatically after 4 seconds, on Escape, or when focus leaves the button.
Expand Down Expand Up @@ -318,10 +326,10 @@ Unlike the Pull Requests tab (which groups by repo), the Dependencies tab groups

| Group | Criteria |
|-------|----------|
| **Mergeable** | CI passing (all checks green), not yet approved — awaiting review |
| **Mergeable** | CI passing (or no CI configured), not a draft, and no changes requested — ready to review or merge |
| **Pending Rebase** | PR has the configured rebase label — waiting for the bot to rebase |
| **Needs Action** | CI pending, checks still running, or PR is a draft — not yet actionable |
| **Stale** | PR has been open more than 14 days without merging — may need a rebase or manual review |
| **Needs Action** | Failing or running CI checks, merge conflicts, draft PR, or changes requested — rows display explicit reason badges (`Failing checks`, `Merge conflict`, `Checks running`, `Draft`, `Changes`) |
| **Stale** | PR has not been updated in over 14 days — may need a rebase or manual review |

Within each group, PRs are sorted by repository name, then update category (maintenance, pin, digest, patch, minor, major), then update date.

Expand Down Expand Up @@ -838,9 +846,9 @@ For detailed per-source API call counts, see Settings > API Usage.

OAuth tokens (from "Sign in with GitHub") work across all your organizations and support all features. Classic PATs with the correct scopes (`repo`, `read:org`) behave identically to OAuth.

Fine-grained PATs are limited to one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs from the dashboard, or read-only for viewing only).
Fine-grained PATs are limited to one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs or mark draft PRs ready from the dashboard, or read-only for viewing only).

**Approving a PR fails with a permission error (403 / FORBIDDEN).**
**Approving a PR or marking a draft PR ready fails with a permission error (403 / FORBIDDEN).**

Your token does not have write permission for pull requests. If you are using a fine-grained Personal Access Token, open GitHub Settings, edit or regenerate the token with **Pull requests** set to **Read and write** (`Pull requests: Read and write`), and replace your token in **Settings > Data > Authentication**.

Expand Down
15 changes: 15 additions & 0 deletions mcp/tests/octokit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ describe("createOctokitClient — write guard hook", () => {
})
).rejects.toThrow(/Write operation blocked/);
});

it("blocks MARK_PR_READY_MUTATION (markPullRequestReadyForReview) on POST /graphql", async () => {
const { createOctokitClient } = await import("../src/octokit.js");
const { MARK_PR_READY_MUTATION } = await import("../../src/shared/validation.js");
const client = createOctokitClient("fake-token");

await expect(
client.request("POST /graphql", {
query: MARK_PR_READY_MUTATION,
variables: {
pullRequestId: "PR_kwDOtest42",
},
})
).rejects.toThrow(/Write operation blocked/);
});
});

// ── Singleton tests ───────────────────────────────────────────────────────────
Expand Down
Loading
Loading