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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 45 additions & 52 deletions tools/bitbucket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,22 @@
Bitbucket Cloud and Bitbucket Data Center bridge for Magpie adopters
that use Bitbucket as a forge, pull-request review surface, or Jira-paired Atlassian backend.

This bridge implements a `partial` profile for
repository metadata context and pull-request discovery/fetching under
`contract:change-request`, with partial Cloud-only issue reads, comment reads, and attachment metadata reads under
`contract:tracker`. Partial adapters may implement named
contract verbs, but they do not satisfy the complete contract and must
not be advertised as complete/selectable backends.
This bridge implements a `partial` profile for repository metadata
context and pull-request discovery/fetching under
`contract:change-request`. Atlassian fully removed the native Bitbucket
Cloud Issue Tracker API on 2026-08-20, so the existing native issue CLI
surface is retained only as a fail-closed compatibility surface that
directs callers toward linked Jira coverage. Partial adapters may
implement named contract verbs, but they do not satisfy the complete
contract and must not be advertised as complete/selectable backends.

Repository metadata reads are currently bridge context for Bitbucket
pull-request workflows, not a complete `contract:source-control`
backend. `contract:tracker` coverage is partial and currently includes
Bitbucket Cloud issue listing/fetching where the repository issue tracker is enabled.
#606 remains open for the remaining Bitbucket/Jira workflow coverage.
Later PRs can extend the same adapter with write operations,
linked Jira handoff, issue write operations, and
backend. `contract:tracker` remains partial: native Bitbucket Cloud
issue operations are unavailable after Atlassian's 2026-08-20 removal,
and Data Center native issue operations are also unsupported.
#606 remains open for linked Jira tracker coverage and the remaining
Bitbucket workflow gaps, including broader repository permissions and
fuller Pipelines run/log/retry coverage.

## Partial coverage roadmap
Expand All @@ -56,16 +58,16 @@ The Bitbucket bridge currently provides partial coverage for repository,
tracker, and pull-request context, plus narrowly scoped Bitbucket Cloud
write operations. It intentionally does not claim full Bitbucket backend parity.

Implemented read-only commands:
Implemented commands and compatibility stubs:

- `magpie-bitbucket auth-check`
- `magpie-bitbucket repo get`
- `magpie-bitbucket repo restrictions`
- `magpie-bitbucket issue list-open`
- `magpie-bitbucket issue get <id>`
- `magpie-bitbucket issue comments <id>`
- `magpie-bitbucket issue comment <id> --body-file <path>` (Cloud-only write)
- `magpie-bitbucket issue attachments <id>`
- `magpie-bitbucket issue list-open` (fail-closed compatibility stub; native Cloud issue tracker removed)
- `magpie-bitbucket issue get <id>` (fail-closed compatibility stub; native Cloud issue tracker removed)
- `magpie-bitbucket issue comments <id>` (fail-closed compatibility stub; native Cloud issue tracker removed)
- `magpie-bitbucket issue comment <id> --body-file <path>` (fail-closed compatibility stub; native Cloud issue tracker removed)
- `magpie-bitbucket issue attachments <id>` (fail-closed compatibility stub; native Cloud issue tracker removed)
- `magpie-bitbucket pr list-open`
- `magpie-bitbucket pr get <id>`
- `magpie-bitbucket pr commits <id>`
Expand Down Expand Up @@ -99,12 +101,14 @@ 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 issue-comment creation, 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. Other writes, such as editing/deleting comments,
creating/updating issues, changing branches, or triggering builds, remain out
of scope and should be added separately with narrow command surfaces and
maintainer review.
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
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
separately with narrow command surfaces and maintainer review.

## Prerequisites

