From b47e651d631805f146ee026b9a051bc0690606c5 Mon Sep 17 00:00:00 2001 From: Kavya Katal Date: Tue, 20 Oct 2026 12:06:32 +0530 Subject: [PATCH 1/2] feat(bitbucket): add Data Center pull request comments --- docs/labels-and-capabilities.md | 2 +- tools/bitbucket/README.md | 36 +++--- .../src/magpie_bitbucket/datacenter.py | 29 ++++- .../src/magpie_bitbucket/normalize.py | 7 +- tools/bitbucket/tests/test_bitbucket.py | 113 ++++++++++++++++-- tools/spec-loop/specs/adapters.md | 6 +- 6 files changed, 155 insertions(+), 38 deletions(-) diff --git a/docs/labels-and-capabilities.md b/docs/labels-and-capabilities.md index a0bb2b9b7..bd48c953d 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 Cloud pull-request comment creation and 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 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/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 c6dd01b01..aca9b15c5 100644 --- a/tools/bitbucket/README.md +++ b/tools/bitbucket/README.md @@ -58,7 +58,7 @@ fuller Pipelines run/log/retry coverage. ## Partial coverage roadmap The Bitbucket bridge currently provides partial coverage for repository -and pull-request context (the native issue commands are fail-closed stubs), plus narrowly scoped Bitbucket Cloud +and pull-request context (the native issue commands are fail-closed stubs), plus narrowly scoped Bitbucket write operations. It intentionally does not claim full Bitbucket backend parity. Implemented commands and compatibility stubs: @@ -77,7 +77,7 @@ Implemented commands and compatibility stubs: - `magpie-bitbucket pr jira-issues ` (Data Center-only linked Jira discovery) - `magpie-bitbucket pr diff ` - `magpie-bitbucket pr discussion ` -- `magpie-bitbucket pr comment --body-file ` (Cloud-only write) +- `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) @@ -104,10 +104,10 @@ including reviewers, approvals, change-request signals, and related review activity where exposed by the configured Bitbucket backend. Write coverage is intentionally narrow. The bridge supports confirmed -Bitbucket Cloud top-level pull-request comment creation and 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 +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 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 @@ -160,7 +160,7 @@ surface: | Change requests / tracker handoff | `pr jira-issues ` | Partial read-only, Data Center only | Fetches Jira issue keys associated by Bitbucket Data Center with commits in the pull request. Output identifies `tools/jira` as the handoff tool; this command does not fetch or mutate Jira issues. Bitbucket Cloud has no equivalent bridge endpoint here and fails closed. | | Change requests | `diff` supplement / `pr diff ` | Partial read-only | Fetches the pull request unified diff so partial Bitbucket `get` coverage can expose proposal diffs. This does not mutate files, branches, refs, or repository history. | | 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, Cloud only | Creates one top-level Bitbucket Cloud pull-request comment from a caller-supplied body file after explicit caller-side confirmation. Data Center PR comment writes remain unsupported in this command. | +| 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). | @@ -213,7 +213,7 @@ uv run --project tools/bitbucket magpie-bitbucket pr diff 123 # Fetch pull request discussion/comments uv run --project tools/bitbucket magpie-bitbucket pr discussion 123 -# Create a Bitbucket Cloud pull request comment after caller-side confirmation +# Create a Bitbucket Cloud or Data Center pull request comment after caller-side confirmation uv run --project tools/bitbucket magpie-bitbucket pr comment 123 --body-file /tmp/comment.txt # Fetch pull request review state @@ -265,7 +265,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. Cloud pull-request comment, 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 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_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. | @@ -292,7 +292,7 @@ to know which backend answered. ## Write-path discipline -The bridge can execute narrowly scoped Bitbucket Cloud pull-request +The bridge can execute narrowly scoped Bitbucket pull-request mutations, but it does **not** decide whether to mutate. Every write operation must be gated on **explicit user confirmation in the calling skill**; the bridge only executes an already-confirmed action. @@ -300,21 +300,21 @@ only executes an already-confirmed action. Comment bodies are read from `--body-file` to avoid shell-quoting issues. Missing or empty body files fail before any outbound write request is made. -The bridge currently supports these narrow Cloud mutations (native Cloud +The bridge currently supports these narrow mutations (native Cloud issue writes were removed upstream and fail closed): -- top-level pull-request comment creation -- pull-request approval and approval withdrawal -- pull-request change-request creation and removal -- pull-request decline -- pull-request merge with source-commit pinning +- 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 +- Cloud-only pull-request decline +- Cloud-only pull-request merge with source-commit pinning 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, pull-request-comment, review-state, -decline, and merge writes remain unsupported by these commands. +Bitbucket Data Center issue-comment, review-state, decline, and merge +writes remain unsupported by these commands. All other Bitbucket mutations remain out of scope for the current bridge and must be introduced separately with the same confirmation discipline. diff --git a/tools/bitbucket/src/magpie_bitbucket/datacenter.py b/tools/bitbucket/src/magpie_bitbucket/datacenter.py index b07273e9d..aeecd6749 100644 --- a/tools/bitbucket/src/magpie_bitbucket/datacenter.py +++ b/tools/bitbucket/src/magpie_bitbucket/datacenter.py @@ -22,7 +22,15 @@ import json from typing import Any -from magpie_bitbucket.client import BitbucketConfig, BitbucketError, get_json, get_text, quote_path, require +from magpie_bitbucket.client import ( + BitbucketConfig, + BitbucketError, + get_json, + get_text, + post_json, + quote_path, + require, +) def _api_base(config: BitbucketConfig) -> str: @@ -485,10 +493,21 @@ def create_pull_request_comment( pull_request_id: str, body: str, ) -> dict[str, Any]: - """Reject pull-request comment creation for Data Center for now.""" - _ = (config, pull_request_id, body) - msg = "Bitbucket Data Center pull request comment writes are not supported by this command yet" - raise BitbucketError(msg) + """Create one top-level comment on a Bitbucket Data Center pull request.""" + 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) + url = f"{_api_base(config)}/projects/{project_key}/repos/{repo_slug}/pull-requests/{pr_id}/comments" + + comment = post_json( + url, + config, + {"text": body}, + ) + return { + "pull_request_id": pull_request_id, + "comment": comment, + } def post_pull_request_review( diff --git a/tools/bitbucket/src/magpie_bitbucket/normalize.py b/tools/bitbucket/src/magpie_bitbucket/normalize.py index 792d88c53..1b7058689 100644 --- a/tools/bitbucket/src/magpie_bitbucket/normalize.py +++ b/tools/bitbucket/src/magpie_bitbucket/normalize.py @@ -296,7 +296,12 @@ def created_pull_request_comment( ) -> dict[str, Any]: """Normalize the result of creating one pull request comment.""" comment = raw.get("comment") - normalized = _cloud_comment(comment) if kind == "cloud" and isinstance(comment, dict) else {} + if not isinstance(comment, dict): + normalized = {} + elif kind == "cloud": + normalized = _cloud_comment(comment) + else: + normalized = _datacenter_comment(comment, {}, None) return { "ok": bool(normalized), diff --git a/tools/bitbucket/tests/test_bitbucket.py b/tools/bitbucket/tests/test_bitbucket.py index 4529e6a6c..589081ed6 100644 --- a/tools/bitbucket/tests/test_bitbucket.py +++ b/tools/bitbucket/tests/test_bitbucket.py @@ -2751,18 +2751,64 @@ def test_cloud_create_pull_request_comment_posts_json( assert result["comment"]["id"] == 601 -def test_datacenter_create_pull_request_comment_unsupported( +@patch("magpie_bitbucket.client.urllib.request.build_opener") +def test_datacenter_create_pull_request_comment_posts_json( + mock_build_opener: MagicMock, datacenter_env: None, ) -> None: - with pytest.raises( - BitbucketError, - match="Data Center pull request comment writes are not supported", - ): - datacenter.create_pull_request_comment( - load_config(), - "9", - "Confirmed PR comment.", - ) + mock_opener( + mock_build_opener, + { + "id": 701, + "text": "Confirmed PR comment.", + "author": {"displayName": "Alice"}, + "createdDate": 1788230400000, + "updatedDate": 1788230401000, + "state": "OPEN", + }, + ) + + result = datacenter.create_pull_request_comment( + load_config(), + "9", + "Confirmed PR comment.", + ) + + request = mock_build_opener.return_value.open.call_args.args[0] + + assert request.full_url == ( + "https://bitbucket.example.test/rest/api/1.0/projects/MAGPIE/repos/magpie/pull-requests/9/comments" + ) + assert request.get_method() == "POST" + assert request.get_header("Content-type") == "application/json" + assert json.loads(request.data.decode("utf-8")) == {"text": "Confirmed PR comment."} + assert result["pull_request_id"] == "9" + assert result["comment"]["id"] == 701 + + +def test_normalize_created_datacenter_pull_request_comment() -> None: + normalized = created_pull_request_comment( + "datacenter", + { + "pull_request_id": "9", + "comment": { + "id": 701, + "text": "Confirmed PR comment.", + "author": {"displayName": "Alice"}, + "createdDate": 1788230400000, + "updatedDate": 1788230401000, + "state": "OPEN", + }, + }, + ) + + assert normalized["ok"] is True + assert normalized["backend"] == "bitbucket-datacenter" + assert normalized["operation"] == "pull-request-comment-create" + assert normalized["pull_request_id"] == "9" + assert normalized["comment"]["id"] == "701" + assert normalized["comment"]["author"] == "Alice" + assert normalized["comment"]["body"] == "Confirmed PR comment." def test_normalize_created_cloud_pull_request_comment() -> None: @@ -2831,6 +2877,53 @@ def test_cli_pr_comment_cloud( assert output["comment"]["id"] == "601" +@patch("magpie_bitbucket.datacenter.create_pull_request_comment") +def test_cli_pr_comment_datacenter( + mock_create_pull_request_comment: MagicMock, + datacenter_env: None, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + body_file = tmp_path / "comment.txt" + body_file.write_text("Confirmed PR comment.", encoding="utf-8") + + mock_create_pull_request_comment.return_value = { + "pull_request_id": "9", + "comment": { + "id": 701, + "text": "Confirmed PR comment.", + "author": {"displayName": "Alice"}, + "createdDate": 1788230400000, + "updatedDate": 1788230401000, + "state": "OPEN", + }, + } + + exit_code = main( + [ + "pr", + "comment", + "9", + "--body-file", + str(body_file), + ] + ) + + assert exit_code == 0 + + mock_create_pull_request_comment.assert_called_once() + args = mock_create_pull_request_comment.call_args.args + assert args[1:] == ("9", "Confirmed PR comment.") + + output = json.loads(capsys.readouterr().out) + assert output["backend"] == "bitbucket-datacenter" + assert output["operation"] == "pull-request-comment-create" + assert output["pull_request_id"] == "9" + assert output["comment"]["id"] == "701" + assert output["comment"]["author"] == "Alice" + assert output["comment"]["body"] == "Confirmed PR comment." + + @patch("magpie_bitbucket.cloud.create_pull_request_comment") def test_cli_pr_comment_rejects_empty_body_before_write( mock_create_pull_request_comment: MagicMock, diff --git a/tools/spec-loop/specs/adapters.md b/tools/spec-loop/specs/adapters.md index 8d2e07716..051be1818 100644 --- a/tools/spec-loop/specs/adapters.md +++ b/tools/spec-loop/specs/adapters.md @@ -157,9 +157,9 @@ 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 Cloud-only pull-request writes cover comments, - approve/unapprove, request-changes, decline, backend-neutral `reject`, - and `pr merge --strategy + 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 {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. From e4f191ef2f42f6988a9b3890c3200b485331524d Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Tue, 20 Oct 2026 15:33:08 +0200 Subject: [PATCH 2/2] Apply review fixups for Data Center comment writes - Say in the second Bitbucket bullet of the adapters spec that comment creation covers Data Center too, so both bullets agree. - Move the activities-feed comment back above get_pull_request_discussion, the function it describes. - Run the empty-body and missing-body-file CLI tests against both the Cloud and Data Center backends. Generated-by: Claude Opus 5 --- .../src/magpie_bitbucket/datacenter.py | 8 ++--- tools/bitbucket/tests/test_bitbucket.py | 32 ++++++++++++------- tools/spec-loop/specs/adapters.md | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/tools/bitbucket/src/magpie_bitbucket/datacenter.py b/tools/bitbucket/src/magpie_bitbucket/datacenter.py index aeecd6749..fb06456eb 100644 --- a/tools/bitbucket/src/magpie_bitbucket/datacenter.py +++ b/tools/bitbucket/src/magpie_bitbucket/datacenter.py @@ -484,10 +484,6 @@ def _pull_request_source_commit(raw: dict[str, Any]) -> str: raise BitbucketError(msg) -# Bitbucket Data Center exposes PR comments through the broader activities feed. -# We fetch the paginated feed here and filter comment-bearing activities during -# normalization so review/merge/rescope lifecycle events are not exposed as -# discussion comments. def create_pull_request_comment( config: BitbucketConfig, pull_request_id: str, @@ -535,6 +531,10 @@ def reject_pull_request( ) +# Bitbucket Data Center exposes PR comments through the broader activities feed. +# We fetch the paginated feed here and filter comment-bearing activities during +# normalization so review/merge/rescope lifecycle events are not exposed as +# discussion comments. def get_pull_request_discussion(config: BitbucketConfig, pull_request_id: str) -> dict[str, Any]: """Fetch pull request activities from Bitbucket Data Center.""" project_key = quote_path(require(config.project_key, "BITBUCKET_PROJECT_KEY")) diff --git a/tools/bitbucket/tests/test_bitbucket.py b/tools/bitbucket/tests/test_bitbucket.py index 589081ed6..a4b3e38a8 100644 --- a/tools/bitbucket/tests/test_bitbucket.py +++ b/tools/bitbucket/tests/test_bitbucket.py @@ -2924,18 +2924,22 @@ def test_cli_pr_comment_datacenter( assert output["comment"]["body"] == "Confirmed PR comment." -@patch("magpie_bitbucket.cloud.create_pull_request_comment") +@pytest.mark.parametrize("backend", ["cloud", "datacenter"]) def test_cli_pr_comment_rejects_empty_body_before_write( - mock_create_pull_request_comment: MagicMock, - cloud_env: None, + backend: str, + request: pytest.FixtureRequest, tmp_path: Path, ) -> None: + request.getfixturevalue(f"{backend}_env") body_file = tmp_path / "comment.txt" body_file.write_text(" ", encoding="utf-8") - with pytest.raises( - BitbucketError, - match="Comment body file must not be empty", + with ( + patch(f"magpie_bitbucket.{backend}.create_pull_request_comment") as mock_create_pull_request_comment, + pytest.raises( + BitbucketError, + match="Comment body file must not be empty", + ), ): main( [ @@ -2950,17 +2954,21 @@ def test_cli_pr_comment_rejects_empty_body_before_write( mock_create_pull_request_comment.assert_not_called() -@patch("magpie_bitbucket.cloud.create_pull_request_comment") +@pytest.mark.parametrize("backend", ["cloud", "datacenter"]) def test_cli_pr_comment_missing_body_file_before_write( - mock_create_pull_request_comment: MagicMock, - cloud_env: None, + backend: str, + request: pytest.FixtureRequest, tmp_path: Path, ) -> None: + request.getfixturevalue(f"{backend}_env") body_file = tmp_path / "missing-comment.txt" - with pytest.raises( - BitbucketError, - match="Body file not found", + with ( + patch(f"magpie_bitbucket.{backend}.create_pull_request_comment") as mock_create_pull_request_comment, + pytest.raises( + BitbucketError, + match="Body file not found", + ), ): main( [ diff --git a/tools/spec-loop/specs/adapters.md b/tools/spec-loop/specs/adapters.md index 051be1818..20dba7a96 100644 --- a/tools/spec-loop/specs/adapters.md +++ b/tools/spec-loop/specs/adapters.md @@ -423,7 +423,7 @@ uv run --all-packages --group dev pytest tools/github-rollup/tests - Bitbucket write operations follow the framework write-path discipline: the calling skill must obtain explicit user confirmation before invoking a mutation. The bridge executes only the confirmed action; current write - coverage is Bitbucket Cloud pull-request comment creation, + coverage is Bitbucket Cloud and Data Center pull-request comment creation, backend-neutral `post_review` through comment/approve/request-changes, backend-neutral `reject`, and Bitbucket Cloud pull-request approve/unapprove, request-changes/remove-request-changes,