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
2 changes: 1 addition & 1 deletion docs/labels-and-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ it implements multiple contracts (e.g. `tools/gmail` provides both
| [`tools/dev`](../tools/dev/) | `substrate:framework-dev` | Framework dev-loop helpers |
| [`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/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-read-only`. 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 Cloud-only issue listing/fetching, issue comment fetching, and issue attachment metadata fetching. The `partial-read-only` qualifier means this tool implements named read-only contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. `contract:tracker` coverage is partial and limited to Cloud issue reads until Bitbucket issue writes or linked Jira handoff coverage exist. |
| [`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 Cloud-only issue listing/fetching, issue comment fetching, issue attachment metadata fetching, and narrowly scoped issue comment creation. 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. `contract:tracker` coverage remains partial: Cloud issue reads and confirmed issue-comment creation are supported, while broader issue writes and linked Jira handoff coverage remain incomplete. |
| [`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` | 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/)) and the pull-request review/merge gate (`change-request`; the ASF default backend, alongside `tools/jira-patch/` and `tools/mail-patch/` for SVN-first projects) |
| [`tools/github-body-field`](../tools/github-body-field/) | `contract:tracker` | Read or rewrite one `### Field` section of a GitHub issue body without bringing the body into agent context — substrate helper for the security-sync skills |
Expand Down
69 changes: 43 additions & 26 deletions tools/bitbucket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Bitbucket forge bridge](#bitbucket-forge-bridge)
- [Partial read-only roadmap](#partial-read-only-roadmap)
- [Partial coverage roadmap](#partial-coverage-roadmap)
- [Prerequisites](#prerequisites)
- [Features](#features)
- [Operation coverage](#operation-coverage)
Expand All @@ -25,7 +25,7 @@

**Capability:** contract:change-request + contract:tracker

**Coverage:** `partial-read-only`
**Coverage:** `partial`

**Kind:** implementation

Expand All @@ -34,7 +34,7 @@
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 initial bridge implements a `partial-read-only` profile for
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
Expand All @@ -43,18 +43,18 @@ 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 limited to
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
fuller Pipelines run/log/retry coverage.

## Partial read-only roadmap
## Partial coverage roadmap

The Bitbucket bridge currently provides partial read-only coverage for
repository and pull-request review context. It intentionally does not claim
full Bitbucket backend parity.
The Bitbucket bridge currently provides partial coverage for repository,
tracker, and pull-request context, plus one narrowly scoped Bitbucket Cloud
tracker write. It intentionally does not claim full Bitbucket backend parity.

Implemented read-only commands:

Expand All @@ -64,6 +64,7 @@ Implemented read-only commands:
- `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 pr list-open`
- `magpie-bitbucket pr get <id>`
Expand All @@ -87,11 +88,12 @@ The `pr reviews` command provides partial read-only review-state coverage,
including reviewers, approvals, change-request signals, and related review
activity where exposed by the configured Bitbucket backend.

Write operations are intentionally out of scope for the current bridge
coverage. Future write support, such as commenting, approving, declining,
merging, creating issues, changing branches, or triggering builds, should be
designed separately with explicit human-in-the-loop approval, narrow command
surfaces, and maintainer review.
Write coverage is intentionally narrow. The bridge can create one
Bitbucket Cloud issue comment after the calling skill has obtained explicit
user confirmation. Other writes, such as editing/deleting comments, approving,
declining, merging, creating/updating issues, changing branches, or triggering
builds, remain out of scope and should be added separately with narrow command
surfaces and maintainer review.

## Prerequisites

Expand All @@ -103,7 +105,7 @@ surfaces, and maintainer review.

## Features

This implementation covers read-only operations:
This implementation primarily covers read-only operations, plus one narrow Cloud issue-comment write:

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.
Expand All @@ -112,15 +114,16 @@ This implementation covers read-only operations:
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. **Pull-request listing:** list open pull requests as `contract:change-request` proposal summaries.
9. **Pull-request fetch:** fetch one pull request as a normalized proposal object.
10. **Pull-request commits fetch:** fetch commits associated with a pull request as normalized read-only output.
11. **Pull-request diff fetch:** fetch the pull request unified diff as normalized read-only output.
12. **Pull-request discussion fetch:** fetch a comments-only pull request discussion subset as normalized read-only output.
13. **Pull-request review-state fetch:** fetch reviewers, approvals, change-request signals, pending review requests, and normalized review activity.
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.
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. **Pull-request tasks fetch:** list Bitbucket Cloud pull-request tasks and fetch one task as partial read-only change-request context.
16. **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.
17. **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 All @@ -145,6 +148,7 @@ surface:
| Change requests | `land` | Not implemented | Follow-up work for #606. |
| Change requests | `reject` | Not implemented | Follow-up work for #606. |
| 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. |
| 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 @@ -168,6 +172,9 @@ 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

Expand Down Expand Up @@ -215,7 +222,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. Most read-only PR/repository commands should use minimum read scopes. `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. The Cloud issue-comment write requires credentials permitted to write issue comments. `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. |
Expand All @@ -228,7 +235,7 @@ injected by the caller as `BITBUCKET_TOKEN` / `BITBUCKET_CLOUD_USER`.
Every successful command emits JSON to stdout. Failures return a
non-zero exit code with a human-readable error on stderr.

Fetched repository branch restriction policy, branch matcher patterns, users, groups, access keys, issue titles/descriptions, issue comments, attachment names, uploader names when present, attachment links, raw attachment payloads, issue reporter/assignee/commenter names, issue links,
Fetched repository branch restriction policy, branch matcher patterns, users, groups, access keys, issue titles/descriptions, fetched or created issue comments, attachment names, uploader names when present, attachment links, raw attachment payloads, issue reporter/assignee/commenter names, issue links,
pull request descriptions, commit messages, diff hunks, file paths, comments,
reviewer names, review decisions/events, approval/change-request activity,
merge-check decisions/blockers, status descriptions, CI URLs, and raw Bitbucket
Expand All @@ -242,7 +249,17 @@ to know which backend answered.

## Write-path discipline

This initial bridge is read-only.
The bridge can execute the narrowly scoped Bitbucket Cloud issue-comment
mutation, 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.

The comment body is read from `--body-file` to avoid shell-quoting issues.
Missing or empty body files fail before any outbound write request is made.
Bitbucket Data Center native issue-comment writes remain unsupported.

All other Bitbucket mutations remain out of scope for the current bridge and
must be introduced separately with the same confirmation discipline.

Future write commands will follow the same discipline as the GitHub and
Jira tools: the bridge may execute a mutation, but it must not decide
Expand Down
38 changes: 37 additions & 1 deletion tools/bitbucket/src/magpie_bitbucket/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,27 @@
import argparse
import json
from collections.abc import Sequence
from pathlib import Path
from typing import Any

from magpie_bitbucket import cloud, datacenter, normalize
from magpie_bitbucket.client import BitbucketConfig, load_config
from magpie_bitbucket.client import BitbucketConfig, BitbucketError, load_config


def _read_body_file(value: str) -> str:
"""Read a confirmed write body from disk."""
path = Path(value)
try:
body = path.read_text(encoding="utf-8")
except FileNotFoundError as exc:
raise BitbucketError(f"Body file not found: {path}") from exc
except OSError as exc:
raise BitbucketError(f"Failed to read body file {path}: {exc}") from exc

if not body.strip():
raise BitbucketError("Comment body file must not be empty")

return body


def main(argv: Sequence[str] | None = None) -> int:
Expand Down Expand Up @@ -64,6 +81,20 @@ def _build_parser() -> argparse.ArgumentParser:
issue_comments = issue_subparsers.add_parser("comments", help="Fetch issue comments.")
issue_comments.add_argument("issue_id", help="Issue ID to fetch comments for.")

issue_comment = issue_subparsers.add_parser(
"comment",
help="Create a comment on an issue after caller-side confirmation.",
)
issue_comment.add_argument(
"issue_id",
help="Issue ID to comment on.",
)
issue_comment.add_argument(
"--body-file",
required=True,
help="Path to the confirmed comment body.",
)

issue_attachments = issue_subparsers.add_parser(
"attachments",
help="Fetch issue attachment metadata and links, not file contents.",
Expand Down Expand Up @@ -140,6 +171,11 @@ def _dispatch(args: argparse.Namespace, config: BitbucketConfig) -> dict[str, An
raw = backend.get_issue_comments(config, args.issue_id)
return normalize.issue_comments(config.kind, raw)

if args.subcommand == "issue" and args.issue_action == "comment":
body = _read_body_file(args.body_file)
raw = backend.create_issue_comment(config, args.issue_id, body)
return normalize.created_issue_comment(config.kind, raw)

if args.subcommand == "issue" and args.issue_action == "attachments":
raw = backend.get_issue_attachments(config, args.issue_id)
return normalize.issue_attachments(config.kind, raw)
Expand Down
45 changes: 45 additions & 0 deletions tools/bitbucket/src/magpie_bitbucket/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,51 @@ def get_json(url: str, config: BitbucketConfig) -> dict[str, Any]:
raise BitbucketError(f"Failed to parse JSON response from {url}") from exc


def post_json(
url: str,
config: BitbucketConfig,
payload: dict[str, Any],
) -> dict[str, Any]:
"""POST JSON to a Bitbucket API URL and parse the JSON response."""
_require_https(url)
data = json.dumps(payload).encode("utf-8")
request = urllib.request.Request(
url,
data=data,
headers={
"Accept": "application/json",
"Authorization": make_auth_header(config),
"Content-Type": "application/json",
},
method="POST",
)

# Writes never follow redirects: repeating a mutation at a redirected
# location is less safe than failing and requiring the caller to retry.
opener = urllib.request.build_opener(NoAuthRedirectHandler)

try:
with opener.open(request, timeout=DEFAULT_TIMEOUT_SECONDS) as response:
body = response.read().decode("utf-8")
parsed = json.loads(body)
if not isinstance(parsed, dict):
raise BitbucketError(f"Expected JSON object from {url}")
return parsed
except BitbucketError:
raise
except urllib.error.HTTPError as exc:
message = _read_http_error(exc)
raise BitbucketError(f"Bitbucket request failed with HTTP {exc.code}: {message}") from exc
except urllib.error.URLError as exc:
raise BitbucketError(f"Failed to connect to Bitbucket: {exc.reason}") from exc
except TimeoutError as exc:
raise BitbucketError(
f"Timed out while connecting to Bitbucket after {DEFAULT_TIMEOUT_SECONDS}s"
) from exc
except json.JSONDecodeError as exc:
raise BitbucketError(f"Failed to parse JSON response from {url}") from exc


def get_text(url: str, config: BitbucketConfig, accept: str = "text/plain") -> dict[str, Any]:
"""GET a Bitbucket API URL and return a text response with metadata."""
_require_https(url)
Expand Down
Loading