Expand All @@ -121,22 +125,18 @@ This implementation primarily covers read-only operations, plus narrowly scoped
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.
3. **Repository branch restrictions:** fetch known read-only repository branch restriction / branch permission policy context.
4. **Cloud issue listing:** list open Bitbucket Cloud issues where the repository issue tracker is enabled.
5. **Cloud issue fetch:** fetch one Bitbucket Cloud issue as partial read-only tracker context.
6. **Cloud issue comments fetch:** fetch comments for one Bitbucket Cloud issue as partial read-only tracker context.
7. **Cloud issue attachments fetch:** fetch attachment metadata and links for one Bitbucket Cloud issue as partial read-only tracker context; file contents are not downloaded.
8. **Cloud issue comment create:** create one Bitbucket Cloud issue comment from a confirmed body file; the calling skill is responsible for explicit user confirmation before invoking the bridge.
9. **Pull-request listing:** list open pull requests as `contract:change-request` proposal summaries.
10. **Pull-request fetch:** fetch one pull request as a normalized proposal object.
11. **Pull-request commits fetch:** fetch commits associated with a pull request as normalized read-only output.
12. **Pull-request diff fetch:** fetch the pull request unified diff as normalized read-only output.
13. **Pull-request discussion fetch:** fetch a comments-only pull request discussion subset as normalized read-only output.
14. **Pull-request review-state fetch:** fetch reviewers, approvals, change-request signals, pending review requests, and normalized review activity.
15. **Cloud pull-request approve:** approve one pull request as the authenticated user after explicit caller-side confirmation.
16. **Cloud pull-request unapprove:** withdraw the authenticated user's approval after explicit caller-side confirmation.
17. **Pull-request tasks fetch:** list Bitbucket Cloud pull-request tasks and fetch one task as partial read-only change-request context.
18. **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.
19. **Pull-request status fetch:** fetch build/status checks for the pull request as normalized read-only output.
4. **Native issue tracker compatibility guard:** retain the historical `issue` CLI surface, but fail closed before any request because Atlassian removed the native Bitbucket Cloud Issue Tracker API on 2026-08-20; Data Center native issues remain unsupported.
5. **Pull-request listing:** list open pull requests as `contract:change-request` proposal summaries.
6. **Pull-request fetch:** fetch one pull request as a normalized proposal object.
7. **Pull-request commits fetch:** fetch commits associated with a pull request as normalized read-only output.
8. **Pull-request diff fetch:** fetch the pull request unified diff as normalized read-only output.
9. **Pull-request discussion fetch:** fetch a comments-only pull request discussion subset as normalized read-only output.
10. **Pull-request review-state fetch:** fetch reviewers, approvals, change-request signals, pending review requests, and normalized review activity.
11. **Cloud pull-request approve:** approve one pull request as the authenticated user after explicit caller-side confirmation.
12. **Cloud pull-request unapprove:** withdraw the authenticated user's approval after explicit caller-side confirmation.
13. **Pull-request tasks fetch:** list Bitbucket Cloud pull-request tasks and fetch one task as partial read-only change-request context.
14. **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.
15. **Pull-request status fetch:** fetch build/status checks for the pull request as normalized read-only output.

The bridge supports two Bitbucket API flavours behind one command
surface:
Expand Down Expand Up @@ -165,8 +165,7 @@ surface:
| Change requests | `land` / `pr merge <id> --strategy {merge,squash,rebase} --expected-source-commit <sha>` | Partial write, Cloud only | Submits a Bitbucket Cloud pull-request merge after explicit caller-side confirmation. The caller must run and inspect `pr merge-checks <id>` before invoking this command; `pr merge` does not independently enforce approval, build-status, or merge-check gates. The expected source commit (7–40 hexadecimal characters, rejected before any request otherwise) is checked immediately before the merge POST so a changed PR head fails closed. The requested strategy is mapped to Bitbucket's merge strategy (reported as `backend_strategy`) and the resulting merge commit is returned as `landed_ref` when available. An asynchronous merge may be accepted before a `landed_ref` is available; `merge_status` is then `submitted`, with Bitbucket's own task state in `task_status` and the task URL in `task_url`. A timeout on the merge POST is reported as "outcome unknown" — check `pr get <id>` before retrying, since the merge may already be running. Data Center merge writes remain unsupported. |
| Change requests | `pr merge-task-status <id> <task-id>` | Partial read-only, Cloud only | Fetches the status of an asynchronous Bitbucket Cloud pull-request merge task. Pending tasks normalize to `merge_status=submitted`; successful tasks expose `merge_result.merge_commit.hash` as `landed_ref`; failed tasks normalize to `merge_status=failed`. Data Center merge task-status reads remain unsupported. |
| Change requests | `reject` / `pr reject <id> --reason-file <path>` | Partial write, Cloud only | Implements the backend-neutral reject contract for Bitbucket Cloud after explicit caller-side confirmation. Posts the confirmed rejection reason as a pull-request comment, then declines the pull request. If the decline fails after the reason was posted, the command reports the partial outcome and directs the caller to inspect the PR before retrying to avoid duplicating the rejection comment. Data Center reject writes remain unsupported. |
| Tracker | `issue list-open` / `issue get <id>` / `issue comments <id>` / `issue attachments <id>` | Partial read-only, Cloud only | Lists and fetches Bitbucket Cloud issues, issue comments, and issue attachment metadata/links where the repository issue tracker is enabled. Bitbucket Data Center native issue reads/comments/attachments are unsupported; linked Jira handoff remains separate follow-up work. |
| Tracker | `issue comment <id> --body-file <path>` | Partial write, Cloud only | Creates one Bitbucket Cloud issue comment from a caller-supplied body file. The calling skill must obtain explicit user confirmation before invoking this mutation. Bitbucket Data Center native issue comment writes are unsupported; linked Jira coverage remains separate. |
| Tracker | `issue list-open` / `issue get <id>` / `issue comments <id>` / `issue comment <id> --body-file <path>` / `issue attachments <id>` | Removed upstream / fail-closed | Atlassian fully removed the native Bitbucket Cloud Issue Tracker API on 2026-08-20. These commands remain as compatibility stubs and fail before any HTTP request, directing callers toward linked Jira coverage. Bitbucket Data Center native issue operations are also unsupported. |
| CI | `pr status <id>` | Partial read-only | Fetches build/status checks for a pull request. This does not trigger, retry, or mutate Pipelines/builds. |

