Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9126a27
Phase 4.1: bb_api.py + Python test infrastructure (MCP foundation) (#2)
daniel-pittman May 26, 2026
cb9071c
Phase 4.2: bb_ops pipeline operations + cross-host-safe log fetch (#3)
daniel-pittman May 26, 2026
fb889c3
Phase 4.3: bb_ops pull-request operations + tests (#4)
daniel-pittman May 26, 2026
aabdc32
Phase 4.4: bb_ops repos / branches / vars / downloads / commits + tes…
daniel-pittman May 27, 2026
217a2ac
Phase 4.5: git_ops — git-context wrappers for the MCP agent (#6)
daniel-pittman May 27, 2026
b20c894
Phase 4.6: mcp_server.py — FastMCP wrapper around bb_ops + git_ops (#7)
daniel-pittman May 27, 2026
9290218
Phase 4.7: bash parity — 5 new commands + 8 parity bugfixes (#8)
daniel-pittman May 27, 2026
274f5d4
Move MCP venv from /tmp to ~/.local/share/bitbucket-cli/venv (durable…
daniel-pittman May 27, 2026
a88408c
Phase 4.8: generic agent definition, README MCP install section, CLAU…
daniel-pittman May 27, 2026
751b692
Address PR #10 review nits
daniel-pittman May 27, 2026
5f9c8fa
Address PR #10 round-2 review findings (4 doc accuracy fixes)
daniel-pittman May 27, 2026
b51c082
Fix whoami parity: add workspace-reachability probe to Python whoami
daniel-pittman May 27, 2026
177043f
Address PR #10 round-4 review (1 regression fix + 2 polish)
daniel-pittman May 27, 2026
cdc5593
README: restructure MCP-server section to mirror zenhub-cli's install…
daniel-pittman May 27, 2026
8780146
Address PR #11 review nits
daniel-pittman May 27, 2026
dc060f9
Address PR #11 round-2 review (3 regressions + 5 UX + 7 polish)
daniel-pittman May 27, 2026
4d31535
Fix bb prs bash 3.2 incompatibility + improve macOS install docs
daniel-pittman May 27, 2026
499d997
Address PR #12 review (4 fixes, 1 deferred)
daniel-pittman May 27, 2026
a642baa
Bump VERSION to 1.1.0 for release
daniel-pittman May 27, 2026
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
15 changes: 13 additions & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@
# Workflow and CI configuration — the most security-sensitive paths.
/.github/ @daniel-pittman

# Core source — the bash CLI script.
# Changes here can affect runtime behaviour or surface secrets.
# Core source — the bash CLI script and the parallel Python implementation
# the MCP server uses. Changes to any of these can affect runtime behaviour
# or surface secrets. The bash script and the Python modules are PARALLEL
# implementations of the same Bitbucket REST contract; review of one often
# implies review of the other (see CONTRIBUTING.md for the parity rule).
/bb @daniel-pittman
/bb_api.py @daniel-pittman
/bb_ops.py @daniel-pittman
/git_ops.py @daniel-pittman
/mcp_server.py @daniel-pittman

# Release versioning. A `VERSION` bump corresponds 1:1 with a release tag.
/VERSION @daniel-pittman

# Packaging + agent definition.
/pyproject.toml @daniel-pittman
/agents/ @daniel-pittman
31 changes: 30 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Continuous integration: bash syntax + tooling sanity.
# Continuous integration: bash syntax + Python tests.
#
# This workflow needs NO secrets and is safe to run on every push and PR,
# including PRs from forks. The secret-sensitive Claude review workflows are
Expand All @@ -17,6 +17,16 @@ permissions:
jobs:
syntax:
runs-on: ubuntu-latest
strategy:
# Enforce the Python compatibility floor advertised in pyproject.toml
# (requires-python = ">=3.10"). 3.10 is the floor because bb_api uses
# PEP 604 union syntax (`X | None`) and PEP 585 builtin generics
# (`list[str]`) as runtime-resolved annotations; both are 3.10 features.
# 3.12 catches removed-feature regressions. fail-fast: false so a
# problem in one version doesn't mask problems in others.
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
# SHA-pinned actions/checkout v4.2.2
- name: Check out the repository
Expand All @@ -36,3 +46,22 @@ jobs:
command -v curl >/dev/null || { echo "curl missing"; exit 1; }
bash --version | head -1
jq --version

# SHA-pinned actions/setup-python v5.3.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: ${{ matrix.python-version }}

- name: Python syntax check
run: python -m py_compile bb_api.py bb_ops.py git_ops.py mcp_server.py

# Install pytest only. The MCP server's heavy dependencies (mcp) are
# exercised at runtime, not here — the unit tests mock the network and
# the MCP transport entirely, so pulling them into CI would just slow
# the matrix down for no signal.
- name: Install pytest
run: python -m pip install --upgrade pip pytest

- name: Run pytest
run: python -m pytest tests/ -v
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
.env
.idea/
.claude/

# Python
__pycache__/
*.py[cod]
*.egg-info/
.venv/
venv/
.pytest_cache/
.ruff_cache/
.mypy_cache/
build/
dist/

# macOS
.DS_Store
118 changes: 83 additions & 35 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,115 @@ This file provides guidance to Claude Code when working with this repository.

## Overview

`bb` is a bash CLI wrapper around the Bitbucket Cloud REST API. It provides commands for managing pipelines, pull requests, branches, and repositories from the terminal.
`bb` is a Bitbucket Cloud client with two parallel implementations of the same REST contract:

- **Bash CLI** (`bb`) — human-facing, depends only on `curl` + `jq`.
- **Python MCP server** (`mcp_server.py` + `bb_api.py` + `bb_ops.py` + `git_ops.py`) — exposes the same Bitbucket surface plus git-context helpers as MCP tools that Claude Code (or any MCP-aware client) can call directly.

Both implementations target Bitbucket Cloud REST API v2.0. Neither wraps the other — they speak HTTP directly.

## Project Structure

```
bitbucket-cli/
├── bb # Main CLI script (bash)
├── README.md # User documentation
├── LICENSE # MIT license
├── CLAUDE.md # This file
├── .env.example # Example environment config
└── .gitignore # Git ignore rules
├── bb # Bash CLI (cmd_* functions, ~1.4k lines)
├── bb_api.py # urllib-based HTTP client, pagination, redacting
├── bb_ops.py # Bitbucket operations: pipelines / PRs / repos / branches / vars / commits
├── git_ops.py # subprocess wrappers: branch / status / remote / commits / diffs
├── mcp_server.py # FastMCP tool registry + self-bootstrap venv
├── agents/bitbucket.md # Generic agent definition for the MCP server
├── tests/ # pytest suite (~360 tests)
├── pyproject.toml # Python packaging + pytest config
├── docs/img/ # social-preview.png and other assets
├── README.md # User-facing docs (install, usage, MCP setup)
├── CONTRIBUTING.md # Parity rule, GitFlow, branch protection
├── SECURITY.md # Disclosure policy + maintainer checklist
└── .github/ # CI, Claude code-review / security-review workflows, CODEOWNERS
```

## Key Technical Details

- **Language**: Pure bash (no external dependencies beyond curl and jq)
- **Languages**: bash (3.2+ — macOS system bash is supported) and Python (3.10+)
- **API**: Bitbucket Cloud REST API v2.0 (`https://api.bitbucket.org/2.0`)
- **Auth**: HTTP Basic authentication using Atlassian API tokens
- `BB_USER`: Bitbucket account email address
- `BB_TOKEN`: API token from id.atlassian.com
- `BB_WORKSPACE`: Bitbucket workspace slug
- **Auth**: HTTP Basic using Atlassian API tokens
- `BB_USER`: Bitbucket account email
- `BB_TOKEN`: API token from id.atlassian.com (never echoed)
- `BB_WORKSPACE`: workspace slug
- **MCP runtime**: stdlib-only at runtime; the `mcp` package is the only third-party dep, installed into a self-bootstrapped venv at `$XDG_DATA_HOME/bitbucket-cli/venv` (default `~/.local/share/bitbucket-cli/venv`) on first invocation.

## Configuration

The script loads config from two locations (in order):
1. `~/.config/bb/config` - User config file
2. `.env` in script directory - Local override (gitignored)
Loaded in this order (later overrides earlier):
1. `~/.config/bb/config` — user config
2. `.env` in script directory — local override (gitignored)
3. Environment variables (highest priority)

## Code Conventions

- Functions are named `cmd_*` for user-facing commands
- Helper functions: `bb_get`, `bb_post`, `bb_put`, `bb_delete` for API calls
- `detect_repo()` auto-detects repo from git remote if not provided
- `format_state()` normalizes pipeline/PR states to 4-char display codes
### Bash (`bb`)
- User-facing commands: `cmd_<name>` functions.
- HTTP helpers: `bb_get` / `bb_post` / `bb_put` / `bb_delete`.
- `detect_repo` / `repo_path` resolve the repo from git remote when not supplied.
- Boundary validation via helpers like `_require_build_number` (rejects non-numeric).
- Variables are passed to `jq -Rs` with NUL delimiters to prevent injection.
- Error rc capture: `if cmd; then ...; else local rc=$?; ...; exit $rc; fi` (never lose the exit code).

### Python (`bb_api.py`, `bb_ops.py`, `git_ops.py`, `mcp_server.py`)
- `BBClient` injected as first arg into every `bb_ops` function.
- `bb_ops.<verb>_<noun>` naming (e.g. `pipeline_trigger`, `pr_create`).
- `_is_positive_int` guard rejects bool (the bool-is-int trap).
- MCP tools resolve repo via `_resolve_repo` (rejects malformed/`.`/`..`/whitespace BEFORE any network call).
- Error envelopes route ALL string fields (`message`, `body`, `stderr`, `url`) through `_safe_text` / `_redact_url` — single chokepoint, not per-field.
- `_error_dict_with(e, ...)` threads request identifiers (pr_id, number, step_index) into the error dict for correlation.

### Parallel-implementation parity rule
The bash and Python sides implement the **same Bitbucket REST contract** independently. When a defect surfaces in either side (URL construction, body shape, parameter naming), the fix lands in BOTH paths. Tests verify the correct contract — never pin existing buggy behavior. See CONTRIBUTING.md.

## Adding New Commands

1. Create a `cmd_yourcommand()` function
2. Add it to the case statement at the bottom of the script
3. Add help text in `cmd_help()`
4. Update README.md with usage examples
For end-user CLI features:
1. Add `cmd_yourcommand()` in `bb`.
2. Wire into the case statement at the bottom of the script.
3. Add help text in `cmd_help()`.
4. Update README.md usage examples.

## Testing
For MCP-tool features:
1. Add `bb_ops.<verb>_<noun>()` Python function.
2. Add pytest coverage in `tests/` (assert URL + method + body shape — not just status).
3. Add a thin `@mcp.tool()` wrapper in `mcp_server.py` that calls `_resolve_repo`, invokes the ops function, returns `{"ok": True, ...}` on success and `_error_dict_with(e, ...)` on failure.
4. Update the tool-surface table in `agents/bitbucket.md`.

Manual testing against a Bitbucket workspace:
If the new feature applies to both surfaces (the dominant case), do both. Parity is the default.

## Testing

```bash
# Verify auth works
bb repos
# Python tests (run from repo root)
pytest

# Test pipeline commands
bb pipelines your-repo
bb pipeline your-repo 1
# Specific test file
pytest tests/test_mcp_server.py -v

# Test PR commands
bb prs your-repo
# Bash smoke tests (manual, against your workspace)
bb whoami
bb repos
bb pipelines your-repo
```

CI runs `pytest` on every PR. Bash side is smoke-tested manually before release.

## Security Posture

- BB_TOKEN never echoed (whoami, error dicts, log lines).
- URL credential leaks (`https://user:token@host`) stripped in all redactors.
- Signed-URL query parameters (AWS X-Amz-Signature, Azure SAS, GCP signing, bearer / access_token / api_key) stripped from error URLs.
- Cross-host Authorization stripping on redirect (so the Bitbucket Basic header never reaches S3).
- Pipeline variable values masked as `KEY=***` when echoed back to the user.
- subprocess calls use `GIT_TERMINAL_PROMPT=0`, `GIT_ASKPASS=""`, `stdin=DEVNULL`, timeout — no interactive prompts can hang the MCP server.
- See `SECURITY.md` for the full posture and disclosure policy.

## Known Limitations

- Manual pipeline step approval requires the Bitbucket UI (API doesn't support it)
- Large log outputs may be truncated by the API
- Rate limiting is not handled (Bitbucket has generous limits)
- Manual pipeline step approval requires the Bitbucket UI (REST API doesn't support it).
- Diffs from `git_uncommitted_changes` are capped at 1 MiB (with truncation marker); untracked file list capped at 10 000 entries.
- Rate limiting is not handled explicitly (Bitbucket Cloud has generous limits; if you hit one, the MCP error envelope surfaces the 429 cleanly).
49 changes: 39 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The first time an outside contributor opens a PR, GitHub holds Actions execution

## CI and review automation

- **`syntax` (required)** — bash syntax + tooling sanity. Must pass before merge into `develop` or `main`. No secrets needed; runs on every push and PR including from forks.
- **`syntax` (required)** — bash syntax check, runtime-tooling presence check, and the Python test suite across Python 3.10 / 3.11 / 3.12. Must pass before merge into `develop` or `main`. No secrets needed; runs on every push and PR including from forks.
- **Claude code review (advisory)** — automated PR review using a subscription-bound OAuth token. Output appears as a PR comment; not a merge gate.
- **Claude security review (advisory)** — runs only on PRs targeting `main` or `develop`. Uses a metered Anthropic API key; also advisory.

Expand All @@ -34,24 +34,53 @@ The interactive `@claude` bot is available for maintainer-triggered triage. **On
- `detect_repo()` is the auto-detect entry point for resolving a repo from the local git remote. Reuse it; don't duplicate the parsing.
- Updates to commands should be reflected in `README.md`, `CLAUDE.md`, and the inline `cmd_help()` block together — they all describe the same surface from different angles.

## No personal data in public code
## Bash and Python are parallel implementations

Examples, fixtures, docs, and comments must use **fictional names** and **generic institutional abbreviations** — not real colleagues, organizations, repositories, or workspace slugs from the maintainer's day-to-day use. Real names land in public git history permanently. Default substitutes:
The `bb` bash script and the Python modules (`bb_api.py`, `bb_ops.py`, `mcp_server.py`) are **parallel implementations of the same Bitbucket REST contract**, not layered on top of each other:

- Personal names: Alice Garcia, Bob Jones, Carol Lee.
- Institutional / workspace abbreviations: `acme`, `widget-co`, `example-workspace`.
- Emails: `user@example.com` (RFC-reserved domain; cannot accidentally collide).
```
bb (bash) <--> Bitbucket REST API <--> bb_api / bb_ops (Python)
(single source
of truth)
```

The discipline is at the moment of writing — easier than scrubbing later.
The MCP server talks to Bitbucket directly via Python; it does not shell out to `bb` and parse its output.

**The parity rule:** when a test in `tests/test_bb_*.py` surfaces a defect in URL construction, body shape, parameter naming, or auth handling, the fix lands in the Python module *and* in `bb` if `bb` has the parallel logic. Tests verify the **correct** API contract — they do not pin existing buggy behaviour on either side. If you find yourself writing a test that only passes against the current implementation despite knowing the contract is wrong, that's a signal to fix the code, not pin the bug.

Practical workflow when adding or changing an endpoint call:

1. Write the test against what the Bitbucket API documents should happen.
2. Implement it correctly in the Python module.
3. Inspect `bb` — does it call the same endpoint with the same shape? If yes, mirror the fix.
4. Update both code paths in the same PR.

## Testing

Run the Python suite locally:

## Testing locally
```bash
python -m pip install pytest
python -m pytest tests/ -v
```

There's no unit-test suite (the script wraps a remote API; meaningful tests would need a live workspace). Before opening a PR, at minimum:
The suite mocks HTTP at `urllib.request` and does not need a live Bitbucket workspace. CI runs the same suite across Python 3.10 / 3.11 / 3.12 in addition to the `bash -n bb` syntax check.

Manual coverage for the bash side is still on you when touching `bb` — at minimum:

```bash
bash -n bb # syntax check, same as CI
bb help # smoke-test the dispatcher
bb repos # round-trip a real API call against your own workspace
```

Manual coverage against the surface you changed is the bar.
## No personal data in public code

Examples, fixtures, docs, and comments must use **fictional names** and **generic institutional abbreviations** — not real colleagues, organizations, repositories, or workspace slugs from the maintainer's day-to-day use. Real names land in public git history permanently. Default substitutes:

- Personal names: Alice Garcia, Bob Jones, Carol Lee.
- Institutional / workspace abbreviations: `acme`, `widget-co`, `example-workspace`.
- Emails: `user@example.com` (RFC-reserved domain; cannot accidentally collide).

The discipline is at the moment of writing — easier than scrubbing later.

Loading
Loading