diff --git a/docs/labels-and-capabilities.md b/docs/labels-and-capabilities.md index bd48c953d..1c03e96b4 100644 --- a/docs/labels-and-capabilities.md +++ b/docs/labels-and-capabilities.md @@ -332,7 +332,7 @@ or a contract-free mix of substrates (e.g. `tools/spec-inventory` is | [`tools/egress-gateway`](../tools/egress-gateway/) | `substrate:sandbox` | Egress-allowlist forward proxy (proxy.py plugin); host-level egress chokepoint — defence-in-depth for RFC-AI-0003 §4.4 | | [`tools/container-gateway`](../tools/container-gateway/) | `substrate:sandbox` | Per-project policy proxy for the podman / docker API; label-scoped, mount- and privilege-checked container access from inside the sandbox | | [`tools/forwarder-relay`](../tools/forwarder-relay/) | `contract:report-relay` | Adapter contract for inbound-relay backends (ASF Security relay, huntr.com, HackerOne triagers). Pure interface spec; adapters declare detection + credit-extraction + reporter-addressing rules. | -| [`tools/bitbucket`](../tools/bitbucket/) | `contract:change-request` + `contract:tracker` | Coverage: `partial`. Bitbucket Cloud and Bitbucket Data Center bridge foundation for repository metadata context, branch restriction context for PR-management decisions, pull-request discovery/fetching, read-only commit fetching, read-only diff fetching, comments-only discussion fetching, read-only review-state fetching, Cloud-only pull-request task listing/fetching, read-only merge-check context fetching, and read-only status fetching, plus narrowly scoped pull-request comment creation on Cloud and Data Center and Cloud-only approve/unapprove actions. Native Bitbucket issue operations are not implemented: Atlassian removed the native Bitbucket Cloud Issue Tracker API on 2026-08-20 and Data Center native issues were never supported, so the historical issue CLI surface fails closed before any request. Data Center `pr jira-issues ` provides read-only discovery of Jira keys associated with pull-request commits and hands them to the Jira bridge (`tools/jira`) for tracker operations. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. Broader linked Jira coverage and the remaining pull-request coverage are incomplete. | +| [`tools/bitbucket`](../tools/bitbucket/) | `contract:change-request` + `contract:tracker` | Coverage: `partial`. Bitbucket Cloud and Bitbucket Data Center bridge foundation for repository metadata context, branch restriction context for PR-management decisions, pull-request discovery/fetching, read-only commit fetching, read-only diff fetching, comments-only discussion fetching, read-only review-state fetching, Cloud-only pull-request task listing/fetching, read-only merge-check context fetching, and read-only status fetching, plus narrowly scoped pull-request comment creation, approve/unapprove, and request-changes/remove-request-changes actions on Cloud and Data Center. Native Bitbucket issue operations are not implemented: Atlassian removed the native Bitbucket Cloud Issue Tracker API on 2026-08-20 and Data Center native issues were never supported, so the historical issue CLI surface fails closed before any request. Data Center `pr jira-issues ` provides read-only discovery of Jira keys associated with pull-request commits and hands them to the Jira bridge (`tools/jira`) for tracker operations. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. Broader linked Jira coverage and the remaining pull-request coverage are incomplete. | | [`tools/forgejo`](../tools/forgejo/) | `contract:tracker` + `contract:source-control` + `contract:change-request` | Coverage: `partial`. Forgejo / Gitea REST API and `tea` CLI forge bridge foundation for issue listing/fetching, confirmed issue creation, body edits, comments, labels, and milestones under `contract:tracker`, git-backed branch/commit/push operations under `contract:source-control`, and pull-request creation via REST API / compare URL and label edits under `contract:change-request`. Project boards are unsupported (`no-op`) due to absence of REST card/column endpoints. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. | | [`tools/fossil`](../tools/fossil/) | `contract:tracker` + `contract:source-control` | Fossil SCM forge bridge: integrates local SQLite-backed ticket tracking, wiki, and forum reads with the version-control shim | | [`tools/github`](../tools/github/) | `contract:tracker` + `contract:source-control` + `contract:change-request` + `contract:people` | GitHub REST / GraphQL tracker substrate (called by every lifecycle phase) plus the Git source-control binding documented in [`source-control.md`](../tools/github/source-control.md) (runnable backend in [`tools/vcs`](../tools/vcs/)), the pull-request review/merge gate (`change-request`; the ASF default backend, alongside `tools/jira-patch/` and `tools/mail-patch/` for SVN-first projects), and account profiles / collaborators / team membership (`people`) | diff --git a/tools/bitbucket/README.md b/tools/bitbucket/README.md index aca9b15c5..682dc85de 100644 --- a/tools/bitbucket/README.md +++ b/tools/bitbucket/README.md @@ -80,10 +80,10 @@ Implemented commands and compatibility stubs: - `magpie-bitbucket pr comment --body-file ` (Cloud and Data Center write) - `magpie-bitbucket pr reviews ` - `magpie-bitbucket pr review --verdict {comment,approve,request-changes} --body-file ` (Cloud-only write) -- `magpie-bitbucket pr approve ` (Cloud-only write) -- `magpie-bitbucket pr unapprove ` (Cloud-only write) -- `magpie-bitbucket pr request-changes ` (Cloud-only write) -- `magpie-bitbucket pr remove-request-changes ` (Cloud-only write) +- `magpie-bitbucket pr approve ` (Cloud and Data Center write) +- `magpie-bitbucket pr unapprove ` (Cloud and Data Center write) +- `magpie-bitbucket pr request-changes ` (Cloud and Data Center write) +- `magpie-bitbucket pr remove-request-changes ` (Cloud and Data Center write) - `magpie-bitbucket pr decline ` (Cloud-only write) - `magpie-bitbucket pr reject --reason-file ` (Cloud-only write) - `magpie-bitbucket pr merge --strategy {merge,squash,rebase} --expected-source-commit ` (Cloud-only write) @@ -105,9 +105,10 @@ activity where exposed by the configured Bitbucket backend. Write coverage is intentionally narrow. The bridge supports confirmed top-level pull-request comment creation on Bitbucket Cloud and Data Center. -Additional confirmed Cloud-only writes cover pull-request approve/unapprove, -request-changes/remove-request-changes, decline, backend-neutral reject, and -merge actions after the calling skill has obtained explicit user confirmation. Native Bitbucket Cloud issue writes +Additional confirmed Cloud and Data Center writes cover pull-request +approve/unapprove and request-changes/remove-request-changes. Cloud-only writes +cover decline, backend-neutral reject, and merge actions after the calling +skill has obtained explicit user confirmation. Native Bitbucket Cloud issue writes are unavailable because Atlassian removed the native Issue Tracker API. Other writes, such as editing/deleting pull-request comments, changing branches, or triggering builds, remain out of scope and should be added @@ -123,7 +124,7 @@ separately with narrow command surfaces and maintainer review. ## Features -This implementation primarily covers read-only operations, plus narrowly scoped Cloud write operations: +This implementation primarily covers read-only operations, plus narrowly scoped write operations: 1. **Authentication preflight:** verify the configured Bitbucket backend and credentials can reach the selected repository. 2. **Repository metadata:** fetch normalized repository details from Bitbucket Cloud or Data Center. @@ -136,8 +137,8 @@ This implementation primarily covers read-only operations, plus narrowly scoped 9. **Pull-request diff fetch:** fetch the pull request unified diff as normalized read-only output. 10. **Pull-request discussion fetch:** fetch a comments-only pull request discussion subset as normalized read-only output. 11. **Pull-request review-state fetch:** fetch reviewers, approvals, change-request signals, pending review requests, and normalized review activity. -12. **Cloud pull-request approve:** approve one pull request as the authenticated user after explicit caller-side confirmation. -13. **Cloud pull-request unapprove:** withdraw the authenticated user's approval after explicit caller-side confirmation. +12. **Pull-request approve:** approve one Cloud or Data Center pull request as the authenticated user after explicit caller-side confirmation. +13. **Pull-request unapprove:** withdraw the authenticated user's Cloud or Data Center pull-request approval after explicit caller-side confirmation. 14. **Pull-request tasks fetch:** list Bitbucket Cloud pull-request tasks and fetch one task as partial read-only change-request context. 15. **Pull-request merge-check context fetch:** fetch known read-only mergeability, conflict, status-check, and review blocker context while preserving unknown values where the backend does not expose a clear signal. 16. **Pull-request status fetch:** fetch build/status checks for the pull request as normalized read-only output. @@ -162,8 +163,8 @@ surface: | Change requests | `get_discussion` / `pr discussion ` | Partial read-only | Fetches a comments-only discussion subset with pagination. Participants beyond comment authors and unresolved-thread accounting remain incomplete. | | Change requests | `pr comment --body-file ` | Partial write | Creates one top-level Bitbucket Cloud or Data Center pull-request comment from a caller-supplied body file after explicit caller-side confirmation. | | Change requests | `reviews` supplement / `pr reviews ` | Partial read-only | Fetches reviewers, approvals, change-request signals, pending review requests, normalized review events, and an aggregate review decision. This does not post reviews or mutate PR state. | -| Change requests | `pr approve ` / `pr unapprove ` | Partial write, Cloud only | Approves or withdraws the authenticated user's approval after explicit caller-side confirmation. Data Center approval writes remain unsupported by these commands. For a review body together with a verdict, use `pr review` (the `post_review` contract). | -| Change requests | `pr request-changes ` / `pr remove-request-changes ` | Partial write, Cloud only | Requests changes or removes the authenticated user's change request after explicit caller-side confirmation. Data Center change-request writes remain unsupported by these commands. For a review body together with a verdict, use `pr review` (the `post_review` contract). | +| Change requests | `pr approve ` / `pr unapprove ` | Partial write | Approves or withdraws the authenticated user's approval on Bitbucket Cloud or Data Center after explicit caller-side confirmation. For a review body together with a verdict, use `pr review` (the `post_review` contract), which remains Cloud-only. | +| Change requests | `pr request-changes ` / `pr remove-request-changes ` | Partial write | Requests changes or removes the authenticated user's change request on Bitbucket Cloud or Data Center after explicit caller-side confirmation. Data Center maps these states to `NEEDS_WORK` and `UNAPPROVED`. For a review body together with a verdict, use `pr review` (the `post_review` contract), which remains Cloud-only. | | Change requests | `pr decline ` | Partial write, Cloud only | Declines one Bitbucket Cloud pull request after explicit caller-side confirmation. Data Center decline writes remain unsupported by this command. | | Change requests | `merge_checks` supplement / `pr merge-checks ` | Partial read-only | Fetches known read-only merge-check context, including Data Center merge-test results, reported mergeability/conflict fields, status checks, review decision, and normalized blockers. Unknown backend signals remain unknown. This does not merge or mutate PR state. | | Change requests | `post_review` / `pr review --verdict {comment,approve,request-changes} --body-file ` | Partial write, Cloud only | Implements the backend-neutral review contract for Bitbucket Cloud after explicit caller-side confirmation. `comment` posts the review body only. `approve` and `request-changes` post the body first and then apply the corresponding Bitbucket verdict. If the second mutation fails after the body was posted, the command fails with an explicit partial-outcome warning so callers inspect the PR before retrying. Data Center post-review writes remain unsupported. | @@ -265,7 +266,7 @@ injected by the caller as `BITBUCKET_TOKEN` / `BITBUCKET_CLOUD_USER`. | Variable | Required for | Description | |---|---|---| | `BITBUCKET_KIND` | all commands | `cloud` or `datacenter`. Defaults to `cloud`. | -| `BITBUCKET_TOKEN` | authenticated API calls | API token or personal access token accepted by the selected backend. Read-only PR/repository commands should use minimum read scopes. Pull-request comment writes on Cloud or Data Center, plus Cloud approve/unapprove, request-changes/remove-request-changes, decline, and merge writes require credentials permitted to write pull requests. `repo restrictions` needs elevated repository-admin scope on Bitbucket Cloud and may require `REPO_ADMIN` on Data Center. | +| `BITBUCKET_TOKEN` | authenticated API calls | API token or personal access token accepted by the selected backend. Read-only PR/repository commands should use minimum read scopes. Pull-request comment, approve/unapprove, and request-changes/remove-request-changes writes on Cloud or Data Center, plus Cloud decline and merge writes require credentials permitted to write pull requests. `repo restrictions` needs elevated repository-admin scope on Bitbucket Cloud and may require `REPO_ADMIN` on Data Center. | | `BITBUCKET_AUTH_SCHEME` | all commands | Authentication scheme. Defaults to `Basic` for Cloud and `Bearer` for Data Center. | | `BITBUCKET_CLOUD_USER` | Cloud Basic auth | Atlassian account email/user used with `BITBUCKET_TOKEN`. | | `BITBUCKET_WORKSPACE` | Cloud | Bitbucket Cloud workspace slug. | @@ -304,8 +305,8 @@ The bridge currently supports these narrow mutations (native Cloud issue writes were removed upstream and fail closed): - top-level pull-request comment creation on Cloud and Data Center -- Cloud-only pull-request approval and approval withdrawal -- Cloud-only pull-request change-request creation and removal +- pull-request approval and approval withdrawal on Cloud and Data Center +- pull-request change-request creation and removal on Cloud and Data Center - Cloud-only pull-request decline - Cloud-only pull-request merge with source-commit pinning @@ -313,7 +314,7 @@ For pull-request merge, the calling skill must run and inspect `pr merge-checks ` before invoking `pr merge`; the merge command itself does not independently enforce approval, build-status, or merge-check gates. -Bitbucket Data Center issue-comment, review-state, decline, and merge +Bitbucket Data Center issue-comment, post-review, decline, and merge writes remain unsupported by these commands. All other Bitbucket mutations remain out of scope for the current bridge and diff --git a/tools/bitbucket/src/magpie_bitbucket/datacenter.py b/tools/bitbucket/src/magpie_bitbucket/datacenter.py index fb06456eb..ed33800b7 100644 --- a/tools/bitbucket/src/magpie_bitbucket/datacenter.py +++ b/tools/bitbucket/src/magpie_bitbucket/datacenter.py @@ -30,6 +30,7 @@ post_json, quote_path, require, + write_request, ) @@ -357,14 +358,60 @@ def get_pull_request_status(config: BitbucketConfig, pull_request_id: str) -> di return combined +def _current_user_slug(config: BitbucketConfig) -> str: + """Return the authenticated Bitbucket Data Center user's URL-safe slug.""" + user = get_json(f"{_api_base(config)}/users", config) + slug = user.get("slug") + + if not isinstance(slug, str) or not slug.strip(): + raise BitbucketError("Bitbucket Data Center current-user response did not include a user slug") + + return quote_path(slug) + + +def _set_pull_request_participant_status( + config: BitbucketConfig, + pull_request_id: str, + status: str, +) -> dict[str, Any]: + """Set the authenticated user's pull-request participant status.""" + project_key = quote_path(require(config.project_key, "BITBUCKET_PROJECT_KEY")) + repo_slug = quote_path(require(config.repo_slug, "BITBUCKET_REPO_SLUG")) + pr_id = quote_path(pull_request_id) + user_slug = _current_user_slug(config) + + url = ( + f"{_api_base(config)}/projects/{project_key}/repos/{repo_slug}/" + f"pull-requests/{pr_id}/participants/{user_slug}" + ) + + participant = write_request( + url, + config, + method="PUT", + payload={"status": status}, + ) + + if participant is None: + raise BitbucketError( + "Bitbucket Data Center participant-status response did not include a participant" + ) + + return { + "pull_request_id": pull_request_id, + "participant": participant, + } + + def approve_pull_request( config: BitbucketConfig, pull_request_id: str, ) -> dict[str, Any]: - """Reject pull-request approval writes for Data Center for now.""" - _ = (config, pull_request_id) - raise BitbucketError( - "Bitbucket Data Center pull request approval writes are not supported by this command yet" + """Approve one Bitbucket Data Center pull request as the authenticated user.""" + return _set_pull_request_participant_status( + config, + pull_request_id, + "APPROVED", ) @@ -372,10 +419,11 @@ def unapprove_pull_request( config: BitbucketConfig, pull_request_id: str, ) -> dict[str, Any]: - """Reject pull-request unapproval writes for Data Center for now.""" - _ = (config, pull_request_id) - raise BitbucketError( - "Bitbucket Data Center pull request approval writes are not supported by this command yet" + """Remove the authenticated user's approval from a Data Center pull request.""" + return _set_pull_request_participant_status( + config, + pull_request_id, + "UNAPPROVED", ) @@ -383,10 +431,11 @@ def request_pull_request_changes( config: BitbucketConfig, pull_request_id: str, ) -> dict[str, Any]: - """Reject pull-request change-request writes for Data Center for now.""" - _ = (config, pull_request_id) - raise BitbucketError( - "Bitbucket Data Center pull request change-request writes are not supported by this command yet" + """Request changes on a Data Center pull request as the authenticated user.""" + return _set_pull_request_participant_status( + config, + pull_request_id, + "NEEDS_WORK", ) @@ -394,10 +443,11 @@ def remove_pull_request_changes_request( config: BitbucketConfig, pull_request_id: str, ) -> dict[str, Any]: - """Reject pull-request change-request removal writes for Data Center for now.""" - _ = (config, pull_request_id) - raise BitbucketError( - "Bitbucket Data Center pull request change-request writes are not supported by this command yet" + """Remove the authenticated user's Data Center change request.""" + return _set_pull_request_participant_status( + config, + pull_request_id, + "UNAPPROVED", ) diff --git a/tools/bitbucket/tests/test_bitbucket.py b/tools/bitbucket/tests/test_bitbucket.py index c0bf7393d..9050b8382 100644 --- a/tools/bitbucket/tests/test_bitbucket.py +++ b/tools/bitbucket/tests/test_bitbucket.py @@ -3033,24 +3033,80 @@ def test_cloud_unapprove_pull_request_deletes_without_body( assert result == {"pull_request_id": "7"} -def test_datacenter_approve_pull_request_unsupported( +@pytest.mark.parametrize( + ("operation", "expected_status"), + [ + (datacenter.approve_pull_request, "APPROVED"), + (datacenter.unapprove_pull_request, "UNAPPROVED"), + ], +) +@patch("magpie_bitbucket.client.urllib.request.build_opener") +def test_datacenter_pull_request_approval_status( + mock_build_opener: MagicMock, + operation: Any, + expected_status: str, datacenter_env: None, ) -> None: - with pytest.raises( - BitbucketError, - match="Data Center pull request approval writes are not supported", - ): - datacenter.approve_pull_request(load_config(), "9") + mock_opener( + mock_build_opener, + { + "slug": "alice", + "displayName": "Alice", + "name": "alice", + }, + { + "user": { + "slug": "alice", + "displayName": "Alice", + "name": "alice", + }, + "role": "REVIEWER", + "approved": expected_status == "APPROVED", + "status": expected_status, + }, + ) + + result = operation(load_config(), "9") + + requests = mock_build_opener.return_value.open.call_args_list + assert len(requests) == 2 + + user_request = requests[0].args[0] + status_request = requests[1].args[0] + assert user_request.full_url == "https://bitbucket.example.test/rest/api/1.0/users" + assert user_request.get_method() == "GET" + + assert status_request.full_url == ( + "https://bitbucket.example.test/rest/api/1.0/projects/MAGPIE/repos/magpie/" + "pull-requests/9/participants/alice" + ) + assert status_request.get_method() == "PUT" + assert json.loads(status_request.data.decode("utf-8")) == {"status": expected_status} + + assert result["pull_request_id"] == "9" + assert result["participant"]["status"] == expected_status -def test_datacenter_unapprove_pull_request_unsupported( + +@patch("magpie_bitbucket.client.urllib.request.build_opener") +def test_datacenter_pull_request_status_requires_current_user_slug( + mock_build_opener: MagicMock, datacenter_env: None, ) -> None: + mock_opener( + mock_build_opener, + { + "displayName": "Alice", + }, + ) + with pytest.raises( BitbucketError, - match="Data Center pull request approval writes are not supported", + match="current-user response did not include a user slug", ): - datacenter.unapprove_pull_request(load_config(), "9") + datacenter.approve_pull_request(load_config(), "9") + + assert len(mock_build_opener.return_value.open.call_args_list) == 1 def test_normalize_pull_request_approval_approved() -> None: @@ -3139,6 +3195,49 @@ def test_cli_pr_unapprove_cloud( assert output["approved"] is False +@pytest.mark.parametrize( + ("action", "function_name", "approved", "participant_status"), + [ + ("approve", "approve_pull_request", True, "APPROVED"), + ("unapprove", "unapprove_pull_request", False, "UNAPPROVED"), + ], +) +def test_cli_pr_approval_datacenter( + action: str, + function_name: str, + approved: bool, + participant_status: str, + datacenter_env: None, + capsys: pytest.CaptureFixture[str], +) -> None: + with patch(f"magpie_bitbucket.datacenter.{function_name}") as mock_operation: + mock_operation.return_value = { + "pull_request_id": "9", + "participant": { + "user": { + "slug": "alice", + "displayName": "Alice", + }, + "role": "REVIEWER", + "approved": approved, + "status": participant_status, + }, + } + + exit_code = main(["pr", action, "9"]) + + assert exit_code == 0 + mock_operation.assert_called_once() + args = mock_operation.call_args.args + assert args[1:] == ("9",) + + output = json.loads(capsys.readouterr().out) + assert output["backend"] == "bitbucket-datacenter" + assert output["operation"] == f"pull-request-{action}" + assert output["pull_request_id"] == "9" + assert output["approved"] is approved + + def test_write_request_rejects_redirect_for_delete( cloud_env: None, ) -> None: @@ -3211,24 +3310,107 @@ def test_cloud_remove_pull_request_changes_request_deletes_without_body( assert result == {"pull_request_id": "7"} -def test_datacenter_request_pull_request_changes_unsupported( +@pytest.mark.parametrize( + ("operation", "expected_status"), + [ + (datacenter.request_pull_request_changes, "NEEDS_WORK"), + (datacenter.remove_pull_request_changes_request, "UNAPPROVED"), + ], +) +@patch("magpie_bitbucket.client.urllib.request.build_opener") +def test_datacenter_pull_request_change_request_status( + mock_build_opener: MagicMock, + operation: Any, + expected_status: str, datacenter_env: None, ) -> None: - with pytest.raises( - BitbucketError, - match="Data Center pull request change-request writes are not supported", - ): - datacenter.request_pull_request_changes(load_config(), "9") + mock_opener( + mock_build_opener, + { + "slug": "alice", + "displayName": "Alice", + "name": "alice", + }, + { + "user": { + "slug": "alice", + "displayName": "Alice", + "name": "alice", + }, + "role": "REVIEWER", + "approved": False, + "status": expected_status, + }, + ) + result = operation(load_config(), "9") -def test_datacenter_remove_pull_request_changes_request_unsupported( + requests = mock_build_opener.return_value.open.call_args_list + assert len(requests) == 2 + + status_request = requests[1].args[0] + assert status_request.full_url == ( + "https://bitbucket.example.test/rest/api/1.0/projects/MAGPIE/repos/magpie/" + "pull-requests/9/participants/alice" + ) + assert status_request.get_method() == "PUT" + assert json.loads(status_request.data.decode("utf-8")) == {"status": expected_status} + + assert result["pull_request_id"] == "9" + assert result["participant"]["status"] == expected_status + + +@pytest.mark.parametrize( + ("action", "function_name", "requested", "participant_status"), + [ + ( + "request-changes", + "request_pull_request_changes", + True, + "NEEDS_WORK", + ), + ( + "remove-request-changes", + "remove_pull_request_changes_request", + False, + "UNAPPROVED", + ), + ], +) +def test_cli_pr_change_request_datacenter( + action: str, + function_name: str, + requested: bool, + participant_status: str, datacenter_env: None, + capsys: pytest.CaptureFixture[str], ) -> None: - with pytest.raises( - BitbucketError, - match="Data Center pull request change-request writes are not supported", - ): - datacenter.remove_pull_request_changes_request(load_config(), "9") + with patch(f"magpie_bitbucket.datacenter.{function_name}") as mock_operation: + mock_operation.return_value = { + "pull_request_id": "9", + "participant": { + "user": { + "slug": "alice", + "displayName": "Alice", + }, + "role": "REVIEWER", + "approved": False, + "status": participant_status, + }, + } + + exit_code = main(["pr", action, "9"]) + + assert exit_code == 0 + mock_operation.assert_called_once() + args = mock_operation.call_args.args + assert args[1:] == ("9",) + + output = json.loads(capsys.readouterr().out) + assert output["backend"] == "bitbucket-datacenter" + assert output["operation"] == f"pull-request-{action}" + assert output["pull_request_id"] == "9" + assert output["changes_requested"] is requested def test_normalize_pull_request_change_request_requested() -> None: diff --git a/tools/spec-loop/specs/adapters.md b/tools/spec-loop/specs/adapters.md index 20dba7a96..ea04d127a 100644 --- a/tools/spec-loop/specs/adapters.md +++ b/tools/spec-loop/specs/adapters.md @@ -157,9 +157,10 @@ by swapping the adapter, not the skill. On Cloud, `pr status` fetches the pull request first and reports its state and source commit alongside the `/statuses` build checks, matching the Data Center payload (#1526). - Confirmed pull-request comment writes cover Cloud and Data Center. - Additional Cloud-only writes cover approve/unapprove, request-changes, - decline, backend-neutral `reject`, and `pr merge --strategy + Confirmed pull-request comment, approve/unapprove, and + request-changes/remove-request-changes writes cover Cloud and Data Center. + Additional Cloud-only writes cover decline, backend-neutral `reject`, + and `pr merge --strategy {merge,squash,rebase} --expected-source-commit `, which re-checks the head commit immediately before the merge POST so a moved head fails closed (#1471); it does not enforce approval or build gates itself. @@ -171,7 +172,7 @@ by swapping the adapter, not the skill. `tools/jira`; Bitbucket does not proxy Jira tracker operations. It is not a complete `contract:change-request` or `contract:tracker` backend yet; broader Jira handoff, Data Center - review/merge writes, broader repository permissions, and fuller Pipelines + post-review/decline/merge writes, broader repository permissions, and fuller Pipelines run/log/retry coverage remain tracked in #606. - `tools/gitlab/` (`magpie-gitlab`) — partial, read-only GitLab REST API v4 bridge, declared `contract:tracker + contract:source-control +