## Invocation
Expand All @@ -181,19 +180,13 @@ uv run --project tools/bitbucket magpie-bitbucket repo get
# Fetch repository branch restrictions
uv run --project tools/bitbucket magpie-bitbucket repo restrictions

# List open Bitbucket Cloud issues
# Native Bitbucket Cloud issue commands are compatibility stubs only.
# Atlassian removed the native Issue Tracker API on 2026-08-20.
# Each command fails closed before any HTTP request and directs callers to Jira.
uv run --project tools/bitbucket magpie-bitbucket issue list-open

# Fetch one Bitbucket Cloud issue
uv run --project tools/bitbucket magpie-bitbucket issue get 123

# Fetch Bitbucket Cloud issue comments
uv run --project tools/bitbucket magpie-bitbucket issue comments 123

# Create a Bitbucket Cloud issue comment after caller-side confirmation
uv run --project tools/bitbucket magpie-bitbucket issue comment 123 --body-file /tmp/comment.txt

# Fetch Bitbucket Cloud issue attachment metadata and links
uv run --project tools/bitbucket magpie-bitbucket issue attachments 123

# List open pull requests
Expand Down Expand Up @@ -327,8 +320,8 @@ then invoke the write command.

Follow-up PRs can extend this bridge with:

- Bitbucket issue write operations and additional tracker fields.
- Linked Jira issue handoff through `tools/jira/`.
- Linked Jira issue/tracker handoff through `tools/jira/`, replacing the
removed native Bitbucket Cloud Issue Tracker surface.
- Remaining pull-request review operations and broader merge coverage.
- Broader repository permission reads.
- Fuller Bitbucket Pipelines run/log/retry coverage beyond read-only pull-request status reads.
114 changes: 23 additions & 91 deletions tools/bitbucket/src/magpie_bitbucket/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,117 +102,49 @@ def get_repository_restrictions(config: BitbucketConfig) -> dict[str, Any]:
return combined


def list_open_issues(config: BitbucketConfig) -> dict[str, Any]:
"""List open issues from a Bitbucket Cloud repository."""
workspace = quote_path(require(config.workspace, "BITBUCKET_WORKSPACE"))
repo_slug = quote_path(require(config.repo_slug, "BITBUCKET_REPO_SLUG"))
url = f"{CLOUD_API_BASE}/repositories/{workspace}/{repo_slug}/issues?q=state%3D%22new%22%20OR%20state%3D%22open%22%20OR%20state%3D%22on%20hold%22"
_NATIVE_ISSUE_TRACKER_REMOVED = (
"Bitbucket Cloud native issue tracker API was removed by Atlassian "
"on 2026-08-20; use linked Jira coverage instead."
)

combined: dict[str, Any] = {
"values": [],
"paginated": True,
"pages": [],
}

