Add Azure DevOps repository support for PR operations (Vibe Kanban) (#1808)
* All tasks are complete. Let me summarize what was implemented:
I've successfully implemented Azure DevOps repos support alongside the existing GitHub integration. Here's what was done:
- **`mod.rs`** - Defines the `GitHostService` trait and factory functions for creating services
- **`types.rs`** - Unified types: `GitHostProvider`, `RepoInfo`, `GitHostError`, `CreatePrRequest`, `UnifiedPrComment`
- **`detection.rs`** - URL pattern matching for auto-detecting GitHub (including Enterprise) and Azure DevOps
- **`github/mod.rs`** - `GitHubHostService` implementing the trait
- **`github/cli.rs`** - `GhCli` wrapper (moved from old location)
- **`azure/mod.rs`** - `AzureHostService` implementing the trait
- **`azure/cli.rs`** - `AzCli` wrapper for Azure DevOps CLI commands
1. **Auto-detection**: The system detects the provider from the git remote URL:
- `github.com` → GitHub
- `github.*.com` → GitHub Enterprise
- `dev.azure.com`, `*.visualstudio.com`, or URLs with `/_git/` → Azure DevOps
2. **Unified trait interface**: Both providers implement `GitHostService` with methods for:
- `get_repo_info()` - Get repository identification
- `check_auth()` - Verify CLI authentication
- `create_pr()` - Create pull requests
- `get_pr_status()` - Get PR status by URL
- `list_prs_for_branch()` - List PRs for a branch
- `get_pr_comments()` - Get PR comments
3. **Azure CLI commands supported**:
- `az repos pr create` - Create PRs
- `az repos pr show` - View PR details
- `az repos pr list` - List PRs
- `az repos pr list-threads` - Get PR comments
- `az account show` - Auth check
- `pr.rs` - Uses auto-detection for PR creation
- `pr_monitor.rs` - Uses provider detection from PR URL
- `task_attempts.rs` - Uses git_host for push auth checking
- `error.rs` - Updated `GitHost` error variant
- `share.rs` - Updated `GitHost` error variant
- `generate_types.rs` - Updated type exports
- Old `crates/services/src/services/github.rs` module and directory (replaced by git_host)
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
All tests pass, including the new URL decoding tests. The fix is complete - the URL-encoded components (like `Vibe%20Kanban%20Test`) will now be properly decoded to `Vibe Kanban Test` before being passed to the Azure CLI.
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
All done! Here's a summary of the changes:
Updated `get_repo_info()` to use the Azure CLI's `az repos show --detect true` command instead of manual URL parsing, similar to how GitHub uses `gh repo view --json owner,name`.
1. **Added `extract_repo_name_from_url()`** - A simple helper that extracts just the repository name from Azure DevOps URLs (looks for `/_git/{repo}` pattern). This is still needed because `az repos show` requires the `--repository` parameter.
2. **Updated `get_repo_info()`** - Now uses `az repos show --repository {name} --detect true --output json` which:
- Auto-detects organization and project from git config
- Returns canonical names (properly decoded)
- Validates the repo exists
3. **Added `extract_org_from_response()`** - Extracts the organization from the `remoteUrl` field in the CLI response, with fallback to URL parsing.
4. **Fixed `run()` method** - Now properly passes the `dir` parameter to `Command::current_dir()` so the `--detect` flag works correctly.
5. **Kept `parse_azure_remote_url()`** - Still used as a fallback for organization extraction and for PR URL parsing.
6. **Added tests** - New tests for `extract_repo_name_from_url()` covering various URL formats.
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
Done! The cleanup is complete. Here's what was removed:
Removed the brittle `parse_azure_remote_url()` function and related code:
1. **Removed `AzureRepoInfo` struct** - No longer needed
2. **Removed `parse_azure_remote_url()` function** - The ~90-line function that manually parsed various Azure DevOps URL formats
3. **Updated `get_repo_info()`** - Changed from fallback (`unwrap_or_else`) to error (`ok_or_else`) when organization can't be extracted from the CLI response
4. **Removed 4 tests** - `test_parse_azure_remote_url_https`, `test_parse_azure_remote_url_visualstudio`, `test_parse_azure_remote_url_ssh`, `test_parse_azure_remote_url_with_spaces`
The code now relies entirely on:
- `extract_repo_name_from_url()` - Simple helper to get repo name from URL (for passing to `az repos show`)
- `az repos show --detect true` - Azure CLI command that auto-detects org/project from git config
- `extract_org_from_response()` - Extracts org from the CLI's `remoteUrl` JSON field
The implementation is complete. Here's a summary of the changes:
Simplified the Azure DevOps integration by using the CLI's `--detect` flag instead of parsing URLs to extract org/project/repo.
1. **`types.rs`** - Changed `RepoInfo::AzureDevOps` to store just `repo_path: PathBuf` instead of `organization`, `project`, and `repo_name`. Removed the unused `repo_name()` method.
2. **`cli.rs`** - Major simplification:
- Removed URL parsing helpers: `decode_url_component()`, `extract_repo_name_from_url()`, `extract_org_from_response()`
- `get_repo_info()` now just verifies it's a valid Azure repo via `az repos show --detect true` and returns the path
- `create_pr()` now takes `repo_path` and uses `--detect true` instead of explicit org/project/repo
- `list_prs_for_branch()` now takes `repo_path` and uses `--detect true`
- `get_pr_threads()` now takes `repo_path` and uses `--detect true`
- `view_pr()` now takes the full PR URL and parses org + PR ID internally
- `parse_pr_url()` simplified to return just `(org, pr_id)` instead of 4 values
- Parsing functions simplified to extract URLs from response's `repository.webUrl` field
- Updated tests for the new API
3. **`mod.rs`** - Updated `AzureHostService` to:
- Extract `repo_path` from `RepoInfo::AzureDevOps`
- Pass `repo_path` to CLI methods that need it
- Pass the PR URL directly to `view_pr()` for `get_pr_status()`
- URL parsing reduced to minimal extraction of org + PR ID from PR URLs (only needed for `get_pr_status()`)
- Repo-based operations don't parse URLs at all - CLI auto-detects via git config
- Much cleaner, less brittle code
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
All done. The implementation is complete:
**Summary of changes:**
1. **`types.rs`**: Updated `RepoInfo::AzureDevOps` to store `organization_url`, `project`, `repo_name` instead of `repo_path`
2. **`cli.rs`**:
- `get_repo_info()` now parses the JSON response from `az repos show --detect true` to extract repo name, project name, and organization URL
- Added `extract_org_url()` helper to extract the base org URL from the API URL
- Updated `create_pr()`, `list_prs_for_branch()`, `get_pr_threads()` to take explicit `organization_url`, `project`, `repo_name` params
3. **`mod.rs`** (`AzureHostService`):
- `create_pr()`, `list_prs_for_branch()`, `get_pr_comments()` all now extract org/project/repo from `RepoInfo` and pass them to the CLI methods
The key insight was that the `url` field from `az repos show` (e.g., `https://dev.azure.com/{org}/.../_apis/...`) can be truncated to `https://dev.azure.com/{org}` and used directly with the `--organization` flag — minimal URL parsing needed.
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
* Done. Refactored all 4 `serde_json::Value` parsing sites to use typed structs:
**Changes to `cli.rs`:**
1. **Added typed response structs** with `#[derive(Deserialize)]`:
- `AzRepoShowResponse`, `AzProject` - for `az repos show`
- `AzPrResponse`, `AzRepository`, `AzCommit` - for PR responses
- `AzThread`, `AzThreadContext`, `AzFilePosition`, `AzThreadComment`, `AzAuthor` - for thread comments
2. **Updated parsing functions**:
- `get_repo_info()` - now deserializes into `AzRepoShowResponse`
- `parse_pr_response()` - now deserializes into `AzPrResponse`
- `parse_pr_list_response()` - now deserializes into `Vec<AzPrResponse>`
- `parse_pr_threads()` - now deserializes into `Vec<AzThread>`
3. **Replaced `extract_pr_info()`** with `az_pr_to_info()` that takes the typed struct directly
4. **Removed `serde_json::Value` import** - no longer needed
The code is now cleaner with no `.get().and_then().as_str()` chains, and serde provides better error messages on parse failures.
* Done. Added the missing `--repository` parameter to `get_pr_threads()`:
1. **`cli.rs`**: Added `repo_name: &str` parameter and `--repository` flag to the CLI command
2. **`mod.rs`**: Updated `get_pr_comments()` to extract both `organization_url` and `repo_name` from `RepoInfo` and pass them to the CLI
Done. Here's a summary of the changes:
**Changes made to `crates/services/src/services/git_host/azure/cli.rs`:**
1. **Removed git2 dependency** - No longer imports `git2::Repository`
2. **Added new typed structs** for `az repos list` response:
- `AzRepoListItem` - with `name`, `project`, and `remote_url` fields
- `AzRepoProject` - with `name` field
3. **Rewrote `get_repo_info()`** to use `az repos list --detect true`:
- Runs CLI command from the repo directory to auto-detect org/project
- Takes the first repo from the list (assuming single repo per directory)
- Extracts org from `remoteUrl` field
4. **Replaced `parse_azure_remote_url()`** with simpler `extract_org_from_remote_url()`:
- Only extracts the organization name from the URL
- Handles both `dev.azure.com` and `visualstudio.com` formats
- Much simpler than the previous 3-tuple URL parser
5. **Updated tests** - Replaced the old URL parsing tests with new `extract_org_from_remote_url` tests
The key improvement is that `az repos list --detect true` uses the Azure CLI's built-in detection (from git config) to find the org and project, then returns all repo info including project name. This avoids git2 library usage and complex URL parsing entirely.
Done. Here's a summary of all changes:
**Summary of Changes**
1. **`crates/services/src/services/git_host/types.rs`**:
- Added `project_id` and `repo_id` fields to `RepoInfo::AzureDevOps` enum variant
2. **`crates/services/src/services/git_host/azure/cli.rs`**:
- Added `id` field to `AzRepoListItem` struct
- Added `id` field to `AzRepoProject` struct
- Added `AzThreadsResponse` struct to wrap the `{ "value": [...] }` REST API response
- Updated `get_repo_info()` to return `project_id` and `repo_id` from `az repos list` output
- Updated `get_pr_threads()` to:
- Accept `project_id` and `repo_id` instead of names
- Use `az rest` with REST API URL instead of non-existent `az repos pr list-threads`
- Updated `parse_pr_threads()` to unwrap the `value` field from REST API response
3. **`crates/services/src/services/git_host/azure/mod.rs`**:
- Updated `create_pr` and `list_prs_for_branch` to use `..` pattern for new fields
- Updated `get_pr_comments()` to extract and pass `project_id` and `repo_id` to CLI
**Key Improvements**:
- Uses UUIDs (`project_id`, `repo_id`) instead of names for REST API calls, avoiding URL encoding issues with names containing spaces like "Vibe Kanban Test"
- Fixed `get_pr_threads()` to use `az rest` since `az repos pr list-threads` doesn't exist as an Azure CLI command
- All unit tests pass
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
Done. The fix adds `--resource 499b84ac-1321-427f-aa17-267ca6975798` to the `az rest` command, which tells it to request an Azure DevOps token instead of a generic Azure token. This should now properly authenticate to the Azure DevOps REST API.
Done. The fix replaces `az rest` with `az devops invoke --area git --resource pullRequestThreads`. This uses the same authentication as other `az repos pr` commands, so it should work without requiring the separate "identity materialization" step.
Done. The fix replaces the single combined string for `--route-parameters` with individual arguments for each `key=value` pair. This matches how the Azure CLI expects the parameters to be passed - each route parameter should be a separate shell argument after the `--route-parameters` flag.
Done. Removed all the requested comments from:
- `crates/server/src/routes/task_attempts.rs` (lines 451, 490)
- `crates/server/src/routes/task_attempts/pr.rs` (lines 283, 516)
- `crates/services/src/services/git_host/azure/cli.rs` (section header comment, doc comments on structs and methods)
* Done. Removed the module-level doc comment and all the method doc comments from the `GitHostService` trait and public functions in `mod.rs`.
Done. Removed the obvious/redundant comments from `types.rs`.
Done. Removed the unnecessary `Arc` usage from all three functions in `mod.rs`. The strings are already cloned when extracted from `RepoInfo`, so we can just clone them directly in the retry closure.
* All tests pass. Fixed the `npm run check` and `npm run lint` issues by:
1. Regenerated TypeScript types from Rust
2. Updated `frontend/src/lib/api.ts` to use `CreatePrApiRequest` instead of `CreateGitHubPrRequest`
3. Updated `CreatePRDialog.tsx` to handle the new error types (`cli_not_installed`/`cli_not_logged_in` with a `provider` field instead of `github_cli_not_installed`/`github_cli_not_logged_in`)
4. Fixed clippy warnings about collapsible if statements in `parse_pr_url()`
* Done. I've made `url`, `diff_hunk`, and `author_association` optional in the `UnifiedPrComment` type:
**Changes made:**
1. **`crates/services/src/services/git_host/types.rs`** - Changed the `UnifiedPrComment` enum:
- `url: String` → `url: Option<String>`
- `diff_hunk: String` → `diff_hunk: Option<String>`
- `author_association: String` → `author_association: Option<String>`
2. **`crates/services/src/services/git_host/azure/cli.rs`** - Updated Azure comment creation to use `None` instead of empty strings
3. **`crates/services/src/services/git_host/github/mod.rs`** - Wrapped GitHub values in `Some()`
4. **`frontend/src/components/ui/github-comment-card.tsx`** - Updated interface to allow `url?: string | null` and `diffHunk?: string | null`
5. **`frontend/src/components/ui/wysiwyg/nodes/github-comment-node.tsx`**:
- Updated `NormalizedComment` interface
- Fixed click handler to check `if (data.url)` before opening
- Removed `data.url` from validation (only `id`, `comment_type`, `author`, `body` required)
All checks pass (`pnpm run check`, `pnpm run lint`, `cargo test --workspace`).
* Done. All three changes are complete:
1. **Removed unnecessary temp file** in `azure/cli.rs:create_pr()` - was writing body to temp file then immediately reading it back. Now just passes `body` directly to `OsString`.
2. **Added structs for JSON parsing** in `github/cli.rs:parse_pr_comments()` - replaced manual `item.get("field")...` parsing with `GhCommentsWrapper` and `GhCommentResponse` structs using serde.
3. **Added structs for JSON parsing** in `github/cli.rs:parse_pr_review_comments()` - replaced manual parsing with `GhReviewCommentResponse` struct.
All checks pass (`pnpm run check`, `pnpm run lint`, `cargo test --workspace`).
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
Done. Both changes are complete:
1. **Inlined single-field structs** - Consolidated `GhCommentAuthor` and `GhReviewCommentUser` into a single `GhUserLogin` struct that's reused by both `GhCommentResponse` and `GhReviewCommentResponse`.
2. **Converted `extract_pr_info` to struct parsing** - Added `GhPrResponse` and `GhMergeCommit` structs. Replaced the manual `Value::get()` parsing with serde deserialization. The new `pr_response_to_info()` helper converts the parsed struct to `PullRequestInfo`. Also removed the now-unused `serde_json::Value` import.
All checks pass (`pnpm run check`, `pnpm run lint`, `cargo test --workspace`).
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
* Done. I've updated `detection.rs` to use `GitCli` instead of `git2`:
1. **Added `get_remote_url` method to `GitCli`** (`git/cli.rs`) - Uses `git remote get-url <remote>` to get the URL
2. **Updated `detect_provider`** (`git_host/detection.rs`) - Now uses `GitCli::get_remote_url()` instead of `git2::Repository`
This keeps the codebase consistent with the pattern of using `GitCli` for git operations rather than mixing `git2` directly in various modules.
All checks pass (`pnpm run check`, `pnpm run lint`, `cargo test --workspace`).
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
* Done! The refactor is complete. Here's a summary of the changes:
**New Architecture:**
1. **`ProviderKind` enum** (was `GitHostProvider`) - The discriminant for provider types (GitHub, AzureDevOps, Unknown)
2. **`GitHostProvider` trait** (was `GitHostService`) - The interface that provider implementations must satisfy
3. **`GitHubProvider` struct** (was `GitHubHostService`) - GitHub implementation
4. **`AzureDevOpsProvider` struct** (was `AzureHostService`) - Azure DevOps implementation
5. **`GitHostService` struct** (new) - Main entry point that:
- Holds repo context (`repo_path`, `GitCli`)
- Provides `provider_for_remote(remote_name)` to get a provider for a specific remote
- Provides `provider_from_url(url)` for URL-based detection (e.g., PR URLs)
- Provides `provider_for_kind(kind)` for direct provider construction
**Benefits:**
- Provider detection is now contextual - callers specify which remote they want
- The service struct encapsulates provider detection logic
- Cleaner separation: `GitHostService` is the facade, `GitHostProvider` trait is the contract
- Ready for multi-remote support (different providers for different remotes)
All checks pass (`pnpm run check`, `pnpm run lint`, `cargo test --workspace`).
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
All the call sites have been updated to use the new cleaner `GitHostService` facade API. Here's a summary of the changes:
**Changes made:**
1. **`pr.rs:414-418` (`attach_existing_pr`)**: Simplified from:
```rust
let git_host = git_host::GitHostService::new(&repo.path);
let git_host_provider = git_host.provider_for_remote("origin")?;
let repo_info = git_host_provider.get_repo_info(&repo.path).await?;
let prs = git_host_provider.list_prs_for_branch(&repo_info, &workspace.branch).await?;
```
To:
```rust
let git_host = git_host::GitHostService::new(&repo.path);
let prs = git_host.list_prs_for_branch(&workspace.branch).await?;
```
2. **`pr.rs:511-525` (`get_pr_comments`)**: Simplified from provider-based pattern to:
```rust
let git_host = git_host::GitHostService::new(&repo.path);
let provider = git_host.provider_kind()?;
match git_host.get_pr_comments(pr_info.number).await { ... }
```
3. **`task_attempts.rs:451-453` (`push_task_attempt_branch`)**: Removed explicit `check_auth()` call since auth is now checked during the actual operation. Just verify provider is supported:
```rust
let git_host = git_host::GitHostService::new(&worktree_path);
git_host.provider_kind()?;
```
4. **`task_attempts.rs:490-492` (`force_push_task_attempt_branch`)**: Same simplification.
The new API is much cleaner - call sites just create a `GitHostService::new(path)` and call methods directly without dealing with providers, repo info, or explicit auth checks.
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
The refactoring is complete. Here's a summary of what was done:
**Changes made:**
1. **`crates/services/Cargo.toml`** - Added `enum_dispatch` dependency
2. **`crates/services/src/services/git_host/mod.rs`** - Completely rewritten:
- `GitHostProvider` is now a public trait with `#[enum_dispatch(GitHostService)]`
- `GitHostService` is now an enum with `GitHub` and `AzureDevOps` variants
- Constructors: `from_pr_url(url)` and `from_remote_url(url)` - no `new()` method
- Methods now take `repo_path` as a parameter where needed
3. **`crates/services/src/services/git_host/github/mod.rs`** - Updated to match new trait signature (methods take `repo_path`, `check_auth` called inside `create_pr`)
4. **`crates/services/src/services/git_host/azure/mod.rs`** - Updated similarly
5. **`crates/services/src/services/git.rs`** - Added `get_remote_url()` method to `GitService`
6. **Call site updates:**
- `pr_monitor.rs` - Uses `from_pr_url`
- `pr.rs` - Uses `get_remote_name_from_branch_name` + `get_remote_url` + `from_remote_url`
- `task_attempts.rs` - Same pattern for push functions
**New API usage:**
```rust
// From a PR URL (e.g., for checking PR status)
let git_host = GitHostService::from_pr_url(&pr_url)?;
let status = git_host.get_pr_status(&pr_url).await?;
// From a remote URL (e.g., for creating PRs)
let remote_name = git_service.get_remote_name_from_branch_name(&repo_path, &branch)?;
let remote_url = git_service.get_remote_url(&repo_path, &remote_name)?;
let git_host = GitHostService::from_remote_url(&remote_url)?;
let pr_info = git_host.create_pr(&repo_path, &pr_request).await?;
// Get provider kind (infallible since already constructed)
let provider = git_host.provider_kind();
```
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
All cleaned up. Here's the final architecture:
**`git_host/mod.rs`:**
```rust
pub trait GitHostProvider: Send + Sync {
async fn create_pr(&self, repo_path: &Path, request: &CreatePrRequest) -> Result<PullRequestInfo, GitHostError>;
async fn get_pr_status(&self, pr_url: &str) -> Result<PullRequestInfo, GitHostError>;
async fn list_prs_for_branch(&self, repo_path: &Path, branch_name: &str) -> Result<Vec<PullRequestInfo>, GitHostError>;
async fn get_pr_comments(&self, repo_path: &Path, pr_number: i64) -> Result<Vec<UnifiedPrComment>, GitHostError>;
fn provider_kind(&self) -> ProviderKind;
}
pub enum GitHostService {
GitHub(GitHubProvider),
AzureDevOps(AzureDevOpsProvider),
}
impl GitHostService {
pub fn from_url(url: &str) -> Result<Self, GitHostError>
}
```
**Changes made:**
1. Removed `check_auth` and `get_repo_info` from the public trait - they're now private methods on each provider
2. Consolidated `from_pr_url` and `from_remote_url` into a single `from_url` constructor
3. The trait now has exactly 5 methods - the minimal public API needed by call sites
**Call site usage is clean:**
```rust
// From any URL (PR URL or remote URL)
let git_host = GitHostService::from_url(&url)?;
// Use the trait methods
let pr_info = git_host.create_pr(&repo_path, &request).await?;
let status = git_host.get_pr_status(&pr_url).await?;
let prs = git_host.list_prs_for_branch(&repo_path, &branch).await?;
let comments = git_host.get_pr_comments(&repo_path, pr_number).await?;
let kind = git_host.provider_kind();
```
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
Done. The refactoring is complete:
1. **Created `GitHubRepoInfo`** in `git_host/github/cli.rs` - a simple struct with `owner` and `repo_name`
2. **Created `AzureRepoInfo`** in `git_host/azure/cli.rs` - a struct with `organization_url`, `project`, `project_id`, `repo_name`, `repo_id`
3. **Updated both providers** to use their respective provider-specific structs directly, removing all the match arms and error cases for "wrong variant" that could never happen
4. **Removed the shared `RepoInfo` enum** from `types.rs` and the public exports
The code is cleaner now - each provider has its own internal representation of repo info, and there's no shared enum that requires impossible error handling paths.
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
The fix compiles. The issue was that `get_remote_name_from_branch_name` was calling `get_remote_from_branch_ref` which uses `repo.branch_remote_name()` - this git2 function requires the branch to have upstream tracking configured. For local branches that haven't been pushed yet (or don't have tracking set), this fails with the error you saw.
The fix changes the behavior to fall back to the default remote (typically "origin") when the branch doesn't have upstream tracking configured. This makes sense because:
1. Most repos only have one remote anyway
2. When creating a PR for a local branch, we need to push to *some* remote - the default is the sensible choice
3. The original code computed `default_remote` but never actually used it as a fallback
Done. Added `get_remote_url_from_branch_or_default` to `GitService` and updated all 4 call sites. The pattern is now:
```rust
let remote_url = deployment
.git()
.get_remote_url_from_branch_or_default(&repo_path, &branch)?;
let git_host = git_host::GitHostService::from_url(&remote_url)?;
```
Two lines instead of four, and the function name makes the fallback behavior explicit.
Done. Removed the unnecessary provider detection checks from both `push_task_attempt_branch` and `force_push_task_attempt_branch`. The push operations now just push directly - git push works with any remote regardless of whether we support it as a PR provider. Auth errors from git push itself (SSH keys, HTTPS credentials) still come through `GitServiceError::GitCLI(...)` and propagate to the frontend the same way.
* All done. Removed the low-value comments:
- "Create git host from the PR URL"
- "Fetch comments"
- "Get the URL for a remote (e.g., "origin")."
- "Azure DevOps-specific repository information"
- "GitHub-specific repository information"
- "Get the remote URL for the branch to detect the provider" (2 places)
- "Get repository info from the GitHub CLI"
- "Get repository info from the Azure CLI"
- "Check authentication status" (2 places)
Kept comments that provide actual value like the fallback behavior explanation on `get_remote_url_from_branch_or_default`.
All done. Removed the low-value comments:
- "Create git host from the PR URL"
- "Fetch comments"
- "Get the URL for a remote (e.g., "origin")."
- "Azure DevOps-specific repository information"
- "GitHub-specific repository information"
- "Get the remote URL for the branch to detect the provider" (2 places)
- "Get repository info from the GitHub CLI"
- "Get repository info from the Azure CLI"
- "Check authentication status" (2 places)
- "Get PR status from URL (auto-detects provider)"
- "High-level errors originating from the Azure CLI."
- "High-level errors originating from the GitHub CLI."
- "Azure DevOps hosting provider implementation"
- "GitHub hosting provider implementation"
- "Newtype wrapper for invoking the `gh` command."
- "Newtype wrapper for invoking the `az` command."
- "Trait for git hosting provider operations (GitHub, Azure DevOps, etc.)"
- All the trait method doc comments (Create a pull request, Get PR status, etc.)
- "Git hosting service - dispatches to the appropriate provider."
- "Create a GitHostService by detecting the provider from a URL."
- Simplified the Azure DevOps pattern comments in detection.rs
* Done. Here's a summary of the fix:
**Problem**: `get_repo_info` used `az repos list --detect true` which returns ALL repos in the Azure DevOps project, then just took the first one. This was wrong for multi-repo projects.
**Solution**:
1. `AzureDevOpsProvider` now stores the remote URL passed to `from_url()`
2. `get_repo_info` now takes the remote URL and filters the repo list to find the matching repo using `remote_urls_match()`
3. Added `remote_urls_match()` helper that normalizes URLs (lowercase, strips trailing `/` and `.git`) for comparison
4. Added `extract_organization_url()` to get the Azure organization URL from a remote URL (supports both `dev.azure.com` and legacy `visualstudio.com` formats)
5. Removed the now-unused `extract_org_from_remote_url()` function
The flow is now:
- Call site gets remote URL via `get_remote_url_from_branch_or_default()`
- Creates `GitHostService::from_url(&remote_url)` which stores the URL on `AzureDevOpsProvider`
- When `get_repo_info` is called, it lists all repos in the project and finds the one matching the stored remote URL
Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
* Done. Summary of changes:
1. **`pr.rs:get_pr_comments`** - Now gets the remote URL from `workspace_repo.target_branch` instead of using the PR URL to create the `GitHostService`
2. **`azure/cli.rs`**:
- Added `ssh_url` field to `AzRepoListItem`
- Updated `get_repo_info` to detect if the input URL is SSH (`starts_with("git@")`) and compare against the appropriate field (`ssh_url` vs `remote_url`)
- Renamed `remote_urls_match` to `urls_match` since it now handles both HTTPS and SSH URLs
- Added SSH URL test case
* Build passes. The simple fix handles both SSH formats without adding a dependency.
* Build passes. Now `ssh_url` is optional - if it's not in the response and the user has an SSH remote configured, they'll get a clear error message ("No repo found matching remote URL: ...").
* Done. The `urls_match` function now normalizes `ssh://` prefix to scp-style before comparing.
* Done. Summary of changes:
1. **Removed `remote_url` field from `AzureDevOpsProvider`** - no longer stored on the struct
2. **Updated `GitHostProvider` trait** - added `remote_url: &str` parameter to:
- `create_pr`
- `list_prs_for_branch`
- `get_pr_comments`
3. **Updated `AzureDevOpsProvider`** - methods now accept `remote_url` and pass it to `get_repo_info`
4. **Updated `GitHubProvider`** - methods accept `_remote_url` (unused, since gh CLI auto-detects)
5. **Updated call sites in `pr.rs`** - all three methods now pass `remote_url`:
- `create_pr` passes `&remote_url`
- `list_prs_for_branch` passes `&remote_url`
- `get_pr_comments` passes `&remote_url`
6. **Fixed `attach_existing_pr`** - now uses `workspace_repo.target_branch` instead of `workspace.branch` when getting the remote URL (consistent with other call sites)
* Cleanup script changes for workspace 28eb9564-f95a-42e0-8cf2-5228c2ffc0bf
* Update frontend remote copy (vibe-kanban a7bf13b6)
We added azure support but in the frontend many things have github specific wording. Please fix!
* Done. I've addressed the PR comment by:
1. **Renamed `CreatePrError` to `PrError`** - A more generic name that works for both `create_pr` and `attach_existing_pr`
2. **Updated `attach_existing_pr`** to mirror the error handling from `create_pr`:
- Changed return type from `ApiResponse<AttachPrResponse>` to `ApiResponse<AttachPrResponse, PrError>`
- Added error handling for `GitHostService::from_url()` - handles `UnsupportedProvider` and `CliNotInstalled`
- Added error handling for `list_prs_for_branch()` - handles `CliNotInstalled` and `AuthFailed`
3. **Updated `generate_types.rs`** to use the renamed `PrError`
4. **Updated `frontend/src/lib/api.ts`** to use `PrError` instead of `CreatePrError`
5. **Regenerated TypeScript types** and verified all checks pass
This commit is contained in:
@@ -177,19 +177,25 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
||||
|
||||
if (result.error) {
|
||||
if (
|
||||
result.error.type === 'github_cli_not_installed' ||
|
||||
result.error.type === 'github_cli_not_logged_in'
|
||||
result.error.type === 'cli_not_installed' ||
|
||||
result.error.type === 'cli_not_logged_in'
|
||||
) {
|
||||
if (isMacEnvironment) {
|
||||
// Only show setup dialog for GitHub CLI on Mac
|
||||
if (result.error.provider === 'git_hub' && isMacEnvironment) {
|
||||
await showGhCliSetupDialog();
|
||||
} else {
|
||||
const ui = mapGhCliErrorToUi(
|
||||
'SETUP_HELPER_NOT_SUPPORTED',
|
||||
defaultGhCliErrorMessage,
|
||||
t
|
||||
);
|
||||
setGhCliHelp(ui.variant ? ui : null);
|
||||
setError(ui.variant ? null : ui.message);
|
||||
const providerName =
|
||||
result.error.provider === 'git_hub'
|
||||
? 'GitHub'
|
||||
: result.error.provider === 'azure_dev_ops'
|
||||
? 'Azure DevOps'
|
||||
: 'Git host';
|
||||
const action =
|
||||
result.error.type === 'cli_not_installed'
|
||||
? 'not installed'
|
||||
: 'not logged in';
|
||||
setError(`${providerName} CLI is ${action}`);
|
||||
setGhCliHelp(null);
|
||||
}
|
||||
return;
|
||||
} else if (
|
||||
|
||||
@@ -14,15 +14,15 @@ import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { MessageSquare, AlertCircle, Loader2 } from 'lucide-react';
|
||||
import { usePrComments } from '@/hooks/usePrComments';
|
||||
import { GitHubCommentCard } from '@/components/ui/github-comment-card';
|
||||
import { PrCommentCard } from '@/components/ui/pr-comment-card';
|
||||
import type { UnifiedPrComment } from 'shared/types';
|
||||
|
||||
export interface GitHubCommentsDialogProps {
|
||||
export interface PrCommentsDialogProps {
|
||||
attemptId: string;
|
||||
repoId: string;
|
||||
}
|
||||
|
||||
export interface GitHubCommentsDialogResult {
|
||||
export interface PrCommentsDialogResult {
|
||||
comments: UnifiedPrComment[];
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ function getCommentId(comment: UnifiedPrComment): string {
|
||||
: comment.id.toString();
|
||||
}
|
||||
|
||||
const GitHubCommentsDialogImpl = NiceModal.create<GitHubCommentsDialogProps>(
|
||||
const PrCommentsDialogImpl = NiceModal.create<PrCommentsDialogProps>(
|
||||
({ attemptId, repoId }) => {
|
||||
const { t } = useTranslation(['tasks', 'common']);
|
||||
const modal = useModal();
|
||||
@@ -109,7 +109,7 @@ const GitHubCommentsDialogImpl = NiceModal.create<GitHubCommentsDialogProps>(
|
||||
<DialogHeader className="px-4 py-3 border-b">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
{t('tasks:githubComments.dialog.title')}
|
||||
{t('tasks:prComments.dialog.title')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -126,13 +126,13 @@ const GitHubCommentsDialogImpl = NiceModal.create<GitHubCommentsDialogProps>(
|
||||
</div>
|
||||
) : comments.length === 0 ? (
|
||||
<p className="text-center text-muted-foreground py-8">
|
||||
{t('tasks:githubComments.dialog.noComments')}
|
||||
{t('tasks:prComments.dialog.noComments')}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t('tasks:githubComments.dialog.selectedCount', {
|
||||
{t('tasks:prComments.dialog.selectedCount', {
|
||||
selected: selectedIds.size,
|
||||
total: comments.length,
|
||||
})}
|
||||
@@ -143,8 +143,8 @@ const GitHubCommentsDialogImpl = NiceModal.create<GitHubCommentsDialogProps>(
|
||||
onClick={isAllSelected ? deselectAll : selectAll}
|
||||
>
|
||||
{isAllSelected
|
||||
? t('tasks:githubComments.dialog.deselectAll')
|
||||
: t('tasks:githubComments.dialog.selectAll')}
|
||||
? t('tasks:prComments.dialog.deselectAll')
|
||||
: t('tasks:prComments.dialog.selectAll')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
@@ -160,7 +160,7 @@ const GitHubCommentsDialogImpl = NiceModal.create<GitHubCommentsDialogProps>(
|
||||
onCheckedChange={() => toggleSelection(id)}
|
||||
className="mt-3"
|
||||
/>
|
||||
<GitHubCommentCard
|
||||
<PrCommentCard
|
||||
author={comment.author}
|
||||
body={comment.body}
|
||||
createdAt={comment.created_at}
|
||||
@@ -201,7 +201,7 @@ const GitHubCommentsDialogImpl = NiceModal.create<GitHubCommentsDialogProps>(
|
||||
{t('common:buttons.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={selectedIds.size === 0}>
|
||||
{t('tasks:githubComments.dialog.add')}
|
||||
{t('tasks:prComments.dialog.add')}
|
||||
{selectedIds.size > 0 ? ` (${selectedIds.size})` : ''}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -219,17 +219,17 @@ function getErrorMessage(error: unknown): string {
|
||||
if (errorData?.type === 'no_pr_attached') {
|
||||
return 'No PR is attached to this task attempt. Create a PR first to see comments.';
|
||||
}
|
||||
if (errorData?.type === 'github_cli_not_installed') {
|
||||
return 'GitHub CLI is not installed. Please install it to fetch PR comments.';
|
||||
if (errorData?.type === 'cli_not_installed') {
|
||||
return 'CLI is not installed. Please install it to fetch PR comments.';
|
||||
}
|
||||
if (errorData?.type === 'github_cli_not_logged_in') {
|
||||
return 'GitHub CLI is not logged in. Please run "gh auth login" to authenticate.';
|
||||
if (errorData?.type === 'cli_not_logged_in') {
|
||||
return 'CLI is not logged in. Please authenticate to fetch PR comments.';
|
||||
}
|
||||
}
|
||||
return 'Failed to load PR comments. Please try again.';
|
||||
}
|
||||
|
||||
export const GitHubCommentsDialog = defineModal<
|
||||
GitHubCommentsDialogProps,
|
||||
GitHubCommentsDialogResult
|
||||
>(GitHubCommentsDialogImpl);
|
||||
export const PrCommentsDialog = defineModal<
|
||||
PrCommentsDialogProps,
|
||||
PrCommentsDialogResult
|
||||
>(PrCommentsDialogImpl);
|
||||
@@ -60,8 +60,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { queueApi } from '@/lib/api';
|
||||
import type { QueueStatus } from 'shared/types';
|
||||
import { imagesApi, attemptsApi } from '@/lib/api';
|
||||
import { GitHubCommentsDialog } from '@/components/dialogs/tasks/GitHubCommentsDialog';
|
||||
import type { NormalizedComment } from '@/components/ui/wysiwyg/nodes/github-comment-node';
|
||||
import { PrCommentsDialog } from '@/components/dialogs/tasks/PrCommentsDialog';
|
||||
import type { NormalizedComment } from '@/components/ui/wysiwyg/nodes/pr-comment-node';
|
||||
import type { Session } from 'shared/types';
|
||||
|
||||
interface TaskFollowUpSectionProps {
|
||||
@@ -574,13 +574,13 @@ export function TaskFollowUpSection({
|
||||
[handlePasteFiles]
|
||||
);
|
||||
|
||||
// Handler for GitHub comments insertion
|
||||
const handleGitHubCommentClick = useCallback(async () => {
|
||||
// Handler for PR comments insertion
|
||||
const handlePrCommentClick = useCallback(async () => {
|
||||
if (!workspaceId) return;
|
||||
const repoId = getSelectedRepoId();
|
||||
if (!repoId) return;
|
||||
|
||||
const result = await GitHubCommentsDialog.show({
|
||||
const result = await PrCommentsDialog.show({
|
||||
attemptId: workspaceId,
|
||||
repoId,
|
||||
});
|
||||
@@ -829,14 +829,14 @@ export function TaskFollowUpSection({
|
||||
<Paperclip className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
{/* GitHub Comments button */}
|
||||
{/* PR Comments button */}
|
||||
<Button
|
||||
onClick={handleGitHubCommentClick}
|
||||
onClick={handlePrCommentClick}
|
||||
disabled={!isEditable}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
title="Insert GitHub comment"
|
||||
aria-label="Insert GitHub comment"
|
||||
title="Insert PR comment"
|
||||
aria-label="Insert PR comment"
|
||||
>
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -2,16 +2,16 @@ import { MessageSquare, Code, ExternalLink } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface GitHubCommentCardProps {
|
||||
export interface PrCommentCardProps {
|
||||
author: string;
|
||||
body: string;
|
||||
createdAt: string;
|
||||
url: string;
|
||||
url?: string | null;
|
||||
// Optional review-specific fields
|
||||
commentType?: 'general' | 'review';
|
||||
path?: string;
|
||||
line?: number | null;
|
||||
diffHunk?: string;
|
||||
diffHunk?: string | null;
|
||||
/** Display variant: 'compact' for inline chip, 'full' for inline card, 'list' for block card */
|
||||
variant: 'compact' | 'full' | 'list';
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
@@ -71,7 +71,7 @@ function CompactCard({
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
className,
|
||||
}: GitHubCommentCardProps) {
|
||||
}: PrCommentCardProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const isReview = commentType === 'review';
|
||||
const Icon = isReview ? Code : MessageSquare;
|
||||
@@ -87,7 +87,7 @@ function CompactCard({
|
||||
onDoubleClick={onDoubleClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title={`@${author}: ${body}\n\n${t('githubComments.card.tooltip')}`}
|
||||
title={`@${author}: ${body}\n\n${t('prComments.card.tooltip')}`}
|
||||
>
|
||||
<Icon className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-xs font-medium flex-shrink-0">@{author}</span>
|
||||
@@ -113,7 +113,7 @@ function FullCard({
|
||||
onClick,
|
||||
variant,
|
||||
className,
|
||||
}: GitHubCommentCardProps) {
|
||||
}: PrCommentCardProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const isReview = commentType === 'review';
|
||||
const Icon = isReview ? Code : MessageSquare;
|
||||
@@ -136,7 +136,7 @@ function FullCard({
|
||||
<span className="font-medium text-sm">@{author}</span>
|
||||
{isReview && (
|
||||
<span className="text-xs text-muted-foreground bg-secondary px-1.5 py-0.5 rounded">
|
||||
{t('githubComments.card.review')}
|
||||
{t('prComments.card.review')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ function FullCard({
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}}
|
||||
className="hover:text-foreground transition-colors"
|
||||
aria-label="Open in GitHub"
|
||||
aria-label="Open in browser"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
@@ -178,11 +178,11 @@ function FullCard({
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHubCommentCard - Shared presentational component for GitHub PR comments
|
||||
* PrCommentCard - Shared presentational component for PR comments
|
||||
*
|
||||
* @param variant - 'compact' for inline chip, 'full' for inline card, 'list' for block card
|
||||
*/
|
||||
export function GitHubCommentCard(props: GitHubCommentCardProps) {
|
||||
export function PrCommentCard(props: PrCommentCardProps) {
|
||||
if (props.variant === 'compact') {
|
||||
return <CompactCard {...props} />;
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPl
|
||||
import { TRANSFORMERS, type Transformer } from '@lexical/markdown';
|
||||
import { ImageNode, IMAGE_TRANSFORMER } from './wysiwyg/nodes/image-node';
|
||||
import {
|
||||
GitHubCommentNode,
|
||||
GITHUB_COMMENT_TRANSFORMER,
|
||||
GITHUB_COMMENT_EXPORT_TRANSFORMER,
|
||||
} from './wysiwyg/nodes/github-comment-node';
|
||||
PrCommentNode,
|
||||
PR_COMMENT_TRANSFORMER,
|
||||
PR_COMMENT_EXPORT_TRANSFORMER,
|
||||
} from './wysiwyg/nodes/pr-comment-node';
|
||||
import { CODE_BLOCK_TRANSFORMER } from './wysiwyg/transformers/code-block-transformer';
|
||||
import { TABLE_TRANSFORMER } from './wysiwyg/transformers/table-transformer';
|
||||
import {
|
||||
@@ -161,7 +161,7 @@ function WYSIWYGEditor({
|
||||
CodeHighlightNode,
|
||||
LinkNode,
|
||||
ImageNode,
|
||||
GitHubCommentNode,
|
||||
PrCommentNode,
|
||||
TableNode,
|
||||
TableRowNode,
|
||||
TableCellNode,
|
||||
@@ -170,13 +170,13 @@ function WYSIWYGEditor({
|
||||
[]
|
||||
);
|
||||
|
||||
// Extended transformers with image, GitHub comment, and code block support (memoized to prevent unnecessary re-renders)
|
||||
// Extended transformers with image, PR comment, and code block support (memoized to prevent unnecessary re-renders)
|
||||
const extendedTransformers: Transformer[] = useMemo(
|
||||
() => [
|
||||
TABLE_TRANSFORMER,
|
||||
IMAGE_TRANSFORMER,
|
||||
GITHUB_COMMENT_EXPORT_TRANSFORMER, // Export transformer for DecoratorNode (must be before import transformer)
|
||||
GITHUB_COMMENT_TRANSFORMER, // Import transformer for fenced code block
|
||||
PR_COMMENT_EXPORT_TRANSFORMER, // Export transformer for DecoratorNode (must be before import transformer)
|
||||
PR_COMMENT_TRANSFORMER, // Import transformer for fenced code block
|
||||
CODE_BLOCK_TRANSFORMER,
|
||||
...TRANSFORMERS,
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { NodeKey, SerializedLexicalNode, Spread } from 'lexical';
|
||||
import { GitHubCommentCard } from '@/components/ui/github-comment-card';
|
||||
import { PrCommentCard } from '@/components/ui/pr-comment-card';
|
||||
import {
|
||||
createDecoratorNode,
|
||||
type DecoratorNodeConfig,
|
||||
@@ -17,19 +17,19 @@ export interface NormalizedComment {
|
||||
author: string;
|
||||
body: string;
|
||||
created_at: string;
|
||||
url: string;
|
||||
url?: string | null;
|
||||
// Review-specific (optional)
|
||||
path?: string;
|
||||
line?: number | null;
|
||||
diff_hunk?: string;
|
||||
diff_hunk?: string | null;
|
||||
}
|
||||
|
||||
export type SerializedGitHubCommentNode = Spread<
|
||||
export type SerializedPrCommentNode = Spread<
|
||||
NormalizedComment,
|
||||
SerializedLexicalNode
|
||||
>;
|
||||
|
||||
function GitHubCommentComponent({
|
||||
function PrCommentComponent({
|
||||
data,
|
||||
onDoubleClickEdit,
|
||||
}: {
|
||||
@@ -41,14 +41,16 @@ function GitHubCommentComponent({
|
||||
(event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Open GitHub URL in new tab
|
||||
window.open(data.url, '_blank', 'noopener,noreferrer');
|
||||
// Open URL in new tab if available
|
||||
if (data.url) {
|
||||
window.open(data.url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
},
|
||||
[data.url]
|
||||
);
|
||||
|
||||
return (
|
||||
<GitHubCommentCard
|
||||
<PrCommentCard
|
||||
author={data.author}
|
||||
body={data.body}
|
||||
createdAt={data.created_at}
|
||||
@@ -72,23 +74,22 @@ const config: DecoratorNodeConfig<NormalizedComment> = {
|
||||
serialize: (data) => JSON.stringify(data, null, 2),
|
||||
deserialize: (content) => JSON.parse(content),
|
||||
validate: (data) =>
|
||||
!!(data.id && data.comment_type && data.author && data.body && data.url),
|
||||
!!(data.id && data.comment_type && data.author && data.body),
|
||||
},
|
||||
component: GitHubCommentComponent,
|
||||
component: PrCommentComponent,
|
||||
exportDOM: (data) => {
|
||||
const span = document.createElement('span');
|
||||
span.setAttribute('data-github-comment-id', data.id);
|
||||
span.textContent = `GitHub comment by @${data.author}: ${data.body}`;
|
||||
span.setAttribute('data-pr-comment-id', data.id);
|
||||
span.textContent = `PR comment by @${data.author}: ${data.body}`;
|
||||
return span;
|
||||
},
|
||||
};
|
||||
|
||||
const result = createDecoratorNode(config);
|
||||
|
||||
export const GitHubCommentNode = result.Node;
|
||||
export type GitHubCommentNodeInstance =
|
||||
GeneratedDecoratorNode<NormalizedComment>;
|
||||
export const $createGitHubCommentNode = result.createNode;
|
||||
export const $isGitHubCommentNode = result.isNode;
|
||||
export const [GITHUB_COMMENT_EXPORT_TRANSFORMER, GITHUB_COMMENT_TRANSFORMER] =
|
||||
export const PrCommentNode = result.Node;
|
||||
export type PrCommentNodeInstance = GeneratedDecoratorNode<NormalizedComment>;
|
||||
export const $createPrCommentNode = result.createNode;
|
||||
export const $isPrCommentNode = result.isNode;
|
||||
export const [PR_COMMENT_EXPORT_TRANSFORMER, PR_COMMENT_TRANSFORMER] =
|
||||
result.transformers;
|
||||
@@ -496,10 +496,10 @@
|
||||
"description": "You need to sign in before you can share tasks. We will redirect you to the sign-in page.",
|
||||
"action": "Go to sign in"
|
||||
},
|
||||
"githubRequired": {
|
||||
"title": "Connect GitHub",
|
||||
"description": "Connect your GitHub account to share tasks. This project must have a remote repository on GitHub.",
|
||||
"action": "Connect GitHub"
|
||||
"gitProviderRequired": {
|
||||
"title": "Connect Git Provider",
|
||||
"description": "Connect your Git provider account to share tasks. This project must have a remote repository.",
|
||||
"action": "Connect"
|
||||
},
|
||||
"linkProjectRequired": {
|
||||
"description": "Link this project to an organization before sharing tasks.",
|
||||
@@ -513,8 +513,8 @@
|
||||
"closeButton": "Close"
|
||||
},
|
||||
"createPrDialog": {
|
||||
"title": "Create GitHub Pull Request",
|
||||
"description": "Create a pull request for this task attempt on GitHub.",
|
||||
"title": "Create Pull Request",
|
||||
"description": "Create a pull request for this task attempt.",
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "Enter PR title",
|
||||
"descriptionLabel": "Description (optional)",
|
||||
@@ -527,9 +527,9 @@
|
||||
"creating": "Creating...",
|
||||
"createButton": "Create PR",
|
||||
"errors": {
|
||||
"insufficientPermissions": "Insufficient permissions. Please ensure the GitHub CLI has the necessary permissions.",
|
||||
"insufficientPermissions": "Insufficient permissions. Please ensure the CLI has the necessary permissions.",
|
||||
"repoNotFoundOrNoAccess": "Repository not found or no access. Please check your repository access and ensure you are authenticated.",
|
||||
"failedToCreate": "Failed to create GitHub PR",
|
||||
"failedToCreate": "Failed to create PR",
|
||||
"gitCliNotLoggedIn": "Git is not authenticated. Run \"gh auth login\" (or configure Git credentials) and try again.",
|
||||
"gitCliNotInstalled": "Git CLI is not installed. Install Git to create a PR.",
|
||||
"targetBranchNotFound": "Target branch '{{branch}}' does not exist on remote. Please ensure the branch exists before creating a pull request."
|
||||
@@ -569,9 +569,9 @@
|
||||
"finish": "Finish"
|
||||
}
|
||||
},
|
||||
"githubComments": {
|
||||
"prComments": {
|
||||
"dialog": {
|
||||
"title": "Select GitHub Comments",
|
||||
"title": "Select PR Comments",
|
||||
"noComments": "No comments found on this PR",
|
||||
"selectAll": "Select All",
|
||||
"deselectAll": "Deselect All",
|
||||
@@ -580,7 +580,7 @@
|
||||
},
|
||||
"card": {
|
||||
"review": "Review",
|
||||
"tooltip": "Click to view on GitHub, double-click to edit"
|
||||
"tooltip": "Click to view, double-click to edit"
|
||||
}
|
||||
},
|
||||
"taskFormDialog": {
|
||||
|
||||
@@ -97,10 +97,10 @@
|
||||
"description": "Debes iniciar sesión antes de poder compartir tareas. Te redirigiremos a la página de inicio de sesión.",
|
||||
"action": "Ir a iniciar sesión"
|
||||
},
|
||||
"githubRequired": {
|
||||
"title": "Conecta GitHub",
|
||||
"description": "Conecta tu cuenta de GitHub para que podamos publicar tareas compartidas por ti.",
|
||||
"action": "Conectar GitHub"
|
||||
"gitProviderRequired": {
|
||||
"title": "Conectar proveedor de Git",
|
||||
"description": "Conecta tu cuenta de proveedor de Git para compartir tareas. Este proyecto debe tener un repositorio remoto.",
|
||||
"action": "Conectar"
|
||||
},
|
||||
"linkProjectRequired": {
|
||||
"description": "Vincula este proyecto a una organización antes de compartir tareas.",
|
||||
@@ -114,8 +114,8 @@
|
||||
"closeButton": "Cerrar"
|
||||
},
|
||||
"createPrDialog": {
|
||||
"title": "Crear Pull Request de GitHub",
|
||||
"description": "Crea un pull request para este intento de tarea en GitHub.",
|
||||
"title": "Crear Pull Request",
|
||||
"description": "Crea un pull request para este intento de tarea.",
|
||||
"titleLabel": "Título",
|
||||
"titlePlaceholder": "Ingresar título del PR",
|
||||
"descriptionLabel": "Descripción (opcional)",
|
||||
@@ -128,9 +128,9 @@
|
||||
"creating": "Creando...",
|
||||
"createButton": "Crear PR",
|
||||
"errors": {
|
||||
"insufficientPermissions": "Permisos insuficientes. Por favor asegúrate de que la CLI de GitHub tenga los permisos necesarios.",
|
||||
"insufficientPermissions": "Permisos insuficientes. Por favor asegúrate de que la CLI tenga los permisos necesarios.",
|
||||
"repoNotFoundOrNoAccess": "Repositorio no encontrado o sin acceso. Por favor verifica el acceso al repositorio y asegúrate de estar autenticado.",
|
||||
"failedToCreate": "Error al crear PR de GitHub",
|
||||
"failedToCreate": "Error al crear PR",
|
||||
"gitCliNotLoggedIn": "Git no está autenticado. Ejecuta \"gh auth login\" (o configura las credenciales de Git) e inténtalo de nuevo.",
|
||||
"gitCliNotInstalled": "Git CLI no está instalado. Instala Git para crear una PR.",
|
||||
"targetBranchNotFound": "La rama objetivo '{{branch}}' no existe en el remoto. Por favor, asegúrese de que la rama exista antes de crear una solicitud de extracción."
|
||||
@@ -503,9 +503,9 @@
|
||||
"finish": "Finalizar"
|
||||
}
|
||||
},
|
||||
"githubComments": {
|
||||
"prComments": {
|
||||
"dialog": {
|
||||
"title": "Seleccionar comentarios de GitHub",
|
||||
"title": "Seleccionar comentarios del PR",
|
||||
"noComments": "No se encontraron comentarios en este PR",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"deselectAll": "Deseleccionar todo",
|
||||
@@ -514,7 +514,7 @@
|
||||
},
|
||||
"card": {
|
||||
"review": "Revisión",
|
||||
"tooltip": "Clic para ver en GitHub, doble clic para editar"
|
||||
"tooltip": "Clic para ver, doble clic para editar"
|
||||
}
|
||||
},
|
||||
"taskFormDialog": {
|
||||
|
||||
@@ -97,10 +97,10 @@
|
||||
"description": "タスクを共有する前にサインインが必要です。サインインページへリダイレクトします。",
|
||||
"action": "サインインへ移動"
|
||||
},
|
||||
"githubRequired": {
|
||||
"title": "GitHub を接続",
|
||||
"description": "共有タスクを公開できるよう、GitHub アカウントを接続してください。",
|
||||
"action": "GitHub を接続"
|
||||
"gitProviderRequired": {
|
||||
"title": "Git プロバイダーを接続",
|
||||
"description": "タスクを共有するには、Git プロバイダーアカウントを接続してください。このプロジェクトにはリモートリポジトリが必要です。",
|
||||
"action": "接続"
|
||||
},
|
||||
"linkProjectRequired": {
|
||||
"description": "タスクを共有する前に、このプロジェクトを組織にリンクしてください。",
|
||||
@@ -114,8 +114,8 @@
|
||||
"closeButton": "閉じる"
|
||||
},
|
||||
"createPrDialog": {
|
||||
"title": "GitHub プルリクエストを作成",
|
||||
"description": "このタスク試行のプルリクエストをGitHubで作成します。",
|
||||
"title": "プルリクエストを作成",
|
||||
"description": "このタスク試行のプルリクエストを作成します。",
|
||||
"titleLabel": "タイトル",
|
||||
"titlePlaceholder": "PRタイトルを入力",
|
||||
"descriptionLabel": "説明 (オプション)",
|
||||
@@ -128,9 +128,9 @@
|
||||
"creating": "作成中...",
|
||||
"createButton": "PRを作成",
|
||||
"errors": {
|
||||
"insufficientPermissions": "権限が不足しています。GitHub CLIに必要な権限があることを確認してください。",
|
||||
"insufficientPermissions": "権限が不足しています。CLIに必要な権限があることを確認してください。",
|
||||
"repoNotFoundOrNoAccess": "リポジトリが見つからないか、アクセス権がありません。リポジトリへのアクセス権を確認し、認証されていることを確認してください。",
|
||||
"failedToCreate": "GitHub PRの作成に失敗しました",
|
||||
"failedToCreate": "PRの作成に失敗しました",
|
||||
"gitCliNotLoggedIn": "Gitが認証されていません。\"gh auth login\" を実行するかGitの認証情報を設定してから再試行してください。",
|
||||
"gitCliNotInstalled": "Git CLIがインストールされていません。PRを作成するにはGitをインストールしてください。",
|
||||
"targetBranchNotFound": "ターゲットブランチ '{{branch}}' がリモートに存在しません。プルリクエストを作成する前にブランチが存在することを確認してください。"
|
||||
@@ -503,9 +503,9 @@
|
||||
"finish": "完了"
|
||||
}
|
||||
},
|
||||
"githubComments": {
|
||||
"prComments": {
|
||||
"dialog": {
|
||||
"title": "GitHubコメントを選択",
|
||||
"title": "PRコメントを選択",
|
||||
"noComments": "このPRにコメントはありません",
|
||||
"selectAll": "すべて選択",
|
||||
"deselectAll": "すべて選択解除",
|
||||
@@ -514,7 +514,7 @@
|
||||
},
|
||||
"card": {
|
||||
"review": "レビュー",
|
||||
"tooltip": "クリックでGitHubで表示、ダブルクリックで編集"
|
||||
"tooltip": "クリックで表示、ダブルクリックで編集"
|
||||
}
|
||||
},
|
||||
"taskFormDialog": {
|
||||
|
||||
@@ -97,10 +97,10 @@
|
||||
"description": "작업을 공유하려면 로그인해야 합니다. 로그인 페이지로 이동합니다.",
|
||||
"action": "로그인으로 이동"
|
||||
},
|
||||
"githubRequired": {
|
||||
"title": "GitHub 연결",
|
||||
"description": "공유 작업을 게시하려면 GitHub 계정을 연결하세요.",
|
||||
"action": "GitHub 연결"
|
||||
"gitProviderRequired": {
|
||||
"title": "Git 제공자 연결",
|
||||
"description": "작업을 공유하려면 Git 제공자 계정을 연결하세요. 이 프로젝트에는 원격 저장소가 있어야 합니다.",
|
||||
"action": "연결"
|
||||
},
|
||||
"linkProjectRequired": {
|
||||
"description": "작업을 공유하기 전에 이 프로젝트를 조직에 연결하세요.",
|
||||
@@ -114,8 +114,8 @@
|
||||
"closeButton": "닫기"
|
||||
},
|
||||
"createPrDialog": {
|
||||
"title": "GitHub Pull Request 생성",
|
||||
"description": "이 작업 시도에 대한 Pull Request를 GitHub에서 생성합니다.",
|
||||
"title": "Pull Request 생성",
|
||||
"description": "이 작업 시도에 대한 Pull Request를 생성합니다.",
|
||||
"titleLabel": "제목",
|
||||
"titlePlaceholder": "PR 제목 입력",
|
||||
"descriptionLabel": "설명 (선택사항)",
|
||||
@@ -128,9 +128,9 @@
|
||||
"creating": "생성 중...",
|
||||
"createButton": "PR 생성",
|
||||
"errors": {
|
||||
"insufficientPermissions": "권한이 부족합니다. GitHub CLI에 필요한 권한이 있는지 확인하세요.",
|
||||
"insufficientPermissions": "권한이 부족합니다. CLI에 필요한 권한이 있는지 확인하세요.",
|
||||
"repoNotFoundOrNoAccess": "저장소를 찾을 수 없거나 액세스 권한이 없습니다. 저장소 액세스를 확인하고 인증되었는지 확인하세요.",
|
||||
"failedToCreate": "GitHub PR 생성에 실패했습니다",
|
||||
"failedToCreate": "PR 생성에 실패했습니다",
|
||||
"gitCliNotLoggedIn": "Git이 인증되지 않았습니다. \"gh auth login\"을 실행하거나 Git 자격 증명을 설정한 후 다시 시도하세요.",
|
||||
"gitCliNotInstalled": "Git CLI가 설치되어 있지 않습니다. PR을 생성하려면 Git을 설치하세요.",
|
||||
"targetBranchNotFound": "대상 브랜치 '{{branch}}'이(가) 원격에 존재하지 않습니다. 풀 리퀘스트를 생성하기 전에 브랜치가 존재하는지 확인하세요."
|
||||
@@ -503,9 +503,9 @@
|
||||
"finish": "완료"
|
||||
}
|
||||
},
|
||||
"githubComments": {
|
||||
"prComments": {
|
||||
"dialog": {
|
||||
"title": "GitHub 댓글 선택",
|
||||
"title": "PR 댓글 선택",
|
||||
"noComments": "이 PR에 댓글이 없습니다",
|
||||
"selectAll": "모두 선택",
|
||||
"deselectAll": "모두 선택 해제",
|
||||
@@ -514,7 +514,7 @@
|
||||
},
|
||||
"card": {
|
||||
"review": "리뷰",
|
||||
"tooltip": "클릭하여 GitHub에서 보기, 더블 클릭하여 편집"
|
||||
"tooltip": "클릭하여 보기, 더블 클릭하여 편집"
|
||||
}
|
||||
},
|
||||
"taskFormDialog": {
|
||||
|
||||
@@ -430,10 +430,10 @@
|
||||
"description": "您需要登录才能共享任务。我们将重定向您到登录页面。",
|
||||
"action": "前往登录"
|
||||
},
|
||||
"githubRequired": {
|
||||
"title": "连接 GitHub",
|
||||
"description": "连接您的 GitHub 账户以共享任务。此项目必须在 GitHub 上有远程仓库。",
|
||||
"action": "连接 GitHub"
|
||||
"gitProviderRequired": {
|
||||
"title": "连接 Git 提供商",
|
||||
"description": "连接您的 Git 提供商账户以共享任务。此项目必须有远程仓库。",
|
||||
"action": "连接"
|
||||
},
|
||||
"linkProjectRequired": {
|
||||
"description": "在共享任务之前,将此项目链接到组织。",
|
||||
@@ -447,8 +447,8 @@
|
||||
"closeButton": "关闭"
|
||||
},
|
||||
"createPrDialog": {
|
||||
"title": "创建 GitHub 拉取请求",
|
||||
"description": "在 GitHub 上为此任务尝试创建拉取请求。",
|
||||
"title": "创建拉取请求",
|
||||
"description": "为此任务尝试创建拉取请求。",
|
||||
"titleLabel": "标题",
|
||||
"titlePlaceholder": "输入 PR 标题",
|
||||
"descriptionLabel": "描述(可选)",
|
||||
@@ -461,9 +461,9 @@
|
||||
"creating": "创建中...",
|
||||
"createButton": "创建 PR",
|
||||
"errors": {
|
||||
"insufficientPermissions": "权限不足。请确保 GitHub CLI 具有必要的权限。",
|
||||
"insufficientPermissions": "权限不足。请确保 CLI 具有必要的权限。",
|
||||
"repoNotFoundOrNoAccess": "未找到仓库或无访问权限。请检查您的仓库访问权限并确保您已通过身份验证。",
|
||||
"failedToCreate": "创建 GitHub PR 失败",
|
||||
"failedToCreate": "创建 PR 失败",
|
||||
"gitCliNotLoggedIn": "Git 未通过身份验证。运行 gh auth login(或配置 Git 凭据)然后重试。",
|
||||
"gitCliNotInstalled": "未安装 Git CLI。安装 Git 以创建 PR。",
|
||||
"targetBranchNotFound": "远程上不存在目标分支 {{branch}}。请在创建拉取请求之前确保该分支存在。"
|
||||
@@ -503,9 +503,9 @@
|
||||
"finish": "完成"
|
||||
}
|
||||
},
|
||||
"githubComments": {
|
||||
"prComments": {
|
||||
"dialog": {
|
||||
"title": "选择 GitHub 评论",
|
||||
"title": "选择 PR 评论",
|
||||
"noComments": "此 PR 未找到评论",
|
||||
"selectAll": "全选",
|
||||
"deselectAll": "取消全选",
|
||||
@@ -514,7 +514,7 @@
|
||||
},
|
||||
"card": {
|
||||
"review": "审查",
|
||||
"tooltip": "点击在 GitHub 上查看,双击编辑"
|
||||
"tooltip": "点击查看,双击编辑"
|
||||
}
|
||||
},
|
||||
"taskFormDialog": {
|
||||
|
||||
@@ -430,10 +430,10 @@
|
||||
"description": "您需要登入才能分享任務。我們會將您導向登入頁面。",
|
||||
"action": "前往登入"
|
||||
},
|
||||
"githubRequired": {
|
||||
"title": "連結 GitHub",
|
||||
"description": "連結您的 GitHub 帳號以分享任務。此專案必須在 GitHub 上有遠端儲存庫。",
|
||||
"action": "連結 GitHub"
|
||||
"gitProviderRequired": {
|
||||
"title": "連結 Git 提供者",
|
||||
"description": "連結您的 Git 提供者帳號以分享任務。此專案必須有遠端儲存庫。",
|
||||
"action": "連結"
|
||||
},
|
||||
"linkProjectRequired": {
|
||||
"description": "分享任務前,請先將此專案連結到組織。",
|
||||
@@ -447,8 +447,8 @@
|
||||
"closeButton": "關閉"
|
||||
},
|
||||
"createPrDialog": {
|
||||
"title": "建立 GitHub PR",
|
||||
"description": "在 GitHub 上為此任務嘗試建立 PR。",
|
||||
"title": "建立 PR",
|
||||
"description": "為此任務嘗試建立 PR。",
|
||||
"titleLabel": "標題",
|
||||
"titlePlaceholder": "輸入 PR 標題",
|
||||
"descriptionLabel": "描述(選填)",
|
||||
@@ -461,9 +461,9 @@
|
||||
"creating": "建立中...",
|
||||
"createButton": "建立 PR",
|
||||
"errors": {
|
||||
"insufficientPermissions": "權限不足。請確認 GitHub CLI 具有必要權限。",
|
||||
"insufficientPermissions": "權限不足。請確認 CLI 具有必要權限。",
|
||||
"repoNotFoundOrNoAccess": "找不到儲存庫或沒有存取權。請檢查儲存庫權限並確保已完成驗證。",
|
||||
"failedToCreate": "建立 GitHub PR 失敗",
|
||||
"failedToCreate": "建立 PR 失敗",
|
||||
"gitCliNotLoggedIn": "Git 尚未驗證。請執行 gh auth login(或設定 Git 憑證)後重試。",
|
||||
"gitCliNotInstalled": "未安裝 Git CLI。請安裝 Git 以建立 PR。",
|
||||
"targetBranchNotFound": "遠端不存在目標分支 {{branch}}。建立 PR 前請確認該分支存在。"
|
||||
@@ -503,9 +503,9 @@
|
||||
"finish": "完成"
|
||||
}
|
||||
},
|
||||
"githubComments": {
|
||||
"prComments": {
|
||||
"dialog": {
|
||||
"title": "選擇 GitHub 評論",
|
||||
"title": "選擇 PR 評論",
|
||||
"noComments": "此 PR 沒有評論",
|
||||
"selectAll": "全選",
|
||||
"deselectAll": "取消全選",
|
||||
@@ -514,7 +514,7 @@
|
||||
},
|
||||
"card": {
|
||||
"review": "審查",
|
||||
"tooltip": "點擊在 GitHub 上查看,雙擊編輯"
|
||||
"tooltip": "點擊查看,雙擊編輯"
|
||||
}
|
||||
},
|
||||
"taskFormDialog": {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Config,
|
||||
CreateFollowUpAttempt,
|
||||
EditorType,
|
||||
CreateGitHubPrRequest,
|
||||
CreatePrApiRequest,
|
||||
CreateTask,
|
||||
CreateAndStartTaskRequest,
|
||||
CreateTaskAttemptBody,
|
||||
@@ -71,7 +71,7 @@ import {
|
||||
ListInvitationsResponse,
|
||||
OpenEditorResponse,
|
||||
OpenEditorRequest,
|
||||
CreatePrError,
|
||||
PrError,
|
||||
Scratch,
|
||||
ScratchType,
|
||||
CreateScratch,
|
||||
@@ -718,13 +718,13 @@ export const attemptsApi = {
|
||||
|
||||
createPR: async (
|
||||
attemptId: string,
|
||||
data: CreateGitHubPrRequest
|
||||
): Promise<Result<string, CreatePrError>> => {
|
||||
data: CreatePrApiRequest
|
||||
): Promise<Result<string, PrError>> => {
|
||||
const response = await makeRequest(`/api/task-attempts/${attemptId}/pr`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return handleApiResponseAsResult<string, CreatePrError>(response);
|
||||
return handleApiResponseAsResult<string, PrError>(response);
|
||||
},
|
||||
|
||||
startDevServer: async (attemptId: string): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user