diff --git a/CHANGELOG.md b/CHANGELOG.md index f2db89f..6d7ee45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to Code Context Control (C3) are documented here. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.158.3] - 2026-10-23 + +### Fixed — `c3_bitbucket list_prs` takes `author` and `reviewer` + +The handler and the Bitbucket client could already filter `list_prs` by +author and by reviewer (Bitbucket's `role.N`/`username.N` query), but the +MCP tool's signature had neither argument, so a client that passed one was +refused with pydantic's "Unexpected keyword argument" before the handler ran. +Work Agents' Bitbucket watchers and its Work page asked for "my pull +requests" that way and failed on every poll. `c3_bitbucket` now takes +`author` and `reviewer` (Bitbucket user names) and passes them through, in +text and in `format="json"`. + ## [2.158.2] - 2026-10-21 ### Fixed — approval requests that never reached the desk or the phone diff --git a/cli/c3.py b/cli/c3.py index 0c44411..fd8e630 100644 --- a/cli/c3.py +++ b/cli/c3.py @@ -91,7 +91,7 @@ # Config CONFIG_DIR = ".c3" CONFIG_FILE = ".c3/config.json" -__version__ = "2.158.2" +__version__ = "2.158.3" # The PreToolUse matcher for native subagent calls (installer and hub migration). AGENT_MATCHER = "Agent|Task" diff --git a/cli/mcp_server.py b/cli/mcp_server.py index 0922868..88158b7 100644 --- a/cli/mcp_server.py +++ b/cli/mcp_server.py @@ -1277,6 +1277,8 @@ async def c3_bitbucket( pr_id: int = 0, branch: str = "", state: str = "OPEN", + author: str = "", + reviewer: str = "", title: str = "", body: str = "", from_branch: str = "", @@ -1304,7 +1306,8 @@ async def c3_bitbucket( default branch, browse and clone URLs), readme ([at=branch/commit] — the root README, markdown preferred), get_file (path [at] — one text file, 256 KB cap, binary refused), - list_prs, get_pr, get_pr_diff, get_pr_activities, get_pr_commits, + list_prs ([author] [reviewer] — Bitbucket user names), get_pr, + get_pr_diff, get_pr_activities, get_pr_commits, pr_issues (pr_id — Jira issues Bitbucket's Jira integration links to the PR), create_pr, update_pr (pr_id + any of title/description/reviewers/to_branch — unchanged fields are preserved; reviewers is the FULL comma-separated @@ -1335,6 +1338,7 @@ def finalize(fname, fargs, fresp, fsumm, **kw): return await asyncio.to_thread( handle_bitbucket, action, svc, finalize, project=project, repo=repo, pr_id=pr_id, branch=branch, state=state, + author=author, reviewer=reviewer, title=title, body=body, from_branch=from_branch, to_branch=to_branch, description=description, reviewers=reviewers, name=name, url=url, events=events, start_point=start_point, commit=commit, diff --git a/pyproject.toml b/pyproject.toml index 6b61f85..037f257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-context-control" -version = "2.158.2" +version = "2.158.3" description = "Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub." readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_bitbucket_tool.py b/tests/test_bitbucket_tool.py index a9f4edf..5ffa74f 100644 --- a/tests/test_bitbucket_tool.py +++ b/tests/test_bitbucket_tool.py @@ -1,11 +1,13 @@ """Tests for cli/tools/bitbucket.py — handle_bitbucket dispatch.""" from __future__ import annotations +import asyncio import json import sys import tempfile import unittest from pathlib import Path +from types import SimpleNamespace from unittest import mock REPO_ROOT = Path(__file__).resolve().parent.parent @@ -456,6 +458,24 @@ def test_list_branches_passes_filter_and_order(self): self.assertEqual(kw["filter_text"], "feat") self.assertEqual(kw["order"], "modification") + def test_list_prs_passes_author_and_reviewer(self): + finalize, _ = _captured_finalize() + tool.handle_bitbucket("list_prs", self.svc, finalize, author="alice", reviewer="bob") + kw = next(c[2] for c in self.client.calls if c[0] == "list_prs") + self.assertEqual((kw["author"], kw["reviewer"]), ("alice", "bob")) + + +class TestBitbucketMcpTool(unittest.TestCase): + def test_list_prs_author_and_reviewer_reach_the_handler(self): + from cli import mcp_server + schema = asyncio.run(mcp_server.mcp.get_tool("c3_bitbucket")).parameters["properties"] + self.assertLessEqual({"author", "reviewer"}, set(schema)) + ctx = SimpleNamespace(request_context=SimpleNamespace(lifespan_context=object())) + with mock.patch.object(tool, "handle_bitbucket", return_value="ok") as handle: + asyncio.run(mcp_server.c3_bitbucket("list_prs", author="alice", reviewer="bob", ctx=ctx)) + self.assertEqual((handle.call_args.kwargs["author"], handle.call_args.kwargs["reviewer"]), + ("alice", "bob")) + def test_cap_clamps_single_overlong_line(self): # Issue 7: one line with no newlines must still be clamped by chars. long_line = "x" * 200_000