From 24bee5e60f95cff44da80282a8ee455d7f0cb51b Mon Sep 17 00:00:00 2001 From: He Wang Date: Tue, 21 Jul 2026 19:21:52 +0800 Subject: [PATCH] feat(review): enrich review attribution metadata feat: - Show agent identity before the skill fingerprint in review summaries - Prefer configured model names for display without changing ACP selection - Preserve the GitHub repository attribution and place model usage last fix: - Update JSON retry generics for Python 3.12 Ruff compliance docs: - Document display-only model fallback behavior test: - Cover model precedence, agent labels, footer layout, and ACP isolation --- .env.example | 2 + AGENTS.md | 4 ++ README.md | 4 ++ src/code_review_bot/agent/json_extract.py | 5 +- src/code_review_bot/config.py | 15 +++++- src/code_review_bot/review/orchestrator.py | 6 ++- .../review/publish/formatter.py | 21 ++++++-- src/code_review_bot/review/runner.py | 26 ++++++--- src/code_review_bot/skill/protocol.py | 1 + tests/test_agent_factory.py | 25 +++++++++ tests/test_config.py | 54 +++++++++++++++++++ tests/test_review_publisher.py | 51 ++++++++++++++++-- tests/test_review_runner.py | 22 ++++++++ 13 files changed, 217 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 1acef26..6b8680c 100644 --- a/.env.example +++ b/.env.example @@ -97,6 +97,8 @@ ACP_AGENT_TYPE=claude # [optional] Model override. OpenCode expects the full provider/model ID. # ACP_MODEL= +# Review comments display ACP_MODEL when set, otherwise ANTHROPIC_MODEL for Claude or +# OPENCODE_MODEL for OpenCode. These display fallbacks do not change ACP model selection. # [optional] Max bytes per ACP JSON frame (default: 10485760 = 10 MB). # ACP_STREAM_LIMIT=10485760 diff --git a/AGENTS.md b/AGENTS.md index c8883ea..b6244fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,6 +66,10 @@ src/code_review_bot/ 5. `ReviewPublisher.publish()` posts inline diff comments and formats the summary, storing the new fingerprint set in hidden metadata for the next run. GitLab and GitHub without automatic approval post it as a note; GitHub with automatic approval defers it to the final review body. + Summary notes end with a GitHub repository attribution line containing the formatted agent type + and skill fingerprint, followed by the model and token-usage line. The displayed model uses + `ACP_MODEL` when set, otherwise `ANTHROPIC_MODEL` for Claude or `OPENCODE_MODEL` for OpenCode; + these fallbacks do not change ACP model selection. 6. When `AUTO_APPROVE_ON_CLEAN_REVIEW=true` (and not in `--debug` mode), the orchestrator approves the change request if no new findings were published, or revokes approval when new findings exist (GitLab: approve/unapprove API; GitHub: `APPROVE` / `REQUEST_CHANGES` review diff --git a/README.md b/README.md index 140a1b5..12d3c2a 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,10 @@ See `.env.example` for the full list with descriptions. | `ACP_VERBOSE` | no | `true` | Log ACP input prompts, tool calls, and streamed agent messages | | `ACP_STREAM_LIMIT` | no | `10485760` (10 MB) | Max bytes for one ACP newline-delimited JSON frame | +Review comments display `ACP_MODEL` when set. Otherwise, they fall back to `ANTHROPIC_MODEL` for +Claude or `OPENCODE_MODEL` for OpenCode. These fallbacks are display-only and do not change the +existing ACP model-selection behavior. + The review workspace checks out the target branch. Each ACP agent therefore discovers project instructions and configuration through its own native rules, without the bot maintaining a list of recognized files. The source branch is retained only as `refs/code-review/source` and is diff --git a/src/code_review_bot/agent/json_extract.py b/src/code_review_bot/agent/json_extract.py index a5dc95b..35ca6e0 100644 --- a/src/code_review_bot/agent/json_extract.py +++ b/src/code_review_bot/agent/json_extract.py @@ -4,14 +4,13 @@ import logging import re from collections.abc import Awaitable, Callable -from typing import Any, TypeVar +from typing import Any import json_repair from pydantic import BaseModel, ValidationError logger = logging.getLogger(__name__) -SchemaT = TypeVar("SchemaT", bound=BaseModel) JSON_BLOCK_RE = re.compile(r"```(?:json)?\s*(?P\{.*?\})\s*```", re.DOTALL) TextRunner = Callable[[str], Awaitable[str]] @@ -19,7 +18,7 @@ _CTRL_ESCAPE = {"\n": "\\n", "\r": "\\r", "\t": "\\t", "\b": "\\b", "\f": "\\f"} -async def complete_json_with_retries( +async def complete_json_with_retries[SchemaT: BaseModel]( prompt: str, schema: type[SchemaT], runner: TextRunner, diff --git a/src/code_review_bot/config.py b/src/code_review_bot/config.py index 5256139..e3d3c91 100644 --- a/src/code_review_bot/config.py +++ b/src/code_review_bot/config.py @@ -51,6 +51,8 @@ class Settings(BaseSettings): description="Required when ACP_AGENT_TYPE is not built-in. Optional override for built-in.", ) acp_model: str | None = None + anthropic_model: str | None = None + opencode_model: str | None = None acp_stream_limit: int = Field( default=10 * 1024 * 1024, description="Max bytes for one ACP newline-delimited JSON frame from the coding agent.", @@ -141,6 +143,17 @@ def resolved_acp_args(self) -> list[str]: args=self.acp_args, )[1] + @property + def review_model_name(self) -> str | None: + """Resolve the model label shown in review output without changing agent selection.""" + if self.acp_model: + return self.acp_model + if self.acp_agent_type == "claude": + return self.anthropic_model + if self.acp_agent_type == "opencode": + return self.opencode_model + return None + @field_validator("acp_agent_type", mode="before") @classmethod def normalize_acp_agent_type(cls, value: object) -> str: @@ -158,7 +171,7 @@ def blank_command_is_none(cls, value: object) -> str | None: return None return str(value).strip() if isinstance(value, str) else value - @field_validator("acp_model", mode="before") + @field_validator("acp_model", "anthropic_model", "opencode_model", mode="before") @classmethod def blank_model_is_none(cls, value: object) -> str | None: if value is None: diff --git a/src/code_review_bot/review/orchestrator.py b/src/code_review_bot/review/orchestrator.py index 4852111..0ec305b 100644 --- a/src/code_review_bot/review/orchestrator.py +++ b/src/code_review_bot/review/orchestrator.py @@ -129,7 +129,11 @@ async def review_change_request(self, cr_id: str) -> ReviewOutcome: logger.info("Running skill name=%s version=%s", skill.name, skill.version) agent = build_coding_agent(self.settings, review_workspace / "source") - result = await CodingAgentReviewRunner(agent).review(skill, task_context) + result = await CodingAgentReviewRunner( + agent, + agent_type=self.settings.acp_agent_type, + configured_model=self.settings.review_model_name, + ).review(skill, task_context) file_filter = FileFilter(task_context.excluded_patterns, task_context.included_patterns) result.findings, file_excluded = file_filter.filter_findings(result.findings) diff --git a/src/code_review_bot/review/publish/formatter.py b/src/code_review_bot/review/publish/formatter.py index 1527932..f36d9c3 100644 --- a/src/code_review_bot/review/publish/formatter.py +++ b/src/code_review_bot/review/publish/formatter.py @@ -6,8 +6,15 @@ from code_review_bot.skill.protocol import Finding, RuntimeMetadata, count_findings_by_severity BOT_METADATA_PREFIX = "