seen_urls = {url}
while url:
page = get_json(url, config)
combined["pages"].append(page)
def _removed_native_issue_tracker() -> BitbucketError:
"""Return the fail-closed error for removed native Cloud issue APIs."""
return BitbucketError(_NATIVE_ISSUE_TRACKER_REMOVED)

values = page.get("values")
if isinstance(values, list):
combined["values"].extend(item for item in values if isinstance(item, dict))

url = _validated_next_url(page.get("next"), seen_urls)

return combined
def list_open_issues(config: BitbucketConfig) -> dict[str, Any]:
"""Fail closed because Bitbucket Cloud removed its native issue tracker."""
_ = config
raise _removed_native_issue_tracker()


def get_issue(config: BitbucketConfig, issue_id: str) -> dict[str, Any]:
"""Fetch one issue from a Bitbucket Cloud repository."""
workspace = quote_path(require(config.workspace, "BITBUCKET_WORKSPACE"))
repo_slug = quote_path(require(config.repo_slug, "BITBUCKET_REPO_SLUG"))
issue = quote_path(issue_id)
url = f"{CLOUD_API_BASE}/repositories/{workspace}/{repo_slug}/issues/{issue}"
return get_json(url, config)
"""Fail closed because Bitbucket Cloud removed its native issue tracker."""
_ = (config, issue_id)
raise _removed_native_issue_tracker()


def create_issue_comment(
config: BitbucketConfig,
issue_id: str,
body: str,
) -> dict[str, Any]:
"""Create one comment on a Bitbucket Cloud issue."""
workspace = quote_path(require(config.workspace, "BITBUCKET_WORKSPACE"))
repo_slug = quote_path(require(config.repo_slug, "BITBUCKET_REPO_SLUG"))
issue = quote_path(issue_id)
url = f"{CLOUD_API_BASE}/repositories/{workspace}/{repo_slug}/issues/{issue}/comments"

comment = post_json(
url,
config,
{"content": {"raw": body}},
)
return {
"issue_id": issue_id,
"comment": comment,
}
"""Fail closed because Bitbucket Cloud removed its native issue tracker."""
_ = (config, issue_id, body)
raise _removed_native_issue_tracker()


def get_issue_comments(config: BitbucketConfig, issue_id: str) -> dict[str, Any]:
"""Fetch comments for a Bitbucket Cloud issue."""
workspace = quote_path(require(config.workspace, "BITBUCKET_WORKSPACE"))
repo_slug = quote_path(require(config.repo_slug, "BITBUCKET_REPO_SLUG"))
issue = quote_path(issue_id)
url = f"{CLOUD_API_BASE}/repositories/{workspace}/{repo_slug}/issues/{issue}/comments"

combined: dict[str, Any] = {
"issue_id": issue_id,
"values": [],
"paginated": True,
"pages": [],
}

seen_urls = {url}
while url:
page = get_json(url, config)
combined["pages"].append(page)

values = page.get("values")
if isinstance(values, list):
combined["values"].extend(item for item in values if isinstance(item, dict))

url = _validated_next_url(page.get("next"), seen_urls)

return combined
"""Fail closed because Bitbucket Cloud removed its native issue tracker."""
_ = (config, issue_id)
raise _removed_native_issue_tracker()


def get_issue_attachments(config: BitbucketConfig, issue_id: str) -> dict[str, Any]:
"""Fetch attachment metadata and links for a Bitbucket Cloud issue."""
workspace = quote_path(require(config.workspace, "BITBUCKET_WORKSPACE"))
repo_slug = quote_path(require(config.repo_slug, "BITBUCKET_REPO_SLUG"))
issue = quote_path(issue_id)
url = f"{CLOUD_API_BASE}/repositories/{workspace}/{repo_slug}/issues/{issue}/attachments"

combined: dict[str, Any] = {
"issue_id": issue_id,
"values": [],
"paginated": True,
"pages": [],
}

seen_urls = {url}
while url:
page = get_json(url, config)
combined["pages"].append(page)

values = page.get("values")
if isinstance(values, list):
combined["values"].extend(item for item in values if isinstance(item, dict))

url = _validated_next_url(page.get("next"), seen_urls)

return combined
"""Fail closed because Bitbucket Cloud removed its native issue tracker."""
_ = (config, issue_id)
raise _removed_native_issue_tracker()


def list_open_pull_requests(config: BitbucketConfig) -> dict[str, Any]:
Expand Down
Loading