v4.0.0
v4.0.0
bb v4.0.0 delivers a stabilized JSON machine contract, credential security hardening, interactive terminal safety guardrails, and full behavioral parity with Bitbucket Data Center.
This release represents a comprehensive overhaul of the CLI's testing and contract foundation (ADR-079). By replacing internal unit simulation mocks with live integration verification against real Bitbucket Data Center instances, dozens of latent defects have been resolved across pull request lifecycles, result pagination, and permission models.
Key Architectural Highlights
- Native Server CODEOWNERS Evaluation (ADR-080): Rather than relying on an internal regex-based parser that drifted from Bitbucket's rules,
bbnow queries Bitbucket's native UI evaluation endpoint (/rest/ui/latest/.../code-owners). This guarantees 100% parity with reviewer assignments in the Bitbucket Web UI. - Unified Paging Engine (ADR-074): All 18 internal service packages now walk pages through a unified engine (
openapi.PageThrough).--limitnow strictly caps the total number of items returned across all commands (permissions, comments, diffs, commits, branches), and--allis available to fetch entire sets. - Global Dry-Run Confidence Tiers (ADR-078): All 41 mutating commands report dry-run predictions tagged with a declared confidence tier (
certain,projected, orunsupported), validated against real permission matrices. - Full Webhook Field Parity (#522): Webhook commands now support all 9 Bitbucket API fields (including SSL verification, active status, and secrets) with automatic secret redaction on stdout.
Upgrading to v4: Automation & Scripting Checklist
If you consume bb inside CI/CD pipelines, shell scripts, or AI agent toolchains, review this checklist before upgrading:
- Audit JSON Parsers (
jq, Python) forcamelCase& Envelope Changes:- All machine output fields now match Bitbucket REST API camelCase (e.g.
pullRequestId,projectKey,repoSlug). meta.contractand contract version numbers have been removed; the CLI binary version is now reported undermeta.version.- On failure,
--jsonemits only a single error envelope to ensure clean stream decoding (ADR-075).
- All machine output fields now match Bitbucket REST API camelCase (e.g.
- Migrate Command-Line Credential Flags:
--tokenand--passwordCLI flags are retired to prevent secret exposure in process tables and shell histories.- Supply credentials via environment variables (
BB_TOKEN,BITBUCKET_TOKEN), the system keyring (bb auth login), or standard input (--token-stdin,--password-stdin).
- Account for Non-Interactive Guardrails in CI/CD:
- Destructive commands (
bb repo delete,bb auth gpg-key clear) now fail immediately if run in a non-interactive pipeline without--yes. bb repo deleterequires an explicit repository argument (PROJECT/slugor--repo) and will not delete a git-inferred working repository.- Implicit stdin reads now require an explicit
-(e.g.bb repo edit file.txt --content -). Pass--no-inputto guarantee scripts never hang on prompts.
- Destructive commands (
- Update Exit Code Handling:
- Invalid arguments and missing required flags now return exit code
2(validation), rather than exit code1(internal). - Transient network failures in
--dry-runreturn exit code10(transient), matching real executions.
- Invalid arguments and missing required flags now return exit code
- Update Go Module Import Paths (Go Developers):
- The Go module path has been renamed to
github.com/vriesdemichael/bitbucket-data-center-cli(#459).
- The Go module path has been renamed to
The complete list of breaking changes, commit references, and full change ledger is itemized below.
Changes since v3.5.2.
Compare: v3.5.2...v4.0.0
⚠ Breaking Changes
- refuse a default branch that does not exist (0ccc14f) —
branch default setandbranch model updatenow fail with exit 2 when the named branch does not exist in a repository that has branches. Previously the value was accepted and written. - stop counting a --dry-run invocation as live command reach (874989b) — docs/quality/command-reach.json is version 2. It gains known_dry_run_only and summary.dry_run_only_commands, and covered_commands drops from 233 to 217 for the same tests, because the sixteen dry-run-only commands were never covered in the first place.
- keep the reviewers when updating a pull request (14eb92b) — pr update now issues a GET before its PUT, so it costs one extra request. A caller that relied on the previous behaviour to clear reviewers must now pass an empty reviewer set explicitly, and no such caller could have been relying on it deliberately -- there was no flag for it.
- register --limit and --all on the commands that page (653e91b) — --limit and --all no longer exist on the 75 commands that never consumed them. An invocation like
bb repo delete --limit 5was previously accepted and the number ignored; it now fails with exit 2. The flags remain on the 38 commands that page. - retire --token and --password in favour of stdin and the environment (8be1a1c) — --token and --password no longer exist on
bb auth login, and --token no longer exists onbb ai mcp serve. Pipe the secret to --token-stdin or --password-stdin, or set BITBUCKET_TOKEN. For the MCP server, set BITBUCKET_TOKEN in the client env block -- "env": { "BITBUCKET_TOKEN": "${BB_MCP_TOKEN}" } -- which also keeps the agent on a narrower PAT than the one your own shell uses. - stop replaying POST and PATCH after a lost response (f3263bb) — POST and PATCH are no longer retried after a transport error or a 5xx. A mutation that previously succeeded on a second attempt now surfaces the failure, with a message saying the request may or may not have been applied. A 429 is still retried for every method.
- derive a preview's confidence from a stated tier (74c4907) — 43 dry-run items now report confidence
partialwhere they reportedfull. Nothing about those predictions changed except the honesty of the label: they are derived from partial state and never checked what they claimed. Automation gated on confidence == full will stop for those commands. Items also carry a newtierfield naming how the prediction was reached. - cap the three list commands the first --limit pass left out (72f0033) — --limit now truncates on
build status get,pr build-statusandpr activity, which previously returned every result whatever the flag said. Pass --all to keep receiving the complete set. - classify the archive stream failure as transient (642d810) —
repo archivenow reports kindtransientand exit 10 when the server is unreachable, instead of kindinternaland exit 1. - classify the twenty-two errors commands raise themselves (759a8ed) — 22 error paths across the permission, reviewer and reviewer-group commands now report kind
validationand exit 2 instead of kindinternaland exit 1 -- a missing project key, --project combined with --repo, and an unreadable config file or stdin among them. - require --matcher-type on restriction update, in both scopes (84d052a) —
branch restriction updateandproject branch-restriction updatenow require --matcher-type, and the project-scoped one also requires --type and --matcher-id. Omitting --matcher-type previously defaulted it to BRANCH and rewrote the restriction's matcher, so the value has to be stated. - plan every repository in a project, not the first hundred (519c2b2) —
bulk plannow covers every repository a selector matches rather than the first 100. A plan over a project with more than 100 repositories will contain more targets and therefore a different hash, andbulk applywill act on all of them -- which is what the policy asked for and what the previous plan silently omitted. - --limit limits on the commands whose service pages to exhaustion (df25e22) — --limit now truncates on the project and repository permission listings,
repo comment list,build required listandinsights report list. Those commands previously returned every result and used --limit only as a page size, so a caller that relied on receiving everything must pass --all. - a dry run and a real run agree that an unreachable host is transient (1547358) — A --dry-run that cannot reach Bitbucket now reports kind
transientand exit 10 instead of kindinternaland exit 1, matching what the same command already reported without --dry-run. - a forgotten required flag is validation, not a defect in bb (bf9f13d) — A missing required flag now reports kind
validationand exit 2 on 16 commands that reported kindinternaland exit 1.reviewer condition createreports validation for malformed JSON for the same reason. - require --count on update-approvers instead of defaulting to 2 (c51f7db) —
repo settings pull-requests update-approversnow requires --count. Omitting it previously wrote a required-approvers count of 2, silently lowering branch protection configured for more. - apply-suggestion needs write access, not read (2ed217f) —
pr comment apply-suggestion --dry-runnow pre-flights REPO_WRITE rather than REPO_READ, because applying a suggestion writes a commit. A read-only caller's dry run now fails with exit 3 instead of predicting success for an operation the server would refuse. - name every output field in camelCase, matching the API (381949f) — machine output field names are now camelCase throughout. In the envelope,
meta.bb_versionismeta.bbVersion,meta.limit_reachedismeta.limitReached, anderror.exit_codeiserror.exitCode. Command payloads change as each is modelled. - drop meta.contract, which never varied either (c2345b1) —
meta.contractis gone from the machine envelope. It was the constant string "bb.machine" in every response; a consumer asserting on it should assert on the envelope's shape instead --meta.bb_versionis present, and exactly one ofdataorerror. - remove the contract version, report the binary version instead (ad63272) — the bb.machine envelope no longer carries a
versionfield on either the success or failure path;meta.bb_versionreports the binary that produced the document instead, and compatibility is now signalled by the release major, so pin the installed version to pin the contract. - migration note for the bulk apply --json failure output (c955b57) —
bb bulk apply --jsonnow writes only the failure envelope when a run fails or is cancelled, so.datais absent; read.error.details.operation_idand fetch the artifact withbb bulk status <id> --json. Human output is unchanged. - reach the consumers that still read BB_* directly (1dcb4c5) — an inferred repository context is carried on AppConfig.RepoSlug rather than published to BITBUCKET_REPO_SLUG. A caller constructing AppConfig by hand must set RepoSlug; reading the variable at the point of use no longer works.
- carry the git-inferred repository as a value, and stop it faking an explicit target (3a810ba) — --yes no longer applies to a repository inferred from the git remote. bb repo delete --yes inside a checkout now refuses unless the repository is named.
- carry global flags as values, and blame the right input (f8ed1ef) — a validation failure caused by a global flag now names the flag rather than the BB_* variable it was previously written into.
- name list options for what they do (4f99ee9) — ListOptions.Limit is renamed across internal/services, to MaxResults where it caps the total and PageSize where it is the page size.
- register --no-input, and make the stdin guard see what it claimed to (dc966c2) — bb auth gpg-key clear --json now requires --yes; it previously skipped the confirmation and cleared the keys.
- confirm destructive commands, and never read stdin unasked (fdf6267) — bb repo delete requires the repository to be named, as an argument or --repo; --yes no longer applies to an inferred target. bb repo browse edit requires --content or --content -, and no longer reads stdin when --content is absent. bb reviewer condition create and update read stdin only when given a - argument.
All 284 changes
Features
- pr: ask Bitbucket who the code owners are (ac4980a)
- auth: retire --token and --password in favour of stdin and the environment (8be1a1c)
- dryrun: derive a preview's confidence from a stated tier (74c4907)
- cli: enforce the closed sets that arrive as positional arguments (a1c4b91)
- cli: make a flag enforce the set it advertises (fc46de0)
- repo,diff: model the last commands and gate on completeness (a2b217a)
- pr: model pull requests, comments, reviews and checkouts (fb6c6ed)
- project: model projects, permissions, restrictions, tasks and webhooks (c4f8665)
- auth: model status, credentials, tokens and GPG keys (930a8f4)
- build,branch,browse: model build statuses, branches and browse targets (6332adf)
- reviewer,reviewergroup: model conditions, groups and members (0424da5)
- insights: model reports and annotations (fa48e8e)
- webhook: model the output; the two renderings disagreed on version (9e57e8b)
- deployment,ai: model the output; a third spelling of repository retired (78bfd18)
- sshkey,update: model the output; record why bulk keeps its own schema (ba51e39)
- commit,search: model the output; share the commit and pull request shapes (821af01)
- result: shared commit model, and stop publishing nulls that cannot occur (c08281e)
- json: name every output field in camelCase, matching the API (381949f)
- ref,admin: model the output; add the shared repository shape (2dc9d85)
- tag: model the output, derive the schema from the model (3c64eef)
- json: drop meta.contract, which never varied either (c2345b1)
- cli: answer --describe with the command's own output schema (149bca0)
- json: remove the contract version, report the binary version instead (ad63272)
- cli: ask for a pull request's title and refs when a person is there (2a22c53)
- cli: confirm destructive commands, and never read stdin unasked (fdf6267)
Fixes
- live: two seeded repositories could not tell their commits apart (1404f17)
- ci: the script tests picked an interpreter this repository has, not one CI does (d45343c)
- test: a parallel test swapped the process's stdin (6f49018)
- style: a data race the tests could not reach until they ran in parallel (a5e6b08)
- test: a live suite that reports "ok (cached)" has not run (46ff8f7)
- live: the shared project answered to a filter a test relies on (fc69bab)
- bulk: a create response cannot say whether a secret is configured (e811c7c)
- webhook: two commands wrote a credential to stdout, and four fields were unreachable (3b52de3)
- mcp: keep ambient repo inference out of serve's scope (26ae59f)
- openapi: read the exception on a 400, and stop calling a spec gap internal (77f8849)
- search: refuse a role the repository listing cannot apply (cd9360c)
- reviewer-group: name the members, so a group can be created at all (2f0fc40)
- mcp: list_pull_requests promised a role filter Bitbucket never applied (a23c196)
- diff: a path with a space came back truncated, and CODEOWNERS lost it (a0b9d7c)
- pr: the update preview promised a change the command would not make (b4557c3)
- coverage: unit coverage counted only what a package tested of itself (3f6c0e7)
- reposettings: every approver update began with a request that could not work (60be9d2)
- dry-run: two previews compared against a shape Bitbucket does not send (8e3213c)
- paging: --limit did nothing in five more places, and the last loop is gone (597d97a)
- commit: --limit on compare did nothing either (f6f6977)
- services: ADR-074 applied to the half its guard could not see (e1fe8df)
- branch: --limit on restriction list did nothing (84acb7a)
- openapi: an empty page is not the end of the listing (f3d12b5)
- pr: --state closed asked Bitbucket for a state it does not have (2433714)
- pr: unapprove clears any status, so its dry run must predict one (b857cb8)
- pr: find the caller from the server, not the config, in the review dry runs (8860cf2)
- pr: a rebase with nothing to do is not an internal error (2d0ff66)
- pr: stop the review summary answering questions it did not measure (1ed1374)
- pr: count what a review summary can cheaply count, and drop two mock files (1bec1ac)
- tools: count an atomic attempt counter as fault injection (2269665)
- reviewer-group: resolve a name on update, and close the five masked commands (4b5c9c8)
- two heuristics from review, a numeric group name and a capped branch scan (1103232)
- tools: tell fault injection apart from paging in the mock inventory (5bdae79)
- reviewer: read the reviewers the default-reviewers endpoint actually returns (5044fe3)
- reviewer-group: delete by name, and say not_found when there is none (81e219e)
- branch: refuse a default branch that does not exist (0ccc14f)
- tools: report a call the scanner cannot read instead of skipping it (f03514b)
- settings: keep a default task's ref matchers when updating its description (e2e45f4)
- pr: resolve the current version when rebasing a pull request (8c6b56c)
- tools: stop counting a --dry-run invocation as live command reach (874989b)
- test: make the live harness write the content it was given (1059b72)
- pr: let pr update set the reviewers, not only keep them (adea79e)
- pr: resolve the current version when merging, declining or reopening (39eae37)
- pr: expand a CODEOWNERS reviewer-group entry instead of inventing a user (0c47b51)
- pr: open a pull request from a fork into the upstream repository (2f56e95)
- pr: keep the reviewers when updating a pull request (14eb92b)
- cli: repair the paging guard, which a name collision defeated (382df2f)
- cli: register --limit and --all on the commands that page (653e91b)
- transport: stop replaying POST and PATCH after a lost response (f3263bb)
- pr: predict a merge from mergeability, not from state alone (b055cca)
- dryrun: complete the three remaining capped existence scans, and cover the batch (586afa8)
- cli: cap the three list commands the first --limit pass left out (72f0033)
- repo: classify the archive stream failure as transient (642d810)
- diff: publish the summary Bitbucket sends, without narrowing it (907a1de)
- cli: classify the twenty-two errors commands raise themselves (759a8ed)
- branch: require --matcher-type on restriction update, in both scopes (84d052a)
- diff: read the stats summary Bitbucket sends, not the one the spec promised (a033f38)
- dryrun: ask whether the thing exists, instead of scanning a capped list (81f1cc6)
- bulk: plan every repository in a project, not the first hundred (519c2b2)
- cli: --limit limits on the commands whose service pages to exhaustion (df25e22)
- cli: a dry run and a real run agree that an unreachable host is transient (1547358)
- cli: a forgotten required flag is validation, not a defect in bb (bf9f13d)
- repo: require --count on update-approvers instead of defaulting to 2 (c51f7db)
- pr: apply-suggestion needs write access, not read (2ed217f)
- repo: send the enabled strategies with the default, so set-strategy works (0b5dabb)
- cli: correct three things the fixes themselves got wrong (1c4417c)
- cli: a nil pre-flight checker is skipped however it is spelled (950ee91)
- cli: enforce the sets the sweep missed, and widen the guard that missed them (eb3b615)
- cli: an empty enum value means "not given" again (1dbef1a)
- pr: restore the ff merge strategy, from one list instead of four (2dc9cab)
- repo: refuse --json with --output -, rather than ignoring it (e587b63)
- comment: decoding cleanly is not the same as carrying a comment (adf693d)
- comment: a read that fails must not become a write with no lock (845ab0a)
- comment: let a reviewer resolve an inline blocker (0e63248)
- quality: keep a raw client call under the operation-path guard (e6849cb)
- comment: decode a comment written in the web interface (63f305e)
- comment: anchor a commit comment so it can be read back (fb052da)
- comment: publish a reply once, and let bb list the comments it makes (a29a8cc)
- comment,settings: reach a reply's body, and stop inventing settings (530e52f)
- the rest of the review findings, and the gaps that let them through (28ea39c)
- five contract defects the review found, and a guard for the naming rule (1d0a528)
- repo,pr: read blame as a list, and keep the comment extras (2e9397d)
- repo: say how a file's bytes are encoded, instead of corrupting them (c9c7a8b)
- bulk,docs: spell the operation id one way (ca089e7)
- bulk: show cancelled counts in the human summary (82f15f2)
- errors: say why WithDetail does not reach through wrappers (45b697c)
- errors: copy on WithDetail instead of writing into the caller's error (da44eef)
- json: carry failure handles in error.details, and correct the jq claim (df808d9)
- bulk: emit one document under --json, and derive the kind guard (ed568c5)
- bulk: record cancellation per operation, not only between targets (f308583)
- three Phase 2 defects, each with a guard that would have caught it (50287c9)
- schemas: publish under the renamed host, and identify each release's copy (395d3a5)
- cli: reach the consumers that still read BB_* directly (1dcb4c5)
- git: release the inherited repository before trusting the ceiling (24034ec)
- cli: ask about flags before loading configuration, and tighten two tests (3330b38)
- cli: register --no-input, and make the stdin guard see what it claimed to (dc966c2)
Refactors
- repo: take the git backend and the prompt check as dependencies (fcd7bce)
- paging: the shared page carries a plain int, and two more caps land (8ff7c2f)
- services: browse and repository listings on the shared loop (f49fb31)
- services: four more paging loops, and one I broke on the way (b7ce2d3)
- services: three more paging loops, and two that were not caps at all (c7f4ce4)
- services: one repository check, not nine (4ce5d1c)
- services: five paging loops become calls to the shared one (f262c68)
- repo: drop the archive format guards the enum flag made unreachable (3f98c6f)
- move safederef out of the CLI layer, and guard the direction (4c49849)
- cli: delete what the ladder removal left behind (425e5af)
- dryrun: derive the preview summary instead of restating it (2a29621)
- cli: one place to read a pointer, not thirty-four (2438838)
- cli: one permission pre-flight, not eighty copies (ad559d6)
- one comment model, and stop deriving 222 schemas at startup (8daf525)
- dryrun: camelCase the keys inside a preview's target too (8f3a105)
- repo: render pull-request settings from the model, not the raw map (c7def64)
- dryrun: name the preview fields the way everything else is named (82a3532)
- docs: stop publishing per-command output schema files (b9cca70)
- cli: carry the git-inferred repository as a value, and stop it faking an explicit target (3a810ba)
- config: carry global flags as values, and blame the right input (f8ed1ef)
- auth: take the host override as a value, not a process mutation (f3d6c29)
- services: name list options for what they do (4f99ee9)
- git: split Guard so what it decides can be tested (bde265e)
- cli: decide interactivity in one place, with an escape hatch that is free (93997ee)
- mcp: derive DestructiveHint from Safe instead of declaring both (3e31717)
Docs
- release: refine v4.0.0 release notes preamble and upgrade guide (41a3605)
- adr-066: the promotion is a push, and the branch rule is enforced now (50f0a02)
- regenerate the ADR pages for ADR-082 (422d861)
- adr: ADR-081 takes effect after v4.0.0, and names what it still needs (ed70c1b)
- bulk: a secret belongs to an operation, not to a repository (eac68a1)
- webhook: where the secrets come from, and what bb refuses to print (52fbc27)
- openapi: the code-owners endpoint has no source, so record it as one (0b7ba84)
- adr: propose closing issues on release rather than on merge (b7230db)
- adr: reconcile the records, and make one-sided links fail (ac6d506)
- release: give a release room for prose, and stop shipping a stale changelog (1331583)
- agents: list two governance guards the table did not name (7a254ec)
- adr: regenerate the ADR-067 markdown export (295d682)
- cli: regenerate the command reference for the new --codeowners help (ac27890)
- adr-074: the rule is no page size, not a better-named one (17b5e27)
- openapi: register the three default-reviewer spec divergences (bd268b8)
- adr: supersede ADR-052, and turn the mock inventory into a work list (5f4ddf5)
- agents: write down how a bug gets fixed here (1baaa18)
- regenerate the command reference (e59cabe)
- adr-076: a human key=value label is the field name too (828da66)
- diff: say that a stat run can come back without a summary (316206e)
- result: name the third answer, and fix two comments naming a renamed field (ebdc6ef)
- readme: the machine-mode example was missing three fields (5251f56)
- bulk: migration note for the bulk apply --json failure output (c955b57)
- llms: reshape llms.txt into an agent setup guide (20d8b14)
- adr: record the phase 1 decisions and the guards that hold them (648538c)
- adr: state the invariant rather than narrating the change (a52cdcb)
- drop the two references to things this branch removed (d2f44fd)
- adr: record the governance-test discipline, and retire "CI-safe" (6840b0f)
Tests
- live: the permission tests run in parallel, and three things that stopped them (82ac1ca)
- live: bb waits out a real Bitbucket rate limit, and Retry-After is not dead code (bc9ae75)
- the suite ran one test at a time because a helper set an environment variable (c73505e)
- every test that can run in parallel now does, and auth stops waiting on DNS (f58a95b)
- repo: seven clone tests take their configuration instead of publishing it (7a0bbaa)
- live: name the isolation each test needs, and stop paying for commit ids (906043d)
- webhook: the branches a live payload cannot hold at once (40e7515)
- live: a server started inside a clone can still reach a sibling (37d5c1a)
- openapi,network: cover the branches the two fixes added (cc057fa)
- network: prove the recorder records, and gives the body back (aef9681)
- tools: record what Bitbucket answers, rather than assuming it (f98d4c8)
- prove the unreached guard guards, and the code-owners refusals refuse (700a222)
- live: pr status read the branch of whatever checkout the suite ran in (9245f1c)
- pr: the last suite that read an id out of a reply it had written (b1b30f3)
- pr checkout, against a real clone rather than a recording stub (c27775d)
- a helper with one caller is not a helper (5b36a87)
- CODEOWNERS had no live coverage at all (533808e)
- an inline comment and a reply, read back from the thread they joined (fea62e4)
- where the permission line falls, drawn against a real account (14c7d80)
- a pull request from a real fork, checked out into a real clone (0494135)
- the property counters, and the endpoint that does not send them (f9c7c56)
- the unreachable URLs became listeners that say when they are reached (c5e4c76)
- a fork cloned for real, and the remote git ended up with (b6aad33)
- pr status asked a real dashboard which reviews are still owed (4758117)
- a failing bulk apply, refused by Bitbucket rather than by a fixture (cc58c2f)
- two service methods only a mock had ever called (a76fc28)
- the MCP review tools read a timeline Bitbucket wrote (b588231)
- the permission aliases and the settings tree met a real Bitbucket (e0c9471)
- status-taxonomy is empty, and the bulk runner met a real server (542c666)
- thirty-seven pull request suites, and the shared guard they left behind (78a38fd)
- the canned responses are gone, and two of them were not (1ae21e9)
- fifty suites whose only witness was a handwritten Bitbucket (cf366ad)
- the fork payload, and three servers that were only ever a URL (2784f85)
- three previews whose fixture decided the thing being predicted (9b375dc)
- services: zero means the default, and nothing said so (0085411)
- cli: three suites that proved a formatter agreed with its own fixture (44586a0)
- services: one contract for eighteen paging conversions (0fa0fc1)
- the servers that were only ever a URL, and the flags nothing had driven (3ec7905)
- cli: the dry-run prechecks go live, and the mock refused too much (8276501)
- cli: the resource previews go live, and one more was unreachable (ee18f11)
- cli: the dry-run predictions go live, and one was predicting the impossible (13e89d3)
- move another sixty mocks out, and say why the rest stay (913a17c)
- quality,openapi: one status mapping, tested once, guarded (a42ea33)
- mcp: pin that a review status replaces the one held, rather than joining it (3e5d099)
- mcp,transport: cover the review surface an agent reaches and the transport's two Bitbucket claims (1737aff)
- comment: prove resolving a comment works, then drop the mocks that assumed it (5bc6f99)
- api: take the passthrough's own decisions over their inputs, the rest live (f96daec)
- governance: delete the mocked governance CLI suite for what it could not prove (1d20b95)
- live: migrate auth identity, the command-path smoke tests and the last dry run (56d08a9)
- live: migrate the pull request human output mocks (227ba34)
- live: migrate the pull request human output mocks (f98928a)
- live: migrate the repository settings and permission listing mocks (5cf1fbb)
- live: migrate the diff, comment and quality CLI output mocks (6432f6e)
- live: migrate the dry-run preview mocks, and check they leave no trace (6d17d29)
- live: migrate the tag CLI surface and the empty listings (17738a6)
- live: migrate the branch command surfaces from root_test.go (c089b64)
- live: migrate the admin health mocks, limited auth included (812e104)
- delete the last eight tests that cannot fail (7fa09fe)
- cli: delete twenty-six governance tests that assert nothing (23a3031)
- live: prove a webhook rename keeps the secret the server never echoes (67052cb)
- live: migrate the path escaping and paging mocks (a05975d)
- services: split validation from authorization mapping in three services (78a1266)
- live: migrate what bb api does with a response (4d1f64a)
- live: migrate the pull request comment anchors, watch and auto-merge mocks (f8adce8)
- cli: delete nineteen tests for a command that no longer exists (017e54c)
- live: migrate the diff output modes, and make two validation tests assert (651b73a)
- live: migrate the comment anchoring, reply and resolution mocks (2676ed4)
- live: migrate the settings write mocks, and make a validation test assert (a392b10)
- live: migrate the pull request mergeability, draft and build status mocks (dee7bb8)
- tools: index every mocked Bitbucket server and record what it assumes (f0f997e)
- live: run the sixteen dry-run-only commands for real (ac39f38)
- live: pin the five pull-request fixes against a real server (4d25ba3)
- live: pass --matcher-type where restriction update now requires it (a272d84)
- repo: pin the merge-config shapes enabledStrategiesWith is given (5c7e7d0)
- comment: cover the anchor paths, and fail when a field is dropped (996c8c4)
- live: assert what only a real server can show (6e2d3d0)
- cover the converters, which is where the payload is actually decided (2be4d79)
- live: assert the repository group listing too, not only the project one (702a12e)
- cover the three gaps the patch gate found in the folded-in work (450e941)
- git: cover the branch where there is no repository to place (1a19d90)
- git: make the ambient repository unreachable, not merely watched (d810384)
- git: stop sibling worktrees failing the ambient config guard (816e452)
- cover FillMissing's prompting path (796b75e)
- cover the prompting paths the patch gate found bare (65c59cb)
- live: name the repository and confirm when deleting it (548d873)
- governance: catch a record that cites a record that was never written (d4ad619)
- mcp: cover the annotation fallback in toolSpec (09ce36a)
- mcp: check the safety flag against the annotations in both directions (4738f35)
- verify the governance guards fail, and replace the one that could not (de98974)
Build
- lint: stop rebuilding golangci-lint on every CI run (3041a95)
CI
- the release flow reads Go, not Python (10f57b2)
- refuse a pull request into main that is not part of the release flow (fbf9fbd)
- release: ask the remote which versions exist before publishing one (8a2aa30)
- let the Codecov poll actually retry (9e19b87)
- release: capture the whole BREAKING CHANGE footer, not its first line (bc50f8c)
- pin setup-uv to an exact version, not a major tag (6aabc79)
Chores
- deps: bump the gomod-minor-patch group with 10 updates (e194543)
- release: drop AUR publishing rather than keep pretending (30ed55e)
- correct three mock totals I inferred instead of reading (23f8d0d)
- docs: two webhook endpoints are uncovered because nothing calls them (9dd3236)
- docs: the two deleted webhook methods leave the operation-path index (4753bfa)
- retire the old slug, and make it stay retired (a028530)
- rename the Go module to match the repository (86ee21c)
- deps: bump astral-sh/setup-uv from 7 to 10 (ec18c4d)
- hooks: drop the retired CI-safe vocabulary from the pre-commit job name (18c71cc)
- deps: bump actions/download-artifact from 7 to 8 (14c478c)
- fix an ADR that named a filename as a tool, and two CI cascade faults (e5e0026)
- deps: bump the gomod-minor-patch group with 10 updates (adde115)
- deps: bump anchore/sbom-action from 0.24.0 to 0.24.2 (816839b)
Other
- Revert to a project per test: sharing broke four tests to save 0.8% (f7cfcbb)