#
tokens: 48421/50000 4/353 files (page 19/25)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 19 of 25. Use http://codebase.md/beehiveinnovations/gemini-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .claude
│   ├── commands
│   │   └── fix-github-issue.md
│   └── settings.json
├── .coveragerc
├── .dockerignore
├── .env.example
├── .gitattributes
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── documentation.yml
│   │   ├── feature_request.yml
│   │   └── tool_addition.yml
│   ├── pull_request_template.md
│   └── workflows
│       ├── docker-pr.yml
│       ├── docker-release.yml
│       ├── semantic-pr.yml
│       ├── semantic-release.yml
│       └── test.yml
├── .gitignore
├── .pre-commit-config.yaml
├── AGENTS.md
├── CHANGELOG.md
├── claude_config_example.json
├── CLAUDE.md
├── clink
│   ├── __init__.py
│   ├── agents
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── claude.py
│   │   ├── codex.py
│   │   └── gemini.py
│   ├── constants.py
│   ├── models.py
│   ├── parsers
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── claude.py
│   │   ├── codex.py
│   │   └── gemini.py
│   └── registry.py
├── code_quality_checks.ps1
├── code_quality_checks.sh
├── communication_simulator_test.py
├── conf
│   ├── __init__.py
│   ├── azure_models.json
│   ├── cli_clients
│   │   ├── claude.json
│   │   ├── codex.json
│   │   └── gemini.json
│   ├── custom_models.json
│   ├── dial_models.json
│   ├── gemini_models.json
│   ├── openai_models.json
│   ├── openrouter_models.json
│   └── xai_models.json
├── config.py
├── docker
│   ├── README.md
│   └── scripts
│       ├── build.ps1
│       ├── build.sh
│       ├── deploy.ps1
│       ├── deploy.sh
│       └── healthcheck.py
├── docker-compose.yml
├── Dockerfile
├── docs
│   ├── adding_providers.md
│   ├── adding_tools.md
│   ├── advanced-usage.md
│   ├── ai_banter.md
│   ├── ai-collaboration.md
│   ├── azure_openai.md
│   ├── configuration.md
│   ├── context-revival.md
│   ├── contributions.md
│   ├── custom_models.md
│   ├── docker-deployment.md
│   ├── gemini-setup.md
│   ├── getting-started.md
│   ├── index.md
│   ├── locale-configuration.md
│   ├── logging.md
│   ├── model_ranking.md
│   ├── testing.md
│   ├── tools
│   │   ├── analyze.md
│   │   ├── apilookup.md
│   │   ├── challenge.md
│   │   ├── chat.md
│   │   ├── clink.md
│   │   ├── codereview.md
│   │   ├── consensus.md
│   │   ├── debug.md
│   │   ├── docgen.md
│   │   ├── listmodels.md
│   │   ├── planner.md
│   │   ├── precommit.md
│   │   ├── refactor.md
│   │   ├── secaudit.md
│   │   ├── testgen.md
│   │   ├── thinkdeep.md
│   │   ├── tracer.md
│   │   └── version.md
│   ├── troubleshooting.md
│   ├── vcr-testing.md
│   └── wsl-setup.md
├── examples
│   ├── claude_config_macos.json
│   └── claude_config_wsl.json
├── LICENSE
├── providers
│   ├── __init__.py
│   ├── azure_openai.py
│   ├── base.py
│   ├── custom.py
│   ├── dial.py
│   ├── gemini.py
│   ├── openai_compatible.py
│   ├── openai.py
│   ├── openrouter.py
│   ├── registries
│   │   ├── __init__.py
│   │   ├── azure.py
│   │   ├── base.py
│   │   ├── custom.py
│   │   ├── dial.py
│   │   ├── gemini.py
│   │   ├── openai.py
│   │   ├── openrouter.py
│   │   └── xai.py
│   ├── registry_provider_mixin.py
│   ├── registry.py
│   ├── shared
│   │   ├── __init__.py
│   │   ├── model_capabilities.py
│   │   ├── model_response.py
│   │   ├── provider_type.py
│   │   └── temperature.py
│   └── xai.py
├── pyproject.toml
├── pytest.ini
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── run_integration_tests.ps1
├── run_integration_tests.sh
├── run-server.ps1
├── run-server.sh
├── scripts
│   └── sync_version.py
├── server.py
├── simulator_tests
│   ├── __init__.py
│   ├── base_test.py
│   ├── conversation_base_test.py
│   ├── log_utils.py
│   ├── test_analyze_validation.py
│   ├── test_basic_conversation.py
│   ├── test_chat_simple_validation.py
│   ├── test_codereview_validation.py
│   ├── test_consensus_conversation.py
│   ├── test_consensus_three_models.py
│   ├── test_consensus_workflow_accurate.py
│   ├── test_content_validation.py
│   ├── test_conversation_chain_validation.py
│   ├── test_cross_tool_comprehensive.py
│   ├── test_cross_tool_continuation.py
│   ├── test_debug_certain_confidence.py
│   ├── test_debug_validation.py
│   ├── test_line_number_validation.py
│   ├── test_logs_validation.py
│   ├── test_model_thinking_config.py
│   ├── test_o3_model_selection.py
│   ├── test_o3_pro_expensive.py
│   ├── test_ollama_custom_url.py
│   ├── test_openrouter_fallback.py
│   ├── test_openrouter_models.py
│   ├── test_per_tool_deduplication.py
│   ├── test_planner_continuation_history.py
│   ├── test_planner_validation_old.py
│   ├── test_planner_validation.py
│   ├── test_precommitworkflow_validation.py
│   ├── test_prompt_size_limit_bug.py
│   ├── test_refactor_validation.py
│   ├── test_secaudit_validation.py
│   ├── test_testgen_validation.py
│   ├── test_thinkdeep_validation.py
│   ├── test_token_allocation_validation.py
│   ├── test_vision_capability.py
│   └── test_xai_models.py
├── systemprompts
│   ├── __init__.py
│   ├── analyze_prompt.py
│   ├── chat_prompt.py
│   ├── clink
│   │   ├── codex_codereviewer.txt
│   │   ├── default_codereviewer.txt
│   │   ├── default_planner.txt
│   │   └── default.txt
│   ├── codereview_prompt.py
│   ├── consensus_prompt.py
│   ├── debug_prompt.py
│   ├── docgen_prompt.py
│   ├── generate_code_prompt.py
│   ├── planner_prompt.py
│   ├── precommit_prompt.py
│   ├── refactor_prompt.py
│   ├── secaudit_prompt.py
│   ├── testgen_prompt.py
│   ├── thinkdeep_prompt.py
│   └── tracer_prompt.py
├── tests
│   ├── __init__.py
│   ├── CASSETTE_MAINTENANCE.md
│   ├── conftest.py
│   ├── gemini_cassettes
│   │   ├── chat_codegen
│   │   │   └── gemini25_pro_calculator
│   │   │       └── mldev.json
│   │   ├── chat_cross
│   │   │   └── step1_gemini25_flash_number
│   │   │       └── mldev.json
│   │   └── consensus
│   │       └── step2_gemini25_flash_against
│   │           └── mldev.json
│   ├── http_transport_recorder.py
│   ├── mock_helpers.py
│   ├── openai_cassettes
│   │   ├── chat_cross_step2_gpt5_reminder.json
│   │   ├── chat_gpt5_continuation.json
│   │   ├── chat_gpt5_moon_distance.json
│   │   ├── consensus_step1_gpt5_for.json
│   │   └── o3_pro_basic_math.json
│   ├── pii_sanitizer.py
│   ├── sanitize_cassettes.py
│   ├── test_alias_target_restrictions.py
│   ├── test_auto_mode_comprehensive.py
│   ├── test_auto_mode_custom_provider_only.py
│   ├── test_auto_mode_model_listing.py
│   ├── test_auto_mode_provider_selection.py
│   ├── test_auto_mode.py
│   ├── test_auto_model_planner_fix.py
│   ├── test_azure_openai_provider.py
│   ├── test_buggy_behavior_prevention.py
│   ├── test_cassette_semantic_matching.py
│   ├── test_challenge.py
│   ├── test_chat_codegen_integration.py
│   ├── test_chat_cross_model_continuation.py
│   ├── test_chat_openai_integration.py
│   ├── test_chat_simple.py
│   ├── test_clink_claude_agent.py
│   ├── test_clink_claude_parser.py
│   ├── test_clink_codex_agent.py
│   ├── test_clink_gemini_agent.py
│   ├── test_clink_gemini_parser.py
│   ├── test_clink_integration.py
│   ├── test_clink_parsers.py
│   ├── test_clink_tool.py
│   ├── test_collaboration.py
│   ├── test_config.py
│   ├── test_consensus_integration.py
│   ├── test_consensus_schema.py
│   ├── test_consensus.py
│   ├── test_conversation_continuation_integration.py
│   ├── test_conversation_field_mapping.py
│   ├── test_conversation_file_features.py
│   ├── test_conversation_memory.py
│   ├── test_conversation_missing_files.py
│   ├── test_custom_openai_temperature_fix.py
│   ├── test_custom_provider.py
│   ├── test_debug.py
│   ├── test_deploy_scripts.py
│   ├── test_dial_provider.py
│   ├── test_directory_expansion_tracking.py
│   ├── test_disabled_tools.py
│   ├── test_docker_claude_desktop_integration.py
│   ├── test_docker_config_complete.py
│   ├── test_docker_healthcheck.py
│   ├── test_docker_implementation.py
│   ├── test_docker_mcp_validation.py
│   ├── test_docker_security.py
│   ├── test_docker_volume_persistence.py
│   ├── test_file_protection.py
│   ├── test_gemini_token_usage.py
│   ├── test_image_support_integration.py
│   ├── test_image_validation.py
│   ├── test_integration_utf8.py
│   ├── test_intelligent_fallback.py
│   ├── test_issue_245_simple.py
│   ├── test_large_prompt_handling.py
│   ├── test_line_numbers_integration.py
│   ├── test_listmodels_restrictions.py
│   ├── test_listmodels.py
│   ├── test_mcp_error_handling.py
│   ├── test_model_enumeration.py
│   ├── test_model_metadata_continuation.py
│   ├── test_model_resolution_bug.py
│   ├── test_model_restrictions.py
│   ├── test_o3_pro_output_text_fix.py
│   ├── test_o3_temperature_fix_simple.py
│   ├── test_openai_compatible_token_usage.py
│   ├── test_openai_provider.py
│   ├── test_openrouter_provider.py
│   ├── test_openrouter_registry.py
│   ├── test_parse_model_option.py
│   ├── test_per_tool_model_defaults.py
│   ├── test_pii_sanitizer.py
│   ├── test_pip_detection_fix.py
│   ├── test_planner.py
│   ├── test_precommit_workflow.py
│   ├── test_prompt_regression.py
│   ├── test_prompt_size_limit_bug_fix.py
│   ├── test_provider_retry_logic.py
│   ├── test_provider_routing_bugs.py
│   ├── test_provider_utf8.py
│   ├── test_providers.py
│   ├── test_rate_limit_patterns.py
│   ├── test_refactor.py
│   ├── test_secaudit.py
│   ├── test_server.py
│   ├── test_supported_models_aliases.py
│   ├── test_thinking_modes.py
│   ├── test_tools.py
│   ├── test_tracer.py
│   ├── test_utf8_localization.py
│   ├── test_utils.py
│   ├── test_uvx_resource_packaging.py
│   ├── test_uvx_support.py
│   ├── test_workflow_file_embedding.py
│   ├── test_workflow_metadata.py
│   ├── test_workflow_prompt_size_validation_simple.py
│   ├── test_workflow_utf8.py
│   ├── test_xai_provider.py
│   ├── transport_helpers.py
│   └── triangle.png
├── tools
│   ├── __init__.py
│   ├── analyze.py
│   ├── apilookup.py
│   ├── challenge.py
│   ├── chat.py
│   ├── clink.py
│   ├── codereview.py
│   ├── consensus.py
│   ├── debug.py
│   ├── docgen.py
│   ├── listmodels.py
│   ├── models.py
│   ├── planner.py
│   ├── precommit.py
│   ├── refactor.py
│   ├── secaudit.py
│   ├── shared
│   │   ├── __init__.py
│   │   ├── base_models.py
│   │   ├── base_tool.py
│   │   ├── exceptions.py
│   │   └── schema_builders.py
│   ├── simple
│   │   ├── __init__.py
│   │   └── base.py
│   ├── testgen.py
│   ├── thinkdeep.py
│   ├── tracer.py
│   ├── version.py
│   └── workflow
│       ├── __init__.py
│       ├── base.py
│       ├── schema_builders.py
│       └── workflow_mixin.py
├── utils
│   ├── __init__.py
│   ├── client_info.py
│   ├── conversation_memory.py
│   ├── env.py
│   ├── file_types.py
│   ├── file_utils.py
│   ├── image_utils.py
│   ├── model_context.py
│   ├── model_restrictions.py
│   ├── security_config.py
│   ├── storage_backend.py
│   └── token_utils.py
└── zen-mcp-server
```

# Files

--------------------------------------------------------------------------------
/tools/codereview.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | CodeReview Workflow tool - Systematic code review with step-by-step analysis
  3 | 
  4 | This tool provides a structured workflow for comprehensive code review and analysis.
  5 | It guides the CLI agent through systematic investigation steps with forced pauses between each step
  6 | to ensure thorough code examination, issue identification, and quality assessment before proceeding.
  7 | The tool supports complex review scenarios including security analysis, performance evaluation,
  8 | and architectural assessment.
  9 | 
 10 | Key features:
 11 | - Step-by-step code review workflow with progress tracking
 12 | - Context-aware file embedding (references during investigation, full content for analysis)
 13 | - Automatic issue tracking with severity classification
 14 | - Expert analysis integration with external models
 15 | - Support for focused reviews (security, performance, architecture)
 16 | - Confidence-based workflow optimization
 17 | """
 18 | 
 19 | import logging
 20 | from typing import TYPE_CHECKING, Any, Literal, Optional
 21 | 
 22 | from pydantic import Field, model_validator
 23 | 
 24 | if TYPE_CHECKING:
 25 |     from tools.models import ToolModelCategory
 26 | 
 27 | from config import TEMPERATURE_ANALYTICAL
 28 | from systemprompts import CODEREVIEW_PROMPT
 29 | from tools.shared.base_models import WorkflowRequest
 30 | 
 31 | from .workflow.base import WorkflowTool
 32 | 
 33 | logger = logging.getLogger(__name__)
 34 | 
 35 | # Tool-specific field descriptions for code review workflow
 36 | CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS = {
 37 |     "step": (
 38 |         "Review narrative. Step 1: outline the review strategy. Later steps: report findings. MUST cover quality, security, "
 39 |         "performance, and architecture. Reference code via `relevant_files`; avoid dumping large snippets."
 40 |     ),
 41 |     "step_number": "Current review step (starts at 1) – each step should build on the last.",
 42 |     "total_steps": (
 43 |         "Number of review steps planned. External validation: two steps (analysis + summary). Internal validation: one step. "
 44 |         "Use the same limits when continuing an existing review via continuation_id."
 45 |     ),
 46 |     "next_step_required": (
 47 |         "True when another review step follows. External validation: step 1 → True, step 2 → False. Internal validation: set False immediately. "
 48 |         "Apply the same rule on continuation flows."
 49 |     ),
 50 |     "findings": "Capture findings (positive and negative) across quality, security, performance, and architecture; update each step.",
 51 |     "files_checked": "Absolute paths of every file reviewed, including those ruled out.",
 52 |     "relevant_files": "Step 1: list all files/dirs under review. Must be absolute full non-abbreviated paths. Final step: narrow to files tied to key findings.",
 53 |     "relevant_context": "Functions or methods central to findings (e.g. 'Class.method' or 'function_name').",
 54 |     "issues_found": "Issues with severity (critical/high/medium/low) and descriptions.",
 55 |     "review_validation_type": "Set 'external' (default) for expert follow-up or 'internal' for local-only review.",
 56 |     "images": "Optional diagram or screenshot paths that clarify review context.",
 57 |     "review_type": "Review focus: full, security, performance, or quick.",
 58 |     "focus_on": "Optional note on areas to emphasise (e.g. 'threading', 'auth flow').",
 59 |     "standards": "Coding standards or style guides to enforce.",
 60 |     "severity_filter": "Lowest severity to include when reporting issues (critical/high/medium/low/all).",
 61 | }
 62 | 
 63 | 
 64 | class CodeReviewRequest(WorkflowRequest):
 65 |     """Request model for code review workflow investigation steps"""
 66 | 
 67 |     # Required fields for each investigation step
 68 |     step: str = Field(..., description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["step"])
 69 |     step_number: int = Field(..., description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["step_number"])
 70 |     total_steps: int = Field(..., description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["total_steps"])
 71 |     next_step_required: bool = Field(..., description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"])
 72 | 
 73 |     # Investigation tracking fields
 74 |     findings: str = Field(..., description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["findings"])
 75 |     files_checked: list[str] = Field(
 76 |         default_factory=list, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["files_checked"]
 77 |     )
 78 |     relevant_files: list[str] = Field(
 79 |         default_factory=list, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"]
 80 |     )
 81 |     relevant_context: list[str] = Field(
 82 |         default_factory=list, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["relevant_context"]
 83 |     )
 84 |     issues_found: list[dict] = Field(
 85 |         default_factory=list, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["issues_found"]
 86 |     )
 87 |     # Deprecated confidence field kept for backward compatibility only
 88 |     confidence: Optional[str] = Field("low", exclude=True)
 89 |     review_validation_type: Optional[Literal["external", "internal"]] = Field(
 90 |         "external", description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS.get("review_validation_type", "")
 91 |     )
 92 | 
 93 |     # Optional images for visual context
 94 |     images: Optional[list[str]] = Field(default=None, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["images"])
 95 | 
 96 |     # Code review-specific fields (only used in step 1 to initialize)
 97 |     review_type: Optional[Literal["full", "security", "performance", "quick"]] = Field(
 98 |         "full", description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["review_type"]
 99 |     )
100 |     focus_on: Optional[str] = Field(None, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["focus_on"])
101 |     standards: Optional[str] = Field(None, description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["standards"])
102 |     severity_filter: Optional[Literal["critical", "high", "medium", "low", "all"]] = Field(
103 |         "all", description=CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["severity_filter"]
104 |     )
105 | 
106 |     # Override inherited fields to exclude them from schema (except model which needs to be available)
107 |     temperature: Optional[float] = Field(default=None, exclude=True)
108 |     thinking_mode: Optional[str] = Field(default=None, exclude=True)
109 | 
110 |     @model_validator(mode="after")
111 |     def validate_step_one_requirements(self):
112 |         """Ensure step 1 has required relevant_files field."""
113 |         if self.step_number == 1 and not self.relevant_files:
114 |             raise ValueError("Step 1 requires 'relevant_files' field to specify code files or directories to review")
115 |         return self
116 | 
117 | 
118 | class CodeReviewTool(WorkflowTool):
119 |     """
120 |     Code Review workflow tool for step-by-step code review and expert analysis.
121 | 
122 |     This tool implements a structured code review workflow that guides users through
123 |     methodical investigation steps, ensuring thorough code examination, issue identification,
124 |     and quality assessment before reaching conclusions. It supports complex review scenarios
125 |     including security audits, performance analysis, architectural review, and maintainability assessment.
126 |     """
127 | 
128 |     def __init__(self):
129 |         super().__init__()
130 |         self.initial_request = None
131 |         self.review_config = {}
132 | 
133 |     def get_name(self) -> str:
134 |         return "codereview"
135 | 
136 |     def get_description(self) -> str:
137 |         return (
138 |             "Performs systematic, step-by-step code review with expert validation. "
139 |             "Use for comprehensive analysis covering quality, security, performance, and architecture. "
140 |             "Guides through structured investigation to ensure thoroughness."
141 |         )
142 | 
143 |     def get_system_prompt(self) -> str:
144 |         return CODEREVIEW_PROMPT
145 | 
146 |     def get_default_temperature(self) -> float:
147 |         return TEMPERATURE_ANALYTICAL
148 | 
149 |     def get_model_category(self) -> "ToolModelCategory":
150 |         """Code review requires thorough analysis and reasoning"""
151 |         from tools.models import ToolModelCategory
152 | 
153 |         return ToolModelCategory.EXTENDED_REASONING
154 | 
155 |     def get_workflow_request_model(self):
156 |         """Return the code review workflow-specific request model."""
157 |         return CodeReviewRequest
158 | 
159 |     def get_input_schema(self) -> dict[str, Any]:
160 |         """Generate input schema using WorkflowSchemaBuilder with code review-specific overrides."""
161 |         from .workflow.schema_builders import WorkflowSchemaBuilder
162 | 
163 |         # Code review workflow-specific field overrides
164 |         codereview_field_overrides = {
165 |             "step": {
166 |                 "type": "string",
167 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["step"],
168 |             },
169 |             "step_number": {
170 |                 "type": "integer",
171 |                 "minimum": 1,
172 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["step_number"],
173 |             },
174 |             "total_steps": {
175 |                 "type": "integer",
176 |                 "minimum": 1,
177 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["total_steps"],
178 |             },
179 |             "next_step_required": {
180 |                 "type": "boolean",
181 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"],
182 |             },
183 |             "findings": {
184 |                 "type": "string",
185 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["findings"],
186 |             },
187 |             "files_checked": {
188 |                 "type": "array",
189 |                 "items": {"type": "string"},
190 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["files_checked"],
191 |             },
192 |             "relevant_files": {
193 |                 "type": "array",
194 |                 "items": {"type": "string"},
195 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"],
196 |             },
197 |             "review_validation_type": {
198 |                 "type": "string",
199 |                 "enum": ["external", "internal"],
200 |                 "default": "external",
201 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS.get("review_validation_type", ""),
202 |             },
203 |             "issues_found": {
204 |                 "type": "array",
205 |                 "items": {"type": "object"},
206 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["issues_found"],
207 |             },
208 |             "images": {
209 |                 "type": "array",
210 |                 "items": {"type": "string"},
211 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["images"],
212 |             },
213 |             # Code review-specific fields (for step 1)
214 |             "review_type": {
215 |                 "type": "string",
216 |                 "enum": ["full", "security", "performance", "quick"],
217 |                 "default": "full",
218 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["review_type"],
219 |             },
220 |             "focus_on": {
221 |                 "type": "string",
222 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["focus_on"],
223 |             },
224 |             "standards": {
225 |                 "type": "string",
226 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["standards"],
227 |             },
228 |             "severity_filter": {
229 |                 "type": "string",
230 |                 "enum": ["critical", "high", "medium", "low", "all"],
231 |                 "default": "all",
232 |                 "description": CODEREVIEW_WORKFLOW_FIELD_DESCRIPTIONS["severity_filter"],
233 |             },
234 |         }
235 | 
236 |         # Use WorkflowSchemaBuilder with code review-specific tool fields
237 |         return WorkflowSchemaBuilder.build_schema(
238 |             tool_specific_fields=codereview_field_overrides,
239 |             model_field_schema=self.get_model_field_schema(),
240 |             auto_mode=self.is_effective_auto_mode(),
241 |             tool_name=self.get_name(),
242 |         )
243 | 
244 |     def get_required_actions(
245 |         self, step_number: int, confidence: str, findings: str, total_steps: int, request=None
246 |     ) -> list[str]:
247 |         """Define required actions for each investigation phase.
248 | 
249 |         Now includes request parameter for continuation-aware decisions.
250 |         """
251 |         # Check for continuation - fast track mode
252 |         if request:
253 |             continuation_id = self.get_request_continuation_id(request)
254 |             validation_type = self.get_review_validation_type(request)
255 |             if continuation_id and validation_type == "external":
256 |                 if step_number == 1:
257 |                     return [
258 |                         "Quickly review the code files to understand context",
259 |                         "Identify any critical issues that need immediate attention",
260 |                         "Note main architectural patterns and design decisions",
261 |                         "Prepare summary of key findings for expert validation",
262 |                     ]
263 |                 else:
264 |                     return ["Complete review and proceed to expert analysis"]
265 | 
266 |         if step_number == 1:
267 |             # Initial code review investigation tasks
268 |             return [
269 |                 "Read and understand the code files specified for review",
270 |                 "Examine the overall structure, architecture, and design patterns used",
271 |                 "Identify the main components, classes, and functions in the codebase",
272 |                 "Understand the business logic and intended functionality",
273 |                 "Look for obvious issues: bugs, security concerns, performance problems",
274 |                 "Note any code smells, anti-patterns, or areas of concern",
275 |             ]
276 |         elif step_number == 2:
277 |             # Deeper investigation for step 2
278 |             return [
279 |                 "Examine specific code sections you've identified as concerning",
280 |                 "Analyze security implications: input validation, authentication, authorization",
281 |                 "Check for performance issues: algorithmic complexity, resource usage, inefficiencies",
282 |                 "Look for architectural problems: tight coupling, missing abstractions, scalability issues",
283 |                 "Identify code quality issues: readability, maintainability, error handling",
284 |                 "Search for over-engineering, unnecessary complexity, or design patterns that could be simplified",
285 |             ]
286 |         elif step_number >= 3:
287 |             # Final verification for later steps
288 |             return [
289 |                 "Verify all identified issues have been properly documented with severity levels",
290 |                 "Check for any missed critical security vulnerabilities or performance bottlenecks",
291 |                 "Confirm that architectural concerns and code quality issues are comprehensively captured",
292 |                 "Ensure positive aspects and well-implemented patterns are also noted",
293 |                 "Validate that your assessment aligns with the review type and focus areas specified",
294 |                 "Double-check that findings are actionable and provide clear guidance for improvements",
295 |             ]
296 |         else:
297 |             # General investigation needed
298 |             return [
299 |                 "Continue examining the codebase for additional patterns and potential issues",
300 |                 "Gather more evidence using appropriate code analysis techniques",
301 |                 "Test your assumptions about code behavior and design decisions",
302 |                 "Look for patterns that confirm or refute your current assessment",
303 |                 "Focus on areas that haven't been thoroughly examined yet",
304 |             ]
305 | 
306 |     def should_call_expert_analysis(self, consolidated_findings, request=None) -> bool:
307 |         """
308 |         Decide when to call external model based on investigation completeness.
309 | 
310 |         For continuations with external type, always proceed with expert analysis.
311 |         """
312 |         # Check if user requested to skip assistant model
313 |         if request and not self.get_request_use_assistant_model(request):
314 |             return False
315 | 
316 |         # For continuations with external type, always proceed with expert analysis
317 |         continuation_id = self.get_request_continuation_id(request)
318 |         validation_type = self.get_review_validation_type(request)
319 |         if continuation_id and validation_type == "external":
320 |             return True  # Always perform expert analysis for external continuations
321 | 
322 |         # Check if we have meaningful investigation data
323 |         return (
324 |             len(consolidated_findings.relevant_files) > 0
325 |             or len(consolidated_findings.findings) >= 2
326 |             or len(consolidated_findings.issues_found) > 0
327 |         )
328 | 
329 |     def prepare_expert_analysis_context(self, consolidated_findings) -> str:
330 |         """Prepare context for external model call for final code review validation."""
331 |         context_parts = [
332 |             f"=== CODE REVIEW REQUEST ===\\n{self.initial_request or 'Code review workflow initiated'}\\n=== END REQUEST ==="
333 |         ]
334 | 
335 |         # Add investigation summary
336 |         investigation_summary = self._build_code_review_summary(consolidated_findings)
337 |         context_parts.append(
338 |             f"\\n=== AGENT'S CODE REVIEW INVESTIGATION ===\\n{investigation_summary}\\n=== END INVESTIGATION ==="
339 |         )
340 | 
341 |         # Add review configuration context if available
342 |         if self.review_config:
343 |             config_text = "\\n".join(f"- {key}: {value}" for key, value in self.review_config.items() if value)
344 |             context_parts.append(f"\\n=== REVIEW CONFIGURATION ===\\n{config_text}\\n=== END CONFIGURATION ===")
345 | 
346 |         # Add relevant code elements if available
347 |         if consolidated_findings.relevant_context:
348 |             methods_text = "\\n".join(f"- {method}" for method in consolidated_findings.relevant_context)
349 |             context_parts.append(f"\\n=== RELEVANT CODE ELEMENTS ===\\n{methods_text}\\n=== END CODE ELEMENTS ===")
350 | 
351 |         # Add issues found if available
352 |         if consolidated_findings.issues_found:
353 |             issues_text = "\\n".join(
354 |                 f"[{issue.get('severity', 'unknown').upper()}] {issue.get('description', 'No description')}"
355 |                 for issue in consolidated_findings.issues_found
356 |             )
357 |             context_parts.append(f"\\n=== ISSUES IDENTIFIED ===\\n{issues_text}\\n=== END ISSUES ===")
358 | 
359 |         # Add assessment evolution if available
360 |         if consolidated_findings.hypotheses:
361 |             assessments_text = "\\n".join(
362 |                 f"Step {h['step']} ({h['confidence']} confidence): {h['hypothesis']}"
363 |                 for h in consolidated_findings.hypotheses
364 |             )
365 |             context_parts.append(f"\\n=== ASSESSMENT EVOLUTION ===\\n{assessments_text}\\n=== END ASSESSMENTS ===")
366 | 
367 |         # Add images if available
368 |         if consolidated_findings.images:
369 |             images_text = "\\n".join(f"- {img}" for img in consolidated_findings.images)
370 |             context_parts.append(
371 |                 f"\\n=== VISUAL REVIEW INFORMATION ===\\n{images_text}\\n=== END VISUAL INFORMATION ==="
372 |             )
373 | 
374 |         return "\\n".join(context_parts)
375 | 
376 |     def _build_code_review_summary(self, consolidated_findings) -> str:
377 |         """Prepare a comprehensive summary of the code review investigation."""
378 |         summary_parts = [
379 |             "=== SYSTEMATIC CODE REVIEW INVESTIGATION SUMMARY ===",
380 |             f"Total steps: {len(consolidated_findings.findings)}",
381 |             f"Files examined: {len(consolidated_findings.files_checked)}",
382 |             f"Relevant files identified: {len(consolidated_findings.relevant_files)}",
383 |             f"Code elements analyzed: {len(consolidated_findings.relevant_context)}",
384 |             f"Issues identified: {len(consolidated_findings.issues_found)}",
385 |             "",
386 |             "=== INVESTIGATION PROGRESSION ===",
387 |         ]
388 | 
389 |         for finding in consolidated_findings.findings:
390 |             summary_parts.append(finding)
391 | 
392 |         return "\\n".join(summary_parts)
393 | 
394 |     def should_include_files_in_expert_prompt(self) -> bool:
395 |         """Include files in expert analysis for comprehensive code review."""
396 |         return True
397 | 
398 |     def should_embed_system_prompt(self) -> bool:
399 |         """Embed system prompt in expert analysis for proper context."""
400 |         return True
401 | 
402 |     def get_expert_thinking_mode(self) -> str:
403 |         """Use high thinking mode for thorough code review analysis."""
404 |         return "high"
405 | 
406 |     def get_expert_analysis_instruction(self) -> str:
407 |         """Get specific instruction for code review expert analysis."""
408 |         return (
409 |             "Please provide comprehensive code review analysis based on the investigation findings. "
410 |             "Focus on identifying any remaining issues, validating the completeness of the analysis, "
411 |             "and providing final recommendations for code improvements, following the severity-based "
412 |             "format specified in the system prompt."
413 |         )
414 | 
415 |     # Hook method overrides for code review-specific behavior
416 | 
417 |     def prepare_step_data(self, request) -> dict:
418 |         """
419 |         Map code review-specific fields for internal processing.
420 |         """
421 |         step_data = {
422 |             "step": request.step,
423 |             "step_number": request.step_number,
424 |             "findings": request.findings,
425 |             "files_checked": request.files_checked,
426 |             "relevant_files": request.relevant_files,
427 |             "relevant_context": request.relevant_context,
428 |             "issues_found": request.issues_found,
429 |             "review_validation_type": self.get_review_validation_type(request),
430 |             "hypothesis": request.findings,  # Map findings to hypothesis for compatibility
431 |             "images": request.images or [],
432 |             "confidence": "high",  # Dummy value for workflow_mixin compatibility
433 |         }
434 |         return step_data
435 | 
436 |     def should_skip_expert_analysis(self, request, consolidated_findings) -> bool:
437 |         """
438 |         Code review workflow skips expert analysis only when review_validation_type is "internal".
439 |         Default is always to use expert analysis (external).
440 |         For continuations with external type, always perform expert analysis immediately.
441 |         """
442 |         # If it's a continuation and review_validation_type is external, don't skip
443 |         continuation_id = self.get_request_continuation_id(request)
444 |         validation_type = self.get_review_validation_type(request)
445 |         if continuation_id and validation_type != "internal":
446 |             return False  # Always do expert analysis for external continuations
447 | 
448 |         # Only skip if explicitly set to internal AND review is complete
449 |         return validation_type == "internal" and not request.next_step_required
450 | 
451 |     def store_initial_issue(self, step_description: str):
452 |         """Store initial request for expert analysis."""
453 |         self.initial_request = step_description
454 | 
455 |     # Override inheritance hooks for code review-specific behavior
456 | 
457 |     def get_review_validation_type(self, request) -> str:
458 |         """Get review validation type from request. Hook method for clean inheritance."""
459 |         try:
460 |             return request.review_validation_type or "external"
461 |         except AttributeError:
462 |             return "external"  # Default to external validation
463 | 
464 |     def get_completion_status(self) -> str:
465 |         """Code review tools use review-specific status."""
466 |         return "code_review_complete_ready_for_implementation"
467 | 
468 |     def get_completion_data_key(self) -> str:
469 |         """Code review uses 'complete_code_review' key."""
470 |         return "complete_code_review"
471 | 
472 |     def get_final_analysis_from_request(self, request):
473 |         """Code review tools use 'findings' field."""
474 |         return request.findings
475 | 
476 |     def get_confidence_level(self, request) -> str:
477 |         """Code review tools use 'certain' for high confidence."""
478 |         return "certain"
479 | 
480 |     def get_completion_message(self) -> str:
481 |         """Code review-specific completion message."""
482 |         return (
483 |             "Code review complete. You have identified all significant issues "
484 |             "and provided comprehensive analysis. MANDATORY: Present the user with the complete review results "
485 |             "categorized by severity, and IMMEDIATELY proceed with implementing the highest priority fixes "
486 |             "or provide specific guidance for improvements. Focus on actionable recommendations."
487 |         )
488 | 
489 |     def get_skip_reason(self) -> str:
490 |         """Code review-specific skip reason."""
491 |         return "Completed comprehensive code review with internal analysis only (no external model validation)"
492 | 
493 |     def get_skip_expert_analysis_status(self) -> str:
494 |         """Code review-specific expert analysis skip status."""
495 |         return "skipped_due_to_internal_analysis_type"
496 | 
497 |     def prepare_work_summary(self) -> str:
498 |         """Code review-specific work summary."""
499 |         return self._build_code_review_summary(self.consolidated_findings)
500 | 
501 |     def get_completion_next_steps_message(self, expert_analysis_used: bool = False) -> str:
502 |         """
503 |         Code review-specific completion message.
504 |         """
505 |         base_message = (
506 |             "CODE REVIEW IS COMPLETE. You MUST now summarize and present ALL review findings organized by "
507 |             "severity (Critical → High → Medium → Low), specific code locations with line numbers, and exact "
508 |             "recommendations for improvement. Clearly prioritize the top 3 issues that need immediate attention. "
509 |             "Provide concrete, actionable guidance for each issue—make it easy for a developer to understand "
510 |             "exactly what needs to be fixed and how to implement the improvements."
511 |         )
512 | 
513 |         # Add expert analysis guidance only when expert analysis was actually used
514 |         if expert_analysis_used:
515 |             expert_guidance = self.get_expert_analysis_guidance()
516 |             if expert_guidance:
517 |                 return f"{base_message}\n\n{expert_guidance}"
518 | 
519 |         return base_message
520 | 
521 |     def get_expert_analysis_guidance(self) -> str:
522 |         """
523 |         Provide specific guidance for handling expert analysis in code reviews.
524 |         """
525 |         return (
526 |             "IMPORTANT: Analysis from an assistant model has been provided above. You MUST critically evaluate and validate "
527 |             "the expert findings rather than accepting them blindly. Cross-reference the expert analysis with "
528 |             "your own investigation findings, verify that suggested improvements are appropriate for this "
529 |             "codebase's context and patterns, and ensure recommendations align with the project's standards. "
530 |             "Present a synthesis that combines your systematic review with validated expert insights, clearly "
531 |             "distinguishing between findings you've independently confirmed and additional insights from expert analysis."
532 |         )
533 | 
534 |     def get_step_guidance_message(self, request) -> str:
535 |         """
536 |         Code review-specific step guidance with detailed investigation instructions.
537 |         """
538 |         step_guidance = self.get_code_review_step_guidance(request.step_number, request)
539 |         return step_guidance["next_steps"]
540 | 
541 |     def get_code_review_step_guidance(self, step_number: int, request) -> dict[str, Any]:
542 |         """
543 |         Provide step-specific guidance for code review workflow.
544 |         Uses get_required_actions to determine what needs to be done,
545 |         then formats those actions into appropriate guidance messages.
546 |         """
547 |         # Get the required actions from the single source of truth
548 |         required_actions = self.get_required_actions(
549 |             step_number,
550 |             "medium",  # Dummy value for backward compatibility
551 |             request.findings or "",
552 |             request.total_steps,
553 |             request,  # Pass request for continuation-aware decisions
554 |         )
555 | 
556 |         # Check if this is a continuation to provide context-aware guidance
557 |         continuation_id = self.get_request_continuation_id(request)
558 |         validation_type = self.get_review_validation_type(request)
559 |         is_external_continuation = continuation_id and validation_type == "external"
560 |         is_internal_continuation = continuation_id and validation_type == "internal"
561 | 
562 |         # Step 1 handling
563 |         if step_number == 1:
564 |             if is_external_continuation:
565 |                 # Fast-track for external continuations
566 |                 return {
567 |                     "next_steps": (
568 |                         "You are on step 1 of MAXIMUM 2 steps for continuation. CRITICAL: Quickly review the code NOW. "
569 |                         "MANDATORY ACTIONS:\\n"
570 |                         + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
571 |                         + "\\n\\nSet next_step_required=True and step_number=2 for the next call to trigger expert analysis."
572 |                     )
573 |                 }
574 |             elif is_internal_continuation:
575 |                 # Internal validation mode
576 |                 next_steps = (
577 |                     "Continuing previous conversation with internal validation only. The analysis will build "
578 |                     "upon the prior findings without external model validation. REQUIRED ACTIONS:\\n"
579 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
580 |                 )
581 |             else:
582 |                 # Normal flow for new reviews
583 |                 next_steps = (
584 |                     f"MANDATORY: DO NOT call the {self.get_name()} tool again immediately. You MUST first examine "
585 |                     f"the code files thoroughly using appropriate tools. CRITICAL AWARENESS: You need to:\\n"
586 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
587 |                     + f"\\n\\nOnly call {self.get_name()} again AFTER completing your investigation. "
588 |                     f"When you call {self.get_name()} next time, use step_number: {step_number + 1} "
589 |                     f"and report specific files examined, issues found, and code quality assessments discovered."
590 |                 )
591 | 
592 |         elif step_number == 2:
593 |             # CRITICAL: Check if violating minimum step requirement
594 |             if (
595 |                 request.total_steps >= 3
596 |                 and request.step_number < request.total_steps
597 |                 and not request.next_step_required
598 |             ):
599 |                 next_steps = (
600 |                     f"ERROR: You set total_steps={request.total_steps} but next_step_required=False on step {request.step_number}. "
601 |                     f"This violates the minimum step requirement. You MUST set next_step_required=True until you reach the final step. "
602 |                     f"Call {self.get_name()} again with next_step_required=True and continue your investigation."
603 |                 )
604 |             elif is_external_continuation or (not request.next_step_required and validation_type == "external"):
605 |                 # Fast-track completion or about to complete for external validation
606 |                 next_steps = (
607 |                     "Proceeding immediately to expert analysis. "
608 |                     f"MANDATORY: call {self.get_name()} tool immediately again, and set next_step_required=False to "
609 |                     f"trigger external validation NOW."
610 |                 )
611 |             else:
612 |                 # Normal flow - deeper analysis needed
613 |                 next_steps = (
614 |                     f"STOP! Do NOT call {self.get_name()} again yet. You are on step 2 of {request.total_steps} minimum required steps. "
615 |                     f"MANDATORY ACTIONS before calling {self.get_name()} step {step_number + 1}:\\n"
616 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
617 |                     + f"\\n\\nRemember: You MUST set next_step_required=True until step {request.total_steps}. "
618 |                     + f"Only call {self.get_name()} again with step_number: {step_number + 1} AFTER completing these code review tasks."
619 |                 )
620 | 
621 |         elif step_number >= 3:
622 |             if not request.next_step_required and validation_type == "external":
623 |                 # About to complete - ready for expert analysis
624 |                 next_steps = (
625 |                     "Completing review and proceeding to expert analysis. "
626 |                     "Ensure all findings are documented with specific file references and line numbers."
627 |                 )
628 |             else:
629 |                 # Later steps - final verification
630 |                 next_steps = (
631 |                     f"WAIT! Your code review needs final verification. DO NOT call {self.get_name()} immediately. REQUIRED ACTIONS:\\n"
632 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
633 |                     + f"\\n\\nREMEMBER: Ensure you have identified all significant issues across all severity levels and "
634 |                     f"verified the completeness of your review. Document findings with specific file references and "
635 |                     f"line numbers where applicable, then call {self.get_name()} with step_number: {step_number + 1}."
636 |                 )
637 |         else:
638 |             # Fallback for any other case - check minimum step violation first
639 |             if (
640 |                 request.total_steps >= 3
641 |                 and request.step_number < request.total_steps
642 |                 and not request.next_step_required
643 |             ):
644 |                 next_steps = (
645 |                     f"ERROR: You set total_steps={request.total_steps} but next_step_required=False on step {request.step_number}. "
646 |                     f"This violates the minimum step requirement. You MUST set next_step_required=True until step {request.total_steps}."
647 |                 )
648 |             elif not request.next_step_required and validation_type == "external":
649 |                 next_steps = (
650 |                     "Completing review. "
651 |                     "Ensure all findings are documented with specific file references and severity levels."
652 |                 )
653 |             else:
654 |                 next_steps = (
655 |                     f"PAUSE REVIEW. Before calling {self.get_name()} step {step_number + 1}, you MUST examine more code thoroughly. "
656 |                     + "Required: "
657 |                     + ", ".join(required_actions[:2])
658 |                     + ". "
659 |                     + f"Your next {self.get_name()} call (step_number: {step_number + 1}) must include "
660 |                     f"NEW evidence from actual code analysis, not just theories. NO recursive {self.get_name()} calls "
661 |                     f"without investigation work!"
662 |                 )
663 | 
664 |         return {"next_steps": next_steps}
665 | 
666 |     def customize_workflow_response(self, response_data: dict, request) -> dict:
667 |         """
668 |         Customize response to match code review workflow format.
669 |         """
670 |         # Store initial request on first step
671 |         if request.step_number == 1:
672 |             self.initial_request = request.step
673 |             # Store review configuration for expert analysis
674 |             if request.relevant_files:
675 |                 self.review_config = {
676 |                     "relevant_files": request.relevant_files,
677 |                     "review_type": request.review_type,
678 |                     "focus_on": request.focus_on,
679 |                     "standards": request.standards,
680 |                     "severity_filter": request.severity_filter,
681 |                 }
682 | 
683 |         # Convert generic status names to code review-specific ones
684 |         tool_name = self.get_name()
685 |         status_mapping = {
686 |             f"{tool_name}_in_progress": "code_review_in_progress",
687 |             f"pause_for_{tool_name}": "pause_for_code_review",
688 |             f"{tool_name}_required": "code_review_required",
689 |             f"{tool_name}_complete": "code_review_complete",
690 |         }
691 | 
692 |         if response_data["status"] in status_mapping:
693 |             response_data["status"] = status_mapping[response_data["status"]]
694 | 
695 |         # Rename status field to match code review workflow
696 |         if f"{tool_name}_status" in response_data:
697 |             response_data["code_review_status"] = response_data.pop(f"{tool_name}_status")
698 |             # Add code review-specific status fields
699 |             response_data["code_review_status"]["issues_by_severity"] = {}
700 |             for issue in self.consolidated_findings.issues_found:
701 |                 severity = issue.get("severity", "unknown")
702 |                 if severity not in response_data["code_review_status"]["issues_by_severity"]:
703 |                     response_data["code_review_status"]["issues_by_severity"][severity] = 0
704 |                 response_data["code_review_status"]["issues_by_severity"][severity] += 1
705 |             response_data["code_review_status"]["review_validation_type"] = self.get_review_validation_type(request)
706 | 
707 |         # Map complete_codereviewworkflow to complete_code_review
708 |         if f"complete_{tool_name}" in response_data:
709 |             response_data["complete_code_review"] = response_data.pop(f"complete_{tool_name}")
710 | 
711 |         # Map the completion flag to match code review workflow
712 |         if f"{tool_name}_complete" in response_data:
713 |             response_data["code_review_complete"] = response_data.pop(f"{tool_name}_complete")
714 | 
715 |         return response_data
716 | 
717 |     # Required abstract methods from BaseTool
718 |     def get_request_model(self):
719 |         """Return the code review workflow-specific request model."""
720 |         return CodeReviewRequest
721 | 
722 |     async def prepare_prompt(self, request) -> str:
723 |         """Not used - workflow tools use execute_workflow()."""
724 |         return ""  # Workflow tools use execute_workflow() directly
725 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
   1 | # CHANGELOG
   2 | 
   3 | <!-- version list -->
   4 | 
   5 | ## v9.1.3 (2025-10-22)
   6 | 
   7 | ### Bug Fixes
   8 | 
   9 | - Reduced token usage, removed parameters from schema that CLIs never seem to use
  10 |   ([`3e27319`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3e27319e60b0287df918856b58b2bbf042c948a8))
  11 | 
  12 | - Telemetry option no longer available in gemini 0.11
  13 |   ([`2a8dff0`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/2a8dff0cc8a3f33111533cdb971d654637ed0578))
  14 | 
  15 | ### Chores
  16 | 
  17 | - Sync version to config.py [skip ci]
  18 |   ([`9e163f9`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/9e163f9dc0654fc28961c9897b7c787a2b96e57d))
  19 | 
  20 | - Sync version to config.py [skip ci]
  21 |   ([`557e443`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/557e443a63ffd733fb41faaa8696f6f4bb2c2fd1))
  22 | 
  23 | ### Refactoring
  24 | 
  25 | - Improved precommit system prompt
  26 |   ([`3efff60`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3efff6056e322ee1531d7bed5601038c129a8b29))
  27 | 
  28 | 
  29 | ## v9.1.2 (2025-10-21)
  30 | 
  31 | ### Bug Fixes
  32 | 
  33 | - Configure codex with a longer timeout
  34 |   ([`d2773f4`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d2773f488af28986632846652874de9ff633049c))
  35 | 
  36 | - Handle claude's array style JSON https://github.com/BeehiveInnovations/zen-mcp-server/issues/295
  37 |   ([`d5790a9`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d5790a9bfef719f03d17f2d719f1882e55d13b3b))
  38 | 
  39 | ### Chores
  40 | 
  41 | - Sync version to config.py [skip ci]
  42 |   ([`04132f1`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/04132f1459f1e086afd8e3d456f671b63338f846))
  43 | 
  44 | 
  45 | ## v9.1.1 (2025-10-17)
  46 | 
  47 | ### Bug Fixes
  48 | 
  49 | - Failing test
  50 |   ([`aed3e3e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/aed3e3ee80c440ac8ab0d4abbf235b84df723d18))
  51 | 
  52 | - Handler for parsing multiple generated code blocks
  53 |   ([`f4c20d2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f4c20d2a20e1c57d8b10e8f508e07e2a8d72f94a))
  54 | 
  55 | - Improved error reporting; codex cli would at times fail to figure out how to handle plain-text /
  56 |   JSON errors
  57 |   ([`95e69a7`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/95e69a7cb234305dcd37dcdd2f22be715922e9a8))
  58 | 
  59 | ### Chores
  60 | 
  61 | - Sync version to config.py [skip ci]
  62 |   ([`942757a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/942757a360a74c021b2a1aa63e394f18f5abcecd))
  63 | 
  64 | 
  65 | ## v9.1.0 (2025-10-17)
  66 | 
  67 | ### Chores
  68 | 
  69 | - Sync version to config.py [skip ci]
  70 |   ([`3ee0c8f`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3ee0c8f555cb51b975700290919c2a8e2ada8cc4))
  71 | 
  72 | ### Features
  73 | 
  74 | - Enhance review prompts to emphasize static analysis
  75 |   ([`36e66e2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/36e66e2e9a44a73a466545d4d3477ecb2cb3e669))
  76 | 
  77 | 
  78 | ## v9.0.4 (2025-10-17)
  79 | 
  80 | ### Chores
  81 | 
  82 | - Sync version to config.py [skip ci]
  83 |   ([`8c6f653`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/8c6f6532d843f7f1b283ce9b6472e5ba991efe16))
  84 | 
  85 | 
  86 | ## v9.0.3 (2025-10-16)
  87 | 
  88 | ### Bug Fixes
  89 | 
  90 | - Remove duplicate -o json flag in gemini CLI config
  91 |   ([`3b2eff5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3b2eff58ac0e2388045a7442c63f56ce259b54ba))
  92 | 
  93 | ### Chores
  94 | 
  95 | - Sync version to config.py [skip ci]
  96 |   ([`b205d71`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b205d7159b674ce47ebc11af7255d1e3556fff93))
  97 | 
  98 | 
  99 | ## v9.0.2 (2025-10-15)
 100 | 
 101 | ### Bug Fixes
 102 | 
 103 | - Update Claude CLI commands to new mcp syntax
 104 |   ([`a2189cb`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/a2189cb88a295ebad6268b9b08c893cd65bc1d89))
 105 | 
 106 | ### Chores
 107 | 
 108 | - Sync version to config.py [skip ci]
 109 |   ([`d08cdc6`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d08cdc6691e0f68917f2824945905b7256e0e568))
 110 | 
 111 | 
 112 | ## v9.0.1 (2025-10-14)
 113 | 
 114 | ### Bug Fixes
 115 | 
 116 | - Add JSON output flag to gemini CLI configuration
 117 |   ([`eb3dff8`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/eb3dff845828f60ff2659586883af622b8b035eb))
 118 | 
 119 | ### Chores
 120 | 
 121 | - Sync version to config.py [skip ci]
 122 |   ([`b9408aa`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b9408aae8860d43b1da0ba67f9db98db7e4de2cf))
 123 | 
 124 | 
 125 | ## v9.0.0 (2025-10-08)
 126 | 
 127 | ### Chores
 128 | 
 129 | - Sync version to config.py [skip ci]
 130 |   ([`23c9b35`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/23c9b35d5226b07b59a4c4b3d7833ba81b019ea8))
 131 | 
 132 | ### Features
 133 | 
 134 | - Claude Code as a CLI agent now supported. Mix and match: spawn claude code from within claude
 135 |   code, or claude code from within codex.
 136 |   ([`4cfaa0b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4cfaa0b6060769adfbd785a072526a5368421a73))
 137 | 
 138 | 
 139 | ## v8.0.2 (2025-10-08)
 140 | 
 141 | ### Bug Fixes
 142 | 
 143 | - Restore run-server quote trimming regex
 144 |   ([`1de4542`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/1de454224c105891137134e2a25c2ee4f00dba45))
 145 | 
 146 | ### Chores
 147 | 
 148 | - Sync version to config.py [skip ci]
 149 |   ([`728fb43`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/728fb439b929c9dc37646b24537ae043208fda7d))
 150 | 
 151 | 
 152 | ## v8.0.1 (2025-10-08)
 153 | 
 154 | ### Bug Fixes
 155 | 
 156 | - Resolve executable path for cross-platform compatibility in CLI agent
 157 |   ([`f98046c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f98046c2fccaa7f9a24665a0d705a98006461da5))
 158 | 
 159 | ### Chores
 160 | 
 161 | - Sync version to config.py [skip ci]
 162 |   ([`52245b9`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/52245b91eaa5d720f8c3b21ead55248dd8e8bd57))
 163 | 
 164 | ### Testing
 165 | 
 166 | - Fix clink agent tests to mock shutil.which() for executable resolution
 167 |   ([`4370be3`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4370be33b4b69a40456527213bcd62321a925a57))
 168 | 
 169 | 
 170 | ## v8.0.0 (2025-10-07)
 171 | 
 172 | ### Chores
 173 | 
 174 | - Sync version to config.py [skip ci]
 175 |   ([`4c34541`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4c3454121c3c678cdfe8ea03fa77f4dd414df9bc))
 176 | 
 177 | 
 178 | ## v7.8.1 (2025-10-07)
 179 | 
 180 | ### Bug Fixes
 181 | 
 182 | - Updated model description to fix test
 183 |   ([`04f7ce5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/04f7ce5b03804564263f53a765931edba9c320cd))
 184 | 
 185 | ### Chores
 186 | 
 187 | - Sync version to config.py [skip ci]
 188 |   ([`c27e81d`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c27e81d6d2f22978816f798a161a869d1ab5f025))
 189 | 
 190 | ### Refactoring
 191 | 
 192 | - Moved registries into a separate module and code cleanup
 193 |   ([`7c36b92`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7c36b9255a13007a10af4fadefc21aadfce482b0))
 194 | 
 195 | 
 196 | ## v7.8.0 (2025-10-07)
 197 | 
 198 | ### Chores
 199 | 
 200 | - Sync version to config.py [skip ci]
 201 |   ([`3e5fa96`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3e5fa96c981bbd7b844a9887a518ffe266b78e9b))
 202 | 
 203 | ### Documentation
 204 | 
 205 | - Consensus video
 206 |   ([`2352684`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/23526841922a73c68094e5205e19af04a1f6c8cc))
 207 | 
 208 | - Formatting
 209 |   ([`7d7c74b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7d7c74b5a38b7d1adf132b8e28034017df7aa852))
 210 | 
 211 | - Link to videos from main page
 212 |   ([`e8ef193`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e8ef193daba393b55a3beaaba49721bb9182378a))
 213 | 
 214 | - Update README.md
 215 |   ([`7b13543`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7b13543824fc0af729daf753ecdddba9ee7d9f1e))
 216 | 
 217 | ### Features
 218 | 
 219 | - All native providers now read from catalog files like OpenRouter / Custom configs. Allows for
 220 |   greater control over the capabilities
 221 |   ([`2a706d5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/2a706d5720c0bf97b71c3e0fc95c15f78015bedf))
 222 | 
 223 | - Provider cleanup
 224 |   ([`9268dda`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/9268ddad2a07306351765b47098134512739f49f))
 225 | 
 226 | ### Refactoring
 227 | 
 228 | - New base class for model registry / loading
 229 |   ([`02d13da`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/02d13da897016d7491b4a10a1195983385d66654))
 230 | 
 231 | 
 232 | ## v7.7.0 (2025-10-07)
 233 | 
 234 | ### Chores
 235 | 
 236 | - Sync version to config.py [skip ci]
 237 |   ([`70ae62a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/70ae62a2cd663c3abcabddd1be1bc6ed9512d7df))
 238 | 
 239 | ### Documentation
 240 | 
 241 | - Video
 242 |   ([`ed5dda7`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ed5dda7c5a9439c2835cc69d76e6377169ad048a))
 243 | 
 244 | ### Features
 245 | 
 246 | - More aliases
 247 |   ([`5f0aaf5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/5f0aaf5f69c9d188d817b5ffbf6738c61da40ec7))
 248 | 
 249 | 
 250 | ## v7.6.0 (2025-10-07)
 251 | 
 252 | ### Chores
 253 | 
 254 | - Sync version to config.py [skip ci]
 255 |   ([`c1c75ba`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c1c75ba304c2840329650c46273e87eab9b05906))
 256 | 
 257 | - Sync version to config.py [skip ci]
 258 |   ([`0fa9b66`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/0fa9b6658099c8e0d79fda0c7d2347f62d0e6137))
 259 | 
 260 | ### Documentation
 261 | 
 262 | - Info about AI client timeouts
 263 |   ([`3ddfed5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3ddfed5ef09000791e1c94b041c43dc273ed53a8))
 264 | 
 265 | ### Features
 266 | 
 267 | - Add support for openai/gpt-5-pro model
 268 |   ([`abed075`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/abed075b2eaa99e9618202f47ff921094baae952))
 269 | 
 270 | 
 271 | ## v7.5.2 (2025-10-06)
 272 | 
 273 | ### Bug Fixes
 274 | 
 275 | - Handle 429 response https://github.com/BeehiveInnovations/zen-mcp-server/issues/273
 276 |   ([`cbe1d79`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/cbe1d7993276bd014b495cbd2d0ece1f5d7583d9))
 277 | 
 278 | ### Chores
 279 | 
 280 | - Sync version to config.py [skip ci]
 281 |   ([`74fdd36`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/74fdd36de92d34681fcc5a2f772c3d05634f0a55))
 282 | 
 283 | 
 284 | ## v7.5.1 (2025-10-06)
 285 | 
 286 | ### Chores
 287 | 
 288 | - Sync version to config.py [skip ci]
 289 |   ([`004e379`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/004e379cf2f1853829dccb15fa72ec18d282f1a4))
 290 | 
 291 | 
 292 | ## v7.5.0 (2025-10-06)
 293 | 
 294 | ### Chores
 295 | 
 296 | - Sync version to config.py [skip ci]
 297 |   ([`71e7cd5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/71e7cd55b1f4955a6d718fddc0de419414d133b6))
 298 | 
 299 | ### Documentation
 300 | 
 301 | - Video
 302 |   ([`775e4d5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/775e4d50b826858095c5f2a61a07fc01c4a00816))
 303 | 
 304 | - Videos
 305 |   ([`bb2066c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bb2066c909f6581ba40fc5ddef3870954ae553ab))
 306 | 
 307 | ### Features
 308 | 
 309 | - Support for GPT-5-Pro highest reasoning model
 310 |   https://github.com/BeehiveInnovations/zen-mcp-server/issues/275
 311 |   ([`a65485a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/a65485a1e52fc79739000426295a27d096f4c9d8))
 312 | 
 313 | 
 314 | ## v7.4.0 (2025-10-06)
 315 | 
 316 | ### Chores
 317 | 
 318 | - Sync version to config.py [skip ci]
 319 |   ([`76bf98e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/76bf98e5cd972dabd3c79b25fcb9b9a717b23f6d))
 320 | 
 321 | ### Features
 322 | 
 323 | - Improved prompt
 324 |   ([`b1e9963`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b1e9963991a41dff082ec1dce5691c318f105e6d))
 325 | 
 326 | 
 327 | ## v7.3.0 (2025-10-06)
 328 | 
 329 | ### Chores
 330 | 
 331 | - Sync version to config.py [skip ci]
 332 |   ([`e7920d0`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e7920d0ed16c0e6de9d1ccaa0b58d3fb5cbd7f2f))
 333 | 
 334 | ### Documentation
 335 | 
 336 | - Fixed typo
 337 |   ([`3ab0aa8`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3ab0aa8314ad5992bcb00de549a0fab2e522751d))
 338 | 
 339 | - Fixed typo
 340 |   ([`c17ce3c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c17ce3cf958d488b97fa7127942542ab514b58bd))
 341 | 
 342 | - Update apilookup.md
 343 |   ([`1918679`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/19186794edac4fce5523e671310aecff4cbfdc81))
 344 | 
 345 | - Update README.md
 346 |   ([`23c6c78`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/23c6c78bf152ede6e7b5f7b7770b12a8442845a3))
 347 | 
 348 | ### Features
 349 | 
 350 | - Codex supports web-search natively but needs to be turned on, run-server script asks if the user
 351 |   would like this done
 352 |   ([`97ba7e4`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/97ba7e44ce7e3fd874759514ed2f0738033fc801))
 353 | 
 354 | 
 355 | ## v7.2.0 (2025-10-06)
 356 | 
 357 | ### Chores
 358 | 
 359 | - Sync version to config.py [skip ci]
 360 |   ([`1854b1e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/1854b1e26b705cda0dc3f4d733647f1454aa0352))
 361 | 
 362 | ### Documentation
 363 | 
 364 | - Updated
 365 |   ([`bb57f71`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bb57f719666ab6a586d835688ff8086282a5a0dc))
 366 | 
 367 | ### Features
 368 | 
 369 | - New tool to perform apilookup (latest APIs / SDKs / language features etc)
 370 |   https://github.com/BeehiveInnovations/zen-mcp-server/issues/204
 371 |   ([`5bea595`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/5bea59540f58b3c45044828c10f131aed104dd1c))
 372 | 
 373 | ### Refactoring
 374 | 
 375 | - De-duplicate roles to avoid explosion when more CLIs get added
 376 |   ([`c42e9e9`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c42e9e9c34d7ae4732e2e4fbed579b681a6d170d))
 377 | 
 378 | 
 379 | ## v7.1.1 (2025-10-06)
 380 | 
 381 | ### Bug Fixes
 382 | 
 383 | - Clink missing in toml
 384 |   ([`1ff77fa`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/1ff77faa800ad6c2dde49cad98dfa72035fe1c81))
 385 | 
 386 | ### Chores
 387 | 
 388 | - Sync version to config.py [skip ci]
 389 |   ([`e02e78d`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e02e78d903b35f4c01b8039f4157e97b38d3ec7b))
 390 | 
 391 | ### Documentation
 392 | 
 393 | - Example for codex cli
 394 |   ([`344c42b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/344c42bcbfb543bfd05cbc27fd5b419c76b77954))
 395 | 
 396 | - Example for codex cli
 397 |   ([`c3044de`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c3044de7424e638dde5c8ec49adb6c3c7c5a60b2))
 398 | 
 399 | - Update README.md
 400 |   ([`2e719ae`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/2e719ae35e7979f7b83bd910867e79863a7f9ceb))
 401 | 
 402 | 
 403 | ## v7.1.0 (2025-10-05)
 404 | 
 405 | ### Chores
 406 | 
 407 | - Sync version to config.py [skip ci]
 408 |   ([`d54bfdd`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d54bfdd49797d076ec9cade44c56292a8089c744))
 409 | 
 410 | ### Features
 411 | 
 412 | - Support for codex as external CLI
 413 |   ([`561e4aa`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/561e4aaaa8a89eb89c03985b9e7720cc98ef666c))
 414 | 
 415 | 
 416 | ## v7.0.2 (2025-10-05)
 417 | 
 418 | ### Chores
 419 | 
 420 | - Sync version to config.py [skip ci]
 421 |   ([`f2142a2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f2142a22ec50abc54b464eedd6b8239d20c509be))
 422 | 
 423 | 
 424 | ## v7.0.1 (2025-10-05)
 425 | 
 426 | ### Bug Fixes
 427 | 
 428 | - --yolo needed for running shell commands, documentation added
 429 |   ([`15ae3f2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/15ae3f24babccf42f43be5028bf8c60c05a6beaf))
 430 | 
 431 | ### Chores
 432 | 
 433 | - Sync version to config.py [skip ci]
 434 |   ([`bc4a27b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bc4a27b18a4a3f45afb22178e61ea0be4d6a273c))
 435 | 
 436 | ### Documentation
 437 | 
 438 | - Updated intro
 439 |   ([`fb668c3`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/fb668c39b5f6e3dd37f7027f953f6004f258f2bf))
 440 | 
 441 | 
 442 | ## v7.0.0 (2025-10-05)
 443 | 
 444 | ### Chores
 445 | 
 446 | - Sync version to config.py [skip ci]
 447 |   ([`0d46976`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/0d46976a8aa85254e4dbe06f5e71161cd3b13938))
 448 | 
 449 | - Sync version to config.py [skip ci]
 450 |   ([`8296bf8`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/8296bf871c39597a904c70e7d98c72fcb4dc5a84))
 451 | 
 452 | ### Documentation
 453 | 
 454 | - Instructions for OpenCode
 455 |   ([`bd66622`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bd666227c8f7557483f7e24fb8544fc0456600dc))
 456 | 
 457 | - Updated intro
 458 |   ([`615873c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/615873c3db2ecf5ce6475caa3445e1da9a2517bd))
 459 | 
 460 | ### Features
 461 | 
 462 | - Huge update - Link another CLI (such as `gemini` directly from with Claude Code / Codex).
 463 |   https://github.com/BeehiveInnovations/zen-mcp-server/issues/208
 464 |   ([`a2ccb48`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/a2ccb48e9a5080a75dbfd483b5f09fc719c887e5))
 465 | 
 466 | ### Refactoring
 467 | 
 468 | - Fixed test
 469 |   ([`9c99b9b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/9c99b9b35219f54db8d7be0958d4390a106631ae))
 470 | 
 471 | - Include file modification dates too
 472 |   ([`47973e9`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/47973e945efa2cdbdb8f3404d467d7f1abc62b0a))
 473 | 
 474 | 
 475 | ## v6.1.0 (2025-10-04)
 476 | 
 477 | ### Chores
 478 | 
 479 | - Sync version to config.py [skip ci]
 480 |   ([`18095d7`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/18095d7d398e4bf3d24c57a52c81ac619acb1b89))
 481 | 
 482 | ### Documentation
 483 | 
 484 | - Updated intro
 485 |   ([`aa65394`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/aa6539472c4ddf1c3c1bac446fdee03e75e1cb50))
 486 | 
 487 | ### Features
 488 | 
 489 | - Support for Qwen Code
 490 |   ([`fe9968b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/fe9968b633d0312b82426e9ebddfe1d6515be3c5))
 491 | 
 492 | 
 493 | ## v6.0.0 (2025-10-04)
 494 | 
 495 | ### Chores
 496 | 
 497 | - Sync version to config.py [skip ci]
 498 |   ([`ae8749a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ae8749ab37bdaa7e225b5219820adeb74ca9a552))
 499 | 
 500 | ### Documentation
 501 | 
 502 | - Updated
 503 |   ([`e91ed2a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e91ed2a924b1702edf9e1417479ac0dee0ca1553))
 504 | 
 505 | ### Features
 506 | 
 507 | - Azure OpenAI / Azure AI Foundry support. Models should be defined in conf/azure_models.json (or a
 508 |   custom path). See .env.example for environment variables or see readme.
 509 |   https://github.com/BeehiveInnovations/zen-mcp-server/issues/265
 510 |   ([`ff9a07a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))
 511 | 
 512 | - Breaking change - OpenRouter models are now read from conf/openrouter_models.json while Custom /
 513 |   Self-hosted models are read from conf/custom_models.json
 514 |   ([`ff9a07a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))
 515 | 
 516 | - OpenAI/compatible models (such as Azure OpenAI) can declare if they use the response API instead
 517 |   via `use_openai_responses_api`
 518 |   ([`3824d13`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3824d131618683572e9e8fffa6b25ccfabf4cf50))
 519 | 
 520 | - OpenRouter / Custom Models / Azure can separately also use custom config paths now (see
 521 |   .env.example )
 522 |   ([`ff9a07a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))
 523 | 
 524 | ### Refactoring
 525 | 
 526 | - Breaking change: `is_custom` property has been removed from model_capabilities.py (and thus
 527 |   custom_models.json) given each models are now read from separate configuration files
 528 |   ([`ff9a07a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))
 529 | 
 530 | - Model registry class made abstract, OpenRouter / Custom Provider / Azure OpenAI now subclass these
 531 |   ([`ff9a07a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))
 532 | 
 533 | 
 534 | ## v5.22.0 (2025-10-04)
 535 | 
 536 | ### Bug Fixes
 537 | 
 538 | - CI test
 539 |   ([`bc93b53`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bc93b5343bbd8657b95ab47c00a2cb99a68a009f))
 540 | 
 541 | - Listmodels to always honor restricted models
 542 |   ([`4015e91`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4015e917ed32ae374ec6493b74993fcb34f4a971))
 543 | 
 544 | ### Chores
 545 | 
 546 | - Sync version to config.py [skip ci]
 547 |   ([`054e34e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/054e34e31ca5bee5a11c0e3e6537f58e8897c79c))
 548 | 
 549 | - Sync version to config.py [skip ci]
 550 |   ([`c0334d7`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c0334d77922f1b05e3fd755851da112567fb9ae6))
 551 | 
 552 | ### Features
 553 | 
 554 | - Centralized environment handling, ensures ZEN_MCP_FORCE_ENV_OVERRIDE is honored correctly
 555 |   ([`2c534ac`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/2c534ac06e4c6078b96781dfb55c5759b982afe8))
 556 | 
 557 | ### Refactoring
 558 | 
 559 | - Don't retry on 429
 560 |   ([`d184024`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d18402482087f52b7bd07755c9304ed00ed20592))
 561 | 
 562 | - Improved retry logic and moved core logic to base class
 563 |   ([`f955100`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f955100f3a82973ccd987607e1d8a1bbe07828c8))
 564 | 
 565 | - Removed subclass override when the base class should be resolving the model name
 566 |   ([`06d7701`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/06d7701cc3ee09732ab713fa9c7c004199154483))
 567 | 
 568 | 
 569 | ## v5.21.0 (2025-10-03)
 570 | 
 571 | ### Chores
 572 | 
 573 | - Sync version to config.py [skip ci]
 574 |   ([`ddb20a6`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ddb20a6cdb8cdeee27c0aacb0b9c794283b5774c))
 575 | 
 576 | 
 577 | ## v5.20.1 (2025-10-03)
 578 | 
 579 | ### Chores
 580 | 
 581 | - Sync version to config.py [skip ci]
 582 |   ([`03addcf`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/03addcfa2d3aed5086fe4c94e8b9ae56229a93ae))
 583 | 
 584 | 
 585 | ## v5.20.0 (2025-10-03)
 586 | 
 587 | ### Chores
 588 | 
 589 | - Sync version to config.py [skip ci]
 590 |   ([`539bc72`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/539bc72f1ca2a2cadcccad02de1fd5fc22cd0415))
 591 | 
 592 | 
 593 | ## v5.19.0 (2025-10-03)
 594 | 
 595 | ### Bug Fixes
 596 | 
 597 | - Add GPT-5-Codex to Responses API routing and simplify comments
 598 |   ([`82b021d`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/82b021d75acc791e68c7afb35f6492f68cf02bec))
 599 | 
 600 | ### Chores
 601 | 
 602 | - Sync version to config.py [skip ci]
 603 |   ([`8e32ef3`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/8e32ef33e3ce7ab2a9d7eb5c90fe5b93b12d5c26))
 604 | 
 605 | ### Documentation
 606 | 
 607 | - Bumped defaults
 608 |   ([`95d98a9`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/95d98a9bc0a5bafadccb9f6d1e4eda97a0dd2ce7))
 609 | 
 610 | ### Features
 611 | 
 612 | - Add GPT-5-Codex support with Responses API integration
 613 |   ([`f265342`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f2653427ca829368e7145325d20a98df3ee6d6b4))
 614 | 
 615 | ### Testing
 616 | 
 617 | - Cross tool memory recall, testing continuation via cassette recording
 618 |   ([`88493bd`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/88493bd357c6a12477c3160813100dae1bc46493))
 619 | 
 620 | 
 621 | ## v5.18.3 (2025-10-03)
 622 | 
 623 | ### Bug Fixes
 624 | 
 625 | - External model name now recorded properly in responses
 626 |   ([`d55130a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d55130a430401e106cd86f3e830b3d756472b7ff))
 627 | 
 628 | ### Chores
 629 | 
 630 | - Sync version to config.py [skip ci]
 631 |   ([`5714e20`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/5714e2016405f7607b44d78f85081c7ccee706e5))
 632 | 
 633 | ### Documentation
 634 | 
 635 | - Updated docs
 636 |   ([`b4e5090`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b4e50901ba60c88137a29d00ecf99718582856d3))
 637 | 
 638 | ### Refactoring
 639 | 
 640 | - Generic name for the CLI agent
 641 |   ([`e9b6947`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e9b69476cd922c12931d62ccc3be9082bbbf6014))
 642 | 
 643 | - Generic name for the CLI agent
 644 |   ([`7a6fa0e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7a6fa0e77a8c4a682dc11c9bbb16bdaf86d9edf4))
 645 | 
 646 | - Generic name for the CLI agent
 647 |   ([`b692da2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b692da2a82facce7455b8f2ec0108e1db84c07c3))
 648 | 
 649 | - Generic name for the CLI agent
 650 |   ([`f76ebbf`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f76ebbf280cc78ffcfe17cb4590aeaa231db8aa1))
 651 | 
 652 | - Generic name for the CLI agent
 653 |   ([`c05913a`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c05913a09e53e195b9a108647c09c061ced19d17))
 654 | 
 655 | - Generic name for the CLI agent
 656 |   ([`0dfaa63`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/0dfaa6312ed95ac3d1ae0032334ae1286871b15e))
 657 | 
 658 | ### Testing
 659 | 
 660 | - Fixed integration tests, removed magicmock
 661 |   ([`87ccb6b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/87ccb6b25ba32a3cb9c4cc64fc0e96294f492c04))
 662 | 
 663 | 
 664 | ## v5.18.2 (2025-10-02)
 665 | 
 666 | ### Bug Fixes
 667 | 
 668 | - Https://github.com/BeehiveInnovations/zen-mcp-server/issues/194
 669 |   ([`8b3a286`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/8b3a2867fb83eccb3a8e8467e7e3fc5b8ebe1d0c))
 670 | 
 671 | ### Chores
 672 | 
 673 | - Sync version to config.py [skip ci]
 674 |   ([`bf2196c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bf2196cdd58ae8d8d93597f2be69c798265d678f))
 675 | 
 676 | 
 677 | ## v5.18.1 (2025-10-02)
 678 | 
 679 | ### Chores
 680 | 
 681 | - Sync version to config.py [skip ci]
 682 |   ([`e434a26`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e434a2614af82efd15de4dd94b2c30559c91414e))
 683 | 
 684 | 
 685 | ## v5.18.0 (2025-10-02)
 686 | 
 687 | ### Chores
 688 | 
 689 | - Sync version to config.py [skip ci]
 690 |   ([`e78fe35`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e78fe35a1b64cc0ed89664440ef7c7b94495d7dc))
 691 | 
 692 | ### Features
 693 | 
 694 | - Added `intelligence_score` to the model capabilities schema; a 1-20 number that can be specified
 695 |   to influence the sort order of models presented to the CLI in `auto selection` mode
 696 |   ([`6cab9e5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/6cab9e56fc5373da5c11d4545bcb85371d4803a4))
 697 | 
 698 | 
 699 | ## v5.17.4 (2025-10-02)
 700 | 
 701 | ### Chores
 702 | 
 703 | - Sync version to config.py [skip ci]
 704 |   ([`a6c9b92`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/a6c9b9212c77852d9e9a8780f4bc3e53b3bfed2f))
 705 | 
 706 | 
 707 | ## v5.17.3 (2025-10-02)
 708 | 
 709 | ### Chores
 710 | 
 711 | - Sync version to config.py [skip ci]
 712 |   ([`722f6f8`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/722f6f86ae228206ce0094d109a3b20499d4e11a))
 713 | 
 714 | 
 715 | ## v5.17.2 (2025-10-02)
 716 | 
 717 | ### Chores
 718 | 
 719 | - Sync version to config.py [skip ci]
 720 |   ([`e47a7e8`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e47a7e89d5bfad0bb0150cb3207f1a37dc91b170))
 721 | 
 722 | 
 723 | ## v5.17.1 (2025-10-02)
 724 | 
 725 | ### Bug Fixes
 726 | 
 727 | - Baseclass should return MODEL_CAPABILITIES
 728 |   ([`82a03ce`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/82a03ce63f28fece17bfc1d70bdb75aadec4c6bb))
 729 | 
 730 | ### Chores
 731 | 
 732 | - Sync version to config.py [skip ci]
 733 |   ([`7ce66bd`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7ce66bd9508865cef64dc30936e86e37c1a306d0))
 734 | 
 735 | ### Documentation
 736 | 
 737 | - Document custom timeout values
 738 |   ([`218fbdf`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/218fbdf49cb90f2353f58bbaef567519dd876634))
 739 | 
 740 | ### Refactoring
 741 | 
 742 | - Clean temperature inference
 743 |   ([`9c11ecc`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/9c11ecc4bf37562aa08dc3ecfa70f380e0ead357))
 744 | 
 745 | - Cleanup
 746 |   ([`6ec2033`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/6ec2033f34c74ad139036de83a34cf6d374db77b))
 747 | 
 748 | - Cleanup provider base class; cleanup shared responsibilities; cleanup public contract
 749 |   ([`693b84d`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/693b84db2b87271ac809abcf02100eee7405720b))
 750 | 
 751 | - Cleanup token counting
 752 |   ([`7fe9fc4`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7fe9fc49f8e3cd92be4c45a6645d5d4ab3014091))
 753 | 
 754 | - Code cleanup
 755 |   ([`bb138e2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/bb138e2fb552f837b0f9f466027580e1feb26f7c))
 756 | 
 757 | - Code cleanup
 758 |   ([`182aa62`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/182aa627dfba6c578089f83444882cdd2635a7e3))
 759 | 
 760 | - Moved image related code out of base provider into a separate utility
 761 |   ([`14a35af`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/14a35afa1d25408e62b968d9846be7bffaede327))
 762 | 
 763 | - Moved temperature method from base provider to model capabilities
 764 |   ([`6d237d0`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/6d237d09709f757a042baf655f47eb4ddfc078ad))
 765 | 
 766 | - Moved temperature method from base provider to model capabilities
 767 |   ([`f461cb4`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f461cb451953f882bbde096a9ecf0584deb1dde8))
 768 | 
 769 | - Removed hard coded checks, use model capabilities instead
 770 |   ([`250545e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/250545e34f8d4f8026bfebb3171f3c2bc40f4692))
 771 | 
 772 | - Removed hook from base class, turned into helper static method instead
 773 |   ([`2b10adc`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/2b10adcaf2b8741f0da5de84cc3483eae742a014))
 774 | 
 775 | - Removed method from provider, should use model capabilities instead
 776 |   ([`a254ff2`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/a254ff2220ba00ec30f5110c69a4841419917382))
 777 | 
 778 | - Renaming to reflect underlying type
 779 |   ([`1dc25f6`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/1dc25f6c3d4cdbf01f041cc424e3b5235c23175b))
 780 | 
 781 | 
 782 | ## v5.17.0 (2025-10-02)
 783 | 
 784 | ### Bug Fixes
 785 | 
 786 | - Use types.HttpOptions from module imports instead of local import
 787 |   ([`956e8a6`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/956e8a6927837f5c7f031a0db1dd0b0b5483c626))
 788 | 
 789 | ### Chores
 790 | 
 791 | - Sync version to config.py [skip ci]
 792 |   ([`0836213`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/0836213071d0037d8a6d2e64d34ab5df79b8e684))
 793 | 
 794 | ### Code Style
 795 | 
 796 | - Apply Black formatting to use double quotes
 797 |   ([`33ea896`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/33ea896c511764904bf2b6b22df823928f88a148))
 798 | 
 799 | ### Features
 800 | 
 801 | - Add custom Gemini endpoint support
 802 |   ([`462bce0`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/462bce002e2141b342260969588e69f55f8bb46a))
 803 | 
 804 | ### Refactoring
 805 | 
 806 | - Simplify Gemini provider initialization using kwargs dict
 807 |   ([`023940b`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/023940be3e38a7eedbc8bf8404a4a5afc50f8398))
 808 | 
 809 | 
 810 | ## v5.16.0 (2025-10-01)
 811 | 
 812 | ### Bug Fixes
 813 | 
 814 | - Resolve logging timing and import organization issues
 815 |   ([`d34c299`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d34c299f02a233af4f17bdcc848219bf07799723))
 816 | 
 817 | ### Chores
 818 | 
 819 | - Sync version to config.py [skip ci]
 820 |   ([`b6c4bca`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b6c4bca158e4cee1ae4abd08b7e55216ebffba2d))
 821 | 
 822 | ### Code Style
 823 | 
 824 | - Fix ruff import sorting issue
 825 |   ([`4493a69`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4493a693332e0532d04ad3634de2a2f5b1249b64))
 826 | 
 827 | ### Features
 828 | 
 829 | - Add configurable environment variable override system
 830 |   ([`93ce698`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/93ce6987b6e7d8678ffa5ac51f5106a7a21ce67b))
 831 | 
 832 | 
 833 | ## v5.15.0 (2025-10-01)
 834 | 
 835 | ### Chores
 836 | 
 837 | - Sync version to config.py [skip ci]
 838 |   ([`b0fe956`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b0fe956f8a50240507e0fc911f0800634c15e9f7))
 839 | 
 840 | ### Features
 841 | 
 842 | - Depending on the number of tools in use, this change should save ~50% of overall tokens used.
 843 |   fixes https://github.com/BeehiveInnovations/zen-mcp-server/issues/255 but also refactored
 844 |   individual tools to instead encourage the agent to use the listmodels tool if needed.
 845 |   ([`d9449c7`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d9449c7bb607caff3f0454f210ddfc36256c738a))
 846 | 
 847 | ### Performance Improvements
 848 | 
 849 | - Tweaks to schema descriptions, aiming to reduce token usage without performance degradation
 850 |   ([`cc8a4df`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/cc8a4dfd21b6f3dae4972a833b619e53c964693b))
 851 | 
 852 | ### Refactoring
 853 | 
 854 | - Trimmed some prompts
 855 |   ([`f69ff03`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/f69ff03c4d10e606a1dfed2a167f3ba2e2236ba8))
 856 | 
 857 | 
 858 | ## v5.14.1 (2025-10-01)
 859 | 
 860 | ### Bug Fixes
 861 | 
 862 | - Https://github.com/BeehiveInnovations/zen-mcp-server/issues/258
 863 |   ([`696b45f`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/696b45f25e80faccb67034254cf9a8fc4c643dbd))
 864 | 
 865 | ### Chores
 866 | 
 867 | - Sync version to config.py [skip ci]
 868 |   ([`692016c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/692016c6205ed0a0c3d9e830482d88231aca2e31))
 869 | 
 870 | 
 871 | ## v5.14.0 (2025-10-01)
 872 | 
 873 | ### Chores
 874 | 
 875 | - Sync version to config.py [skip ci]
 876 |   ([`c0f822f`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c0f822ffa23292d668f7b5dd3cb62e3f23fb29af))
 877 | 
 878 | ### Features
 879 | 
 880 | - Add Claude Sonnet 4.5 and update alias configuration
 881 |   ([`95c4822`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/95c4822af2dc55f59c0e4ed9454673d6ca964731))
 882 | 
 883 | ### Testing
 884 | 
 885 | - Update tests to match new Claude Sonnet 4.5 alias configuration
 886 |   ([`7efb409`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7efb4094d4eb7db006340d3d9240b9113ac25cd3))
 887 | 
 888 | 
 889 | ## v5.13.0 (2025-10-01)
 890 | 
 891 | ### Bug Fixes
 892 | 
 893 | - Add sonnet alias for Claude Sonnet 4.1 to match opus/haiku pattern
 894 |   ([`dc96344`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/dc96344db043e087ee4f8bf264a79c51dc2e0b7a))
 895 | 
 896 | - Missing "optenai/" in name
 897 |   ([`7371ed6`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/7371ed6487b7d90a1b225a67dca2a38c1a52f2ad))
 898 | 
 899 | ### Chores
 900 | 
 901 | - Sync version to config.py [skip ci]
 902 |   ([`b8479fc`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/b8479fc638083d6caa4bad6205e3d3fcab830aca))
 903 | 
 904 | ### Features
 905 | 
 906 | - Add comprehensive GPT-5 series model support
 907 |   ([`4930824`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/493082405237e66a2f033481a5f8bf8293b0d553))
 908 | 
 909 | 
 910 | ## v5.12.1 (2025-10-01)
 911 | 
 912 | ### Bug Fixes
 913 | 
 914 | - Resolve consensus tool model_context parameter missing issue
 915 |   ([`9044b63`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/9044b63809113047fe678d659e4fcd175f58e87a))
 916 | 
 917 | ### Chores
 918 | 
 919 | - Sync version to config.py [skip ci]
 920 |   ([`e3ebf4e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/e3ebf4e94eba63acdc4df5a0b0493e44e3343dd1))
 921 | 
 922 | ### Code Style
 923 | 
 924 | - Fix trailing whitespace in consensus.py
 925 |   ([`0760b31`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/0760b31f8a6d03c4bea3fd2a94dfbbfab0ad5079))
 926 | 
 927 | ### Refactoring
 928 | 
 929 | - Optimize ModelContext creation in consensus tool
 930 |   ([`30a8952`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/30a8952fbccd22bebebd14eb2c8005404b79bcd6))
 931 | 
 932 | 
 933 | ## v5.12.0 (2025-10-01)
 934 | 
 935 | ### Bug Fixes
 936 | 
 937 | - Removed use_websearch; this parameter was confusing Codex. It started using this to prompt the
 938 |   external model to perform searches! web-search is enabled by Claude / Codex etc by default and the
 939 |   external agent can ask claude to search on its behalf.
 940 |   ([`cff6d89`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/cff6d8998f64b73265c4e31b2352462d6afe377f))
 941 | 
 942 | ### Chores
 943 | 
 944 | - Sync version to config.py [skip ci]
 945 |   ([`28cabe0`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/28cabe0833661b0bab56d4227781ee2da332b00c))
 946 | 
 947 | ### Features
 948 | 
 949 | - Implement semantic cassette matching for o3 models
 950 |   ([`70fa088`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/70fa088c32ac4e6153d5e7b30a3e32022be2f908))
 951 | 
 952 | 
 953 | ## v5.11.2 (2025-10-01)
 954 | 
 955 | ### Chores
 956 | 
 957 | - Sync version to config.py [skip ci]
 958 |   ([`4d6f1b4`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4d6f1b41005dee428c955e33f04f8f9f6259e662))
 959 | 
 960 | 
 961 | ## v5.11.1 (2025-10-01)
 962 | 
 963 | ### Bug Fixes
 964 | 
 965 | - Remove duplicate OpenAI models from listmodels output
 966 |   ([`c29e762`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/c29e7623ace257eb45396cdf8c19e1659e29edb9))
 967 | 
 968 | ### Chores
 969 | 
 970 | - Sync version to config.py [skip ci]
 971 |   ([`1209064`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/12090646ee83f2368311d595d87ae947e46ddacd))
 972 | 
 973 | ### Testing
 974 | 
 975 | - Update OpenAI provider alias tests to match new format
 976 |   ([`d13700c`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d13700c14c7ee3d092302837cb1726d17bab1ab8))
 977 | 
 978 | 
 979 | ## v5.11.0 (2025-08-26)
 980 | 
 981 | ### Chores
 982 | 
 983 | - Sync version to config.py [skip ci]
 984 |   ([`9735469`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/973546990f2c45afa93f1aa6de33ff461ecf1a83))
 985 | 
 986 | ### Features
 987 | 
 988 | - Codex CLI support
 989 |   ([`ce56d16`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/ce56d16240ddcc476145a512561efe5c66438f0d))
 990 | 
 991 | 
 992 | ## v5.10.3 (2025-08-24)
 993 | 
 994 | ### Bug Fixes
 995 | 
 996 | - Address test failures and PR feedback
 997 |   ([`6bd9d67`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/6bd9d6709acfb584ab30a0a4d6891cabdb6d3ccf))
 998 | 
 999 | - Resolve temperature handling issues for O3/custom models
1000 |   ([#245](https://github.com/BeehiveInnovations/zen-mcp-server/pull/245),
1001 |   [`3b4fd88`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/3b4fd88d7e9a3f09fea616a10cb3e9d6c1a0d63b))
1002 | 
1003 | ### Chores
1004 | 
1005 | - Sync version to config.py [skip ci]
1006 |   ([`d6e6808`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d6e6808be525192ab8388c0f01bc1bbd016fc23a))
1007 | 
1008 | 
1009 | ## v5.10.2 (2025-08-24)
1010 | 
1011 | ### Bug Fixes
1012 | 
1013 | - Another fix for https://github.com/BeehiveInnovations/zen-mcp-server/issues/251
1014 |   ([`a07036e`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/a07036e6805042895109c00f921c58a09caaa319))
1015 | 
1016 | ### Chores
1017 | 
1018 | - Sync version to config.py [skip ci]
1019 |   ([`9da5c37`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/9da5c37809cbde19d0c7ffed273ae93ca883a016))
1020 | 
1021 | 
1022 | ## v5.10.0 (2025-08-22)
1023 | 
1024 | ### Chores
1025 | 
1026 | - Sync version to config.py [skip ci]
1027 |   ([`1254205`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/12542054a214022d3f515e53367f5bf3a77fb289))
1028 | 
1029 | ### Features
1030 | 
1031 | - Refactored and tweaked model descriptions / schema to use fewer tokens at launch (average
1032 |   reduction per field description: 60-80%) without sacrificing tool effectiveness
1033 |   ([`4b202f5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4b202f5d1d24cea1394adab26a976188f847bd09))
1034 | 
1035 | 
1036 | ## v5.9.0 (2025-08-21)
1037 | 
1038 | ### Documentation
1039 | 
1040 | - Update instructions for precommit
1041 |   ([`90821b5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/90821b51ff653475d9fb1bc70b57951d963e8841))
1042 | 
1043 | ### Features
1044 | 
1045 | - Refactored and improved codereview in line with precommit. Reviews are now either external
1046 |   (default) or internal. Takes away anxiety and loss of tokens when Claude incorrectly decides to be
1047 |   'confident' about its own changes and bungle things up.
1048 |   ([`80d21e5`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/80d21e57c0246762c0a306ede5b93d6aeb2315d8))
1049 | 
1050 | ### Refactoring
1051 | 
1052 | - Minor prompt tweaks
1053 |   ([`d30c212`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/d30c212029c05b767d99b5391c1dd4cee78ef336))
1054 | 
1055 | 
1056 | ## v5.8.6 (2025-08-20)
1057 | 
1058 | ### Bug Fixes
1059 | 
1060 | - Escape backslashes in TOML regex pattern
1061 |   ([`1c973af`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/1c973afb002650b9bbee8a831b756bef848915a1))
1062 | 
1063 | - Establish version 5.8.6 and add version sync automation
1064 |   ([`90a4195`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/90a419538128b54fbd30da4b8a8088ac59f8c691))
1065 | 
1066 | - Restore proper version 5.8.6
1067 |   ([`340b58f`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/340b58f2e790b84c3736aa96df7f6f5f2d6a13c9))
1068 | 
1069 | ### Chores
1070 | 
1071 | - Sync version to config.py [skip ci]
1072 |   ([`4f82f65`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/4f82f6500502b7b6ba41875a560c41f6a63b683b))
1073 | 
1074 | 
1075 | ## v1.1.0 (2025-08-20)
1076 | 
1077 | ### Features
1078 | 
1079 | - Improvements to precommit
1080 |   ([`2966dcf`](https://github.com/BeehiveInnovations/zen-mcp-server/commit/2966dcf2682feb7eef4073738d0c225a44ce0533))
1081 | 
1082 | 
1083 | ## v1.0.0 (2025-08-20)
1084 | 
1085 | - Initial Release
1086 | 
```

--------------------------------------------------------------------------------
/tools/consensus.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Consensus tool - Step-by-step multi-model consensus with expert analysis
  3 | 
  4 | This tool provides a structured workflow for gathering consensus from multiple models.
  5 | It guides the CLI agent through systematic steps where the CLI agent first provides its own analysis,
  6 | then consults each requested model one by one, and finally synthesizes all perspectives.
  7 | 
  8 | Key features:
  9 | - Step-by-step consensus workflow with progress tracking
 10 | - The CLI agent's initial neutral analysis followed by model-specific consultations
 11 | - Context-aware file embedding
 12 | - Support for stance-based analysis (for/against/neutral)
 13 | - Final synthesis combining all perspectives
 14 | """
 15 | 
 16 | from __future__ import annotations
 17 | 
 18 | import json
 19 | import logging
 20 | from typing import TYPE_CHECKING, Any
 21 | 
 22 | from pydantic import Field, model_validator
 23 | 
 24 | if TYPE_CHECKING:
 25 |     from tools.models import ToolModelCategory
 26 | 
 27 | from mcp.types import TextContent
 28 | 
 29 | from config import TEMPERATURE_ANALYTICAL
 30 | from systemprompts import CONSENSUS_PROMPT
 31 | from tools.shared.base_models import ConsolidatedFindings, WorkflowRequest
 32 | from utils.conversation_memory import MAX_CONVERSATION_TURNS, create_thread, get_thread
 33 | 
 34 | from .workflow.base import WorkflowTool
 35 | 
 36 | logger = logging.getLogger(__name__)
 37 | 
 38 | # Tool-specific field descriptions for consensus workflow
 39 | CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS = {
 40 |     "step": (
 41 |         "Consensus prompt. Step 1: write the exact proposal/question every model will see (use 'Evaluate…', not meta commentary). "
 42 |         "Steps 2+: capture internal notes about the latest model response—these notes are NOT sent to other models."
 43 |     ),
 44 |     "step_number": "Current step index (starts at 1). Step 1 is your analysis; steps 2+ handle each model response.",
 45 |     "total_steps": "Total steps = number of models consulted plus the final synthesis step.",
 46 |     "next_step_required": "True if more model consultations remain; set false when ready to synthesize.",
 47 |     "findings": (
 48 |         "Step 1: your independent analysis for later synthesis (not shared with other models). Steps 2+: summarize the newest model response."
 49 |     ),
 50 |     "relevant_files": "Optional supporting files that help the consensus analysis. Must be absolute full, non-abbreviated paths.",
 51 |     "models": (
 52 |         "User-specified list of models to consult (provide at least two entries). "
 53 |         "Each entry may include model, stance (for/against/neutral), and stance_prompt. "
 54 |         "Each (model, stance) pair must be unique, e.g. [{'model':'gpt5','stance':'for'}, {'model':'pro','stance':'against'}]."
 55 |     ),
 56 |     "current_model_index": "0-based index of the next model to consult (managed internally).",
 57 |     "model_responses": "Internal log of responses gathered so far.",
 58 |     "images": "Optional absolute image paths or base64 references that add helpful visual context.",
 59 | }
 60 | 
 61 | 
 62 | class ConsensusRequest(WorkflowRequest):
 63 |     """Request model for consensus workflow steps"""
 64 | 
 65 |     # Required fields for each step
 66 |     step: str = Field(..., description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["step"])
 67 |     step_number: int = Field(..., description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["step_number"])
 68 |     total_steps: int = Field(..., description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["total_steps"])
 69 |     next_step_required: bool = Field(..., description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"])
 70 | 
 71 |     # Investigation tracking fields
 72 |     findings: str = Field(..., description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["findings"])
 73 |     confidence: str = Field(default="exploring", exclude=True, description="Not used")
 74 | 
 75 |     # Consensus-specific fields (only needed in step 1)
 76 |     models: list[dict] | None = Field(None, description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["models"])
 77 |     relevant_files: list[str] | None = Field(
 78 |         default_factory=list,
 79 |         description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"],
 80 |     )
 81 | 
 82 |     # Internal tracking fields
 83 |     current_model_index: int | None = Field(
 84 |         0,
 85 |         description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["current_model_index"],
 86 |     )
 87 |     model_responses: list[dict] | None = Field(
 88 |         default_factory=list,
 89 |         description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["model_responses"],
 90 |     )
 91 | 
 92 |     # Optional images for visual debugging
 93 |     images: list[str] | None = Field(default=None, description=CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["images"])
 94 | 
 95 |     # Override inherited fields to exclude them from schema
 96 |     temperature: float | None = Field(default=None, exclude=True)
 97 |     thinking_mode: str | None = Field(default=None, exclude=True)
 98 | 
 99 |     # Not used in consensus workflow
100 |     files_checked: list[str] | None = Field(default_factory=list, exclude=True)
101 |     relevant_context: list[str] | None = Field(default_factory=list, exclude=True)
102 |     issues_found: list[dict] | None = Field(default_factory=list, exclude=True)
103 |     hypothesis: str | None = Field(None, exclude=True)
104 | 
105 |     @model_validator(mode="after")
106 |     def validate_step_one_requirements(self):
107 |         """Ensure step 1 has required models field and unique model+stance combinations."""
108 |         if self.step_number == 1:
109 |             if not self.models:
110 |                 raise ValueError("Step 1 requires 'models' field to specify which models to consult")
111 | 
112 |             # Check for unique model + stance combinations
113 |             seen_combinations = set()
114 |             for model_config in self.models:
115 |                 model_name = model_config.get("model", "")
116 |                 stance = model_config.get("stance", "neutral")
117 |                 combination = f"{model_name}:{stance}"
118 | 
119 |                 if combination in seen_combinations:
120 |                     raise ValueError(
121 |                         f"Duplicate model + stance combination found: {model_name} with stance '{stance}'. "
122 |                         f"Each model + stance combination must be unique."
123 |                     )
124 |                 seen_combinations.add(combination)
125 | 
126 |         return self
127 | 
128 | 
129 | class ConsensusTool(WorkflowTool):
130 |     """
131 |     Consensus workflow tool for step-by-step multi-model consensus gathering.
132 | 
133 |     This tool implements a structured consensus workflow where the CLI agent first provides
134 |     its own neutral analysis, then consults each specified model individually,
135 |     and finally synthesizes all perspectives into a unified recommendation.
136 |     """
137 | 
138 |     def __init__(self):
139 |         super().__init__()
140 |         self.initial_prompt: str | None = None
141 |         self.original_proposal: str | None = None  # Store the original proposal separately
142 |         self.models_to_consult: list[dict] = []
143 |         self.accumulated_responses: list[dict] = []
144 |         self._current_arguments: dict[str, Any] = {}
145 | 
146 |     def get_name(self) -> str:
147 |         return "consensus"
148 | 
149 |     def get_description(self) -> str:
150 |         return (
151 |             "Builds multi-model consensus through systematic analysis and structured debate. "
152 |             "Use for complex decisions, architectural choices, feature proposals, and technology evaluations. "
153 |             "Consults multiple models with different stances to synthesize comprehensive recommendations."
154 |         )
155 | 
156 |     def get_system_prompt(self) -> str:
157 |         # For the CLI agent's initial analysis, use a neutral version of the consensus prompt
158 |         return CONSENSUS_PROMPT.replace(
159 |             "{stance_prompt}",
160 |             """BALANCED PERSPECTIVE
161 | 
162 | Provide objective analysis considering both positive and negative aspects. However, if there is overwhelming evidence
163 | that the proposal clearly leans toward being exceptionally good or particularly problematic, you MUST accurately
164 | reflect this reality. Being "balanced" means being truthful about the weight of evidence, not artificially creating
165 | 50/50 splits when the reality is 90/10.
166 | 
167 | Your analysis should:
168 | - Present all significant pros and cons discovered
169 | - Weight them according to actual impact and likelihood
170 | - If evidence strongly favors one conclusion, clearly state this
171 | - Provide proportional coverage based on the strength of arguments
172 | - Help the questioner see the true balance of considerations
173 | 
174 | Remember: Artificial balance that misrepresents reality is not helpful. True balance means accurate representation
175 | of the evidence, even when it strongly points in one direction.""",
176 |         )
177 | 
178 |     def get_default_temperature(self) -> float:
179 |         return TEMPERATURE_ANALYTICAL
180 | 
181 |     def get_model_category(self) -> ToolModelCategory:
182 |         """Consensus workflow requires extended reasoning"""
183 |         from tools.models import ToolModelCategory
184 | 
185 |         return ToolModelCategory.EXTENDED_REASONING
186 | 
187 |     def get_workflow_request_model(self):
188 |         """Return the consensus workflow-specific request model."""
189 |         return ConsensusRequest
190 | 
191 |     def get_input_schema(self) -> dict[str, Any]:
192 |         """Generate input schema for consensus workflow."""
193 |         from .workflow.schema_builders import WorkflowSchemaBuilder
194 | 
195 |         # Consensus tool-specific field definitions
196 |         consensus_field_overrides = {
197 |             # Override standard workflow fields that need consensus-specific descriptions
198 |             "step": {
199 |                 "type": "string",
200 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["step"],
201 |             },
202 |             "step_number": {
203 |                 "type": "integer",
204 |                 "minimum": 1,
205 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["step_number"],
206 |             },
207 |             "total_steps": {
208 |                 "type": "integer",
209 |                 "minimum": 1,
210 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["total_steps"],
211 |             },
212 |             "next_step_required": {
213 |                 "type": "boolean",
214 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"],
215 |             },
216 |             "findings": {
217 |                 "type": "string",
218 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["findings"],
219 |             },
220 |             "relevant_files": {
221 |                 "type": "array",
222 |                 "items": {"type": "string"},
223 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"],
224 |             },
225 |             # consensus-specific fields (not in base workflow)
226 |             "models": {
227 |                 "type": "array",
228 |                 "items": {
229 |                     "type": "object",
230 |                     "properties": {
231 |                         "model": {"type": "string"},
232 |                         "stance": {"type": "string", "enum": ["for", "against", "neutral"], "default": "neutral"},
233 |                         "stance_prompt": {"type": "string"},
234 |                     },
235 |                     "required": ["model"],
236 |                 },
237 |                 "description": (
238 |                     "User-specified roster of models to consult (provide at least two entries). "
239 |                     + CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["models"]
240 |                 ),
241 |                 "minItems": 2,
242 |             },
243 |             "current_model_index": {
244 |                 "type": "integer",
245 |                 "minimum": 0,
246 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["current_model_index"],
247 |             },
248 |             "model_responses": {
249 |                 "type": "array",
250 |                 "items": {"type": "object"},
251 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["model_responses"],
252 |             },
253 |             "images": {
254 |                 "type": "array",
255 |                 "items": {"type": "string"},
256 |                 "description": CONSENSUS_WORKFLOW_FIELD_DESCRIPTIONS["images"],
257 |             },
258 |         }
259 | 
260 |         # Provide guidance on available models similar to single-model tools
261 |         model_description = (
262 |             "When the user names a model, you MUST use that exact value or report the "
263 |             "provider error—never swap in another option. Use the `listmodels` tool for the full roster."
264 |         )
265 | 
266 |         summaries, total, restricted = self._get_ranked_model_summaries()
267 |         remainder = max(0, total - len(summaries))
268 |         if summaries:
269 |             label = "Allowed models" if restricted else "Top models"
270 |             top_line = "; ".join(summaries)
271 |             if remainder > 0:
272 |                 top_line = f"{label}: {top_line}; +{remainder} more via `listmodels`."
273 |             else:
274 |                 top_line = f"{label}: {top_line}."
275 |             model_description = f"{model_description} {top_line}"
276 |         else:
277 |             model_description = (
278 |                 f"{model_description} No models detected—configure provider credentials or use the `listmodels` tool "
279 |                 "to inspect availability."
280 |             )
281 | 
282 |         restriction_note = self._get_restriction_note()
283 |         if restriction_note and (remainder > 0 or not summaries):
284 |             model_description = f"{model_description} {restriction_note}."
285 | 
286 |         existing_models_desc = consensus_field_overrides["models"]["description"]
287 |         consensus_field_overrides["models"]["description"] = f"{existing_models_desc} {model_description}"
288 | 
289 |         # Define excluded fields for consensus workflow
290 |         excluded_workflow_fields = [
291 |             "files_checked",  # Not used in consensus workflow
292 |             "relevant_context",  # Not used in consensus workflow
293 |             "issues_found",  # Not used in consensus workflow
294 |             "hypothesis",  # Not used in consensus workflow
295 |             "confidence",  # Not used in consensus workflow
296 |         ]
297 | 
298 |         excluded_common_fields = [
299 |             "model",  # Consensus uses 'models' field instead
300 |             "temperature",  # Not used in consensus workflow
301 |             "thinking_mode",  # Not used in consensus workflow
302 |         ]
303 | 
304 |         requires_model = self.requires_model()
305 |         model_field_schema = self.get_model_field_schema() if requires_model else None
306 |         auto_mode = self.is_effective_auto_mode() if requires_model else False
307 | 
308 |         return WorkflowSchemaBuilder.build_schema(
309 |             tool_specific_fields=consensus_field_overrides,
310 |             model_field_schema=model_field_schema,
311 |             auto_mode=auto_mode,
312 |             tool_name=self.get_name(),
313 |             excluded_workflow_fields=excluded_workflow_fields,
314 |             excluded_common_fields=excluded_common_fields,
315 |             require_model=requires_model,
316 |         )
317 | 
318 |     def get_required_actions(
319 |         self, step_number: int, confidence: str, findings: str, total_steps: int, request=None
320 |     ) -> list[str]:  # noqa: ARG002
321 |         """Define required actions for each consensus phase.
322 | 
323 |         Now includes request parameter for continuation-aware decisions.
324 |         Note: confidence parameter is kept for compatibility with base class but not used.
325 |         """
326 |         if step_number == 1:
327 |             # CLI Agent's initial analysis
328 |             return [
329 |                 "You've provided your initial analysis. The tool will now consult other models.",
330 |                 "Wait for the next step to receive the first model's response.",
331 |             ]
332 |         elif step_number < total_steps - 1:
333 |             # Processing individual model responses
334 |             return [
335 |                 "Review the model response provided in this step",
336 |                 "Note key agreements and disagreements with previous analyses",
337 |                 "Wait for the next model's response",
338 |             ]
339 |         else:
340 |             # Ready for final synthesis
341 |             return [
342 |                 "All models have been consulted",
343 |                 "Synthesize all perspectives into a comprehensive recommendation",
344 |                 "Identify key points of agreement and disagreement",
345 |                 "Provide clear, actionable guidance based on the consensus",
346 |             ]
347 | 
348 |     def should_call_expert_analysis(self, consolidated_findings, request=None) -> bool:
349 |         """Consensus workflow doesn't use traditional expert analysis - it consults models step by step."""
350 |         return False
351 | 
352 |     def prepare_expert_analysis_context(self, consolidated_findings) -> str:
353 |         """Not used in consensus workflow."""
354 |         return ""
355 | 
356 |     def requires_expert_analysis(self) -> bool:
357 |         """Consensus workflow handles its own model consultations."""
358 |         return False
359 | 
360 |     def requires_model(self) -> bool:
361 |         """
362 |         Consensus tool doesn't require model resolution at the MCP boundary.
363 | 
364 |         Uses it's own set of models
365 | 
366 |         Returns:
367 |             bool: False
368 |         """
369 |         return False
370 | 
371 |     # Hook method overrides for consensus-specific behavior
372 | 
373 |     def prepare_step_data(self, request) -> dict:
374 |         """Prepare consensus-specific step data."""
375 |         step_data = {
376 |             "step": request.step,
377 |             "step_number": request.step_number,
378 |             "findings": request.findings,
379 |             "files_checked": [],  # Not used
380 |             "relevant_files": request.relevant_files or [],
381 |             "relevant_context": [],  # Not used
382 |             "issues_found": [],  # Not used
383 |             "confidence": "exploring",  # Not used, kept for compatibility
384 |             "hypothesis": None,  # Not used
385 |             "images": request.images or [],  # Now used for visual context
386 |         }
387 |         return step_data
388 | 
389 |     async def handle_work_completion(self, response_data: dict, request, arguments: dict) -> dict:  # noqa: ARG002
390 |         """Handle consensus workflow completion - no expert analysis, just final synthesis."""
391 |         response_data["consensus_complete"] = True
392 |         response_data["status"] = "consensus_workflow_complete"
393 | 
394 |         # Prepare final synthesis data
395 |         response_data["complete_consensus"] = {
396 |             "initial_prompt": self.original_proposal if self.original_proposal else self.initial_prompt,
397 |             "models_consulted": [m["model"] + ":" + m.get("stance", "neutral") for m in self.accumulated_responses],
398 |             "total_responses": len(self.accumulated_responses),
399 |             "consensus_confidence": "high",  # Consensus complete
400 |         }
401 | 
402 |         response_data["next_steps"] = (
403 |             "CONSENSUS GATHERING IS COMPLETE. You MUST now synthesize all perspectives and present:\n"
404 |             "1. Key points of AGREEMENT across models\n"
405 |             "2. Key points of DISAGREEMENT and why they differ\n"
406 |             "3. Your final consolidated recommendation\n"
407 |             "4. Specific, actionable next steps for implementation\n"
408 |             "5. Critical risks or concerns that must be addressed"
409 |         )
410 | 
411 |         return response_data
412 | 
413 |     def handle_work_continuation(self, response_data: dict, request) -> dict:
414 |         """Handle continuation between consensus steps."""
415 |         current_idx = request.current_model_index or 0
416 | 
417 |         if request.step_number == 1:
418 |             # After CLI Agent's initial analysis, prepare to consult first model
419 |             response_data["status"] = "consulting_models"
420 |             response_data["next_model"] = self.models_to_consult[0] if self.models_to_consult else None
421 |             response_data["next_steps"] = (
422 |                 "Your initial analysis is complete. The tool will now consult the specified models."
423 |             )
424 |         elif current_idx < len(self.models_to_consult):
425 |             next_model = self.models_to_consult[current_idx]
426 |             response_data["status"] = "consulting_next_model"
427 |             response_data["next_model"] = next_model
428 |             response_data["models_remaining"] = len(self.models_to_consult) - current_idx
429 |             response_data["next_steps"] = f"Model consultation in progress. Next: {next_model['model']}"
430 |         else:
431 |             response_data["status"] = "ready_for_synthesis"
432 |             response_data["next_steps"] = "All models consulted. Ready for final synthesis."
433 | 
434 |         return response_data
435 | 
436 |     async def execute_workflow(self, arguments: dict[str, Any]) -> list:
437 |         """Override execute_workflow to handle model consultations between steps."""
438 | 
439 |         # Store arguments
440 |         self._current_arguments = arguments
441 | 
442 |         # Validate request
443 |         request = self.get_workflow_request_model()(**arguments)
444 | 
445 |         # Resolve existing continuation_id or create a new one on first step
446 |         continuation_id = request.continuation_id
447 | 
448 |         if request.step_number == 1:
449 |             if not continuation_id:
450 |                 clean_args = {k: v for k, v in arguments.items() if k not in ["_model_context", "_resolved_model_name"]}
451 |                 continuation_id = create_thread(self.get_name(), clean_args)
452 |                 request.continuation_id = continuation_id
453 |                 arguments["continuation_id"] = continuation_id
454 |                 self.work_history = []
455 |                 self.consolidated_findings = ConsolidatedFindings()
456 | 
457 |             # Store the original proposal from step 1 - this is what all models should see
458 |             self.store_initial_issue(request.step)
459 |             self.initial_request = request.step
460 |             self.models_to_consult = request.models or []
461 |             self.accumulated_responses = []
462 |             # Set total steps: len(models) (each step includes consultation + response)
463 |             request.total_steps = len(self.models_to_consult)
464 | 
465 |         # For all steps (1 through total_steps), consult the corresponding model
466 |         if request.step_number <= request.total_steps:
467 |             # Calculate which model to consult for this step
468 |             model_idx = request.step_number - 1  # 0-based index
469 | 
470 |             if model_idx < len(self.models_to_consult):
471 |                 # Track workflow state for conversation memory
472 |                 step_data = self.prepare_step_data(request)
473 |                 self.work_history.append(step_data)
474 |                 self._update_consolidated_findings(step_data)
475 | 
476 |                 # Consult the model for this step
477 |                 model_response = await self._consult_model(self.models_to_consult[model_idx], request)
478 | 
479 |                 # Add to accumulated responses
480 |                 self.accumulated_responses.append(model_response)
481 | 
482 |                 # Include the model response in the step data
483 |                 response_data = {
484 |                     "status": "model_consulted",
485 |                     "step_number": request.step_number,
486 |                     "total_steps": request.total_steps,
487 |                     "model_consulted": model_response["model"],
488 |                     "model_stance": model_response.get("stance", "neutral"),
489 |                     "model_response": model_response,
490 |                     "current_model_index": model_idx + 1,
491 |                     "next_step_required": request.step_number < request.total_steps,
492 |                 }
493 | 
494 |                 # Add CLAI Agent's analysis to step 1
495 |                 if request.step_number == 1:
496 |                     response_data["agent_analysis"] = {
497 |                         "initial_analysis": request.step,
498 |                         "findings": request.findings,
499 |                     }
500 |                     response_data["status"] = "analysis_and_first_model_consulted"
501 | 
502 |                 # Check if this is the final step
503 |                 if request.step_number == request.total_steps:
504 |                     response_data["status"] = "consensus_workflow_complete"
505 |                     response_data["consensus_complete"] = True
506 |                     response_data["complete_consensus"] = {
507 |                         "initial_prompt": self.original_proposal if self.original_proposal else self.initial_prompt,
508 |                         "models_consulted": [
509 |                             f"{m['model']}:{m.get('stance', 'neutral')}" for m in self.accumulated_responses
510 |                         ],
511 |                         "total_responses": len(self.accumulated_responses),
512 |                         "consensus_confidence": "high",
513 |                     }
514 |                     response_data["next_steps"] = (
515 |                         "CONSENSUS GATHERING IS COMPLETE. Synthesize all perspectives and present:\n"
516 |                         "1. Key points of AGREEMENT across models\n"
517 |                         "2. Key points of DISAGREEMENT and why they differ\n"
518 |                         "3. Your final consolidated recommendation\n"
519 |                         "4. Specific, actionable next steps for implementation\n"
520 |                         "5. Critical risks or concerns that must be addressed"
521 |                     )
522 |                 else:
523 |                     response_data["next_steps"] = (
524 |                         f"Model {model_response['model']} has provided its {model_response.get('stance', 'neutral')} "
525 |                         f"perspective. Please analyze this response and call {self.get_name()} again with:\n"
526 |                         f"- step_number: {request.step_number + 1}\n"
527 |                         f"- findings: Summarize key points from this model's response"
528 |                     )
529 | 
530 |                 # Add continuation information and workflow customization
531 |                 response_data = self.customize_workflow_response(response_data, request)
532 | 
533 |                 # Ensure consensus-specific metadata is attached
534 |                 self._add_workflow_metadata(response_data, arguments)
535 | 
536 |                 if continuation_id:
537 |                     self.store_conversation_turn(continuation_id, response_data, request)
538 |                     continuation_offer = self._build_continuation_offer(continuation_id)
539 |                     if continuation_offer:
540 |                         response_data["continuation_offer"] = continuation_offer
541 | 
542 |                 return [TextContent(type="text", text=json.dumps(response_data, indent=2, ensure_ascii=False))]
543 | 
544 |         # Otherwise, use standard workflow execution
545 |         return await super().execute_workflow(arguments)
546 | 
547 |     def _build_continuation_offer(self, continuation_id: str) -> dict[str, Any] | None:
548 |         """Create a continuation offer without exposing prior model responses."""
549 |         try:
550 |             from tools.models import ContinuationOffer
551 | 
552 |             thread = get_thread(continuation_id)
553 |             if thread and thread.turns:
554 |                 remaining_turns = max(0, MAX_CONVERSATION_TURNS - len(thread.turns))
555 |             else:
556 |                 remaining_turns = MAX_CONVERSATION_TURNS - 1
557 | 
558 |             # Provide a neutral note specific to consensus workflow
559 |             note = (
560 |                 f"Consensus workflow can continue for {remaining_turns} more exchanges."
561 |                 if remaining_turns > 0
562 |                 else "Consensus workflow continuation limit reached."
563 |             )
564 | 
565 |             continuation_offer = ContinuationOffer(
566 |                 continuation_id=continuation_id,
567 |                 note=note,
568 |                 remaining_turns=remaining_turns,
569 |             )
570 |             return continuation_offer.model_dump()
571 |         except Exception:
572 |             return None
573 | 
574 |     async def _consult_model(self, model_config: dict, request) -> dict:
575 |         """Consult a single model and return its response."""
576 |         try:
577 |             # Import and create ModelContext once at the beginning
578 |             from utils.model_context import ModelContext
579 | 
580 |             # Get the provider for this model
581 |             model_name = model_config["model"]
582 |             provider = self.get_model_provider(model_name)
583 | 
584 |             # Create model context once and reuse for both file processing and temperature validation
585 |             model_context = ModelContext(model_name=model_name)
586 | 
587 |             # Prepare the prompt with any relevant files
588 |             # Use continuation_id=None for blinded consensus - each model should only see
589 |             # original prompt + files, not conversation history or other model responses
590 |             # CRITICAL: Use the original proposal from step 1, NOT what's in request.step for steps 2+!
591 |             # Steps 2+ contain summaries/notes that must NEVER be sent to other models
592 |             prompt = self.original_proposal if self.original_proposal else self.initial_prompt
593 |             if request.relevant_files:
594 |                 file_content, _ = self._prepare_file_content_for_prompt(
595 |                     request.relevant_files,
596 |                     None,  # Use None instead of request.continuation_id for blinded consensus
597 |                     "Context files",
598 |                     model_context=model_context,
599 |                 )
600 |                 if file_content:
601 |                     prompt = f"{prompt}\n\n=== CONTEXT FILES ===\n{file_content}\n=== END CONTEXT ==="
602 | 
603 |             # Get stance-specific system prompt
604 |             stance = model_config.get("stance", "neutral")
605 |             stance_prompt = model_config.get("stance_prompt")
606 |             system_prompt = self._get_stance_enhanced_prompt(stance, stance_prompt)
607 | 
608 |             # Validate temperature against model constraints (respects supports_temperature)
609 |             validated_temperature, temp_warnings = self.validate_and_correct_temperature(
610 |                 self.get_default_temperature(), model_context
611 |             )
612 | 
613 |             # Log any temperature corrections
614 |             for warning in temp_warnings:
615 |                 logger.warning(warning)
616 | 
617 |             # Call the model with validated temperature
618 |             response = provider.generate_content(
619 |                 prompt=prompt,
620 |                 model_name=model_name,
621 |                 system_prompt=system_prompt,
622 |                 temperature=validated_temperature,
623 |                 thinking_mode="medium",
624 |                 images=request.images if request.images else None,
625 |             )
626 | 
627 |             return {
628 |                 "model": model_name,
629 |                 "stance": stance,
630 |                 "status": "success",
631 |                 "verdict": response.content,
632 |                 "metadata": {
633 |                     "provider": provider.get_provider_type().value,
634 |                     "model_name": model_name,
635 |                 },
636 |             }
637 | 
638 |         except Exception as e:
639 |             logger.exception("Error consulting model %s", model_config)
640 |             return {
641 |                 "model": model_config.get("model", "unknown"),
642 |                 "stance": model_config.get("stance", "neutral"),
643 |                 "status": "error",
644 |                 "error": str(e),
645 |             }
646 | 
647 |     def _get_stance_enhanced_prompt(self, stance: str, custom_stance_prompt: str | None = None) -> str:
648 |         """Get the system prompt with stance injection."""
649 |         base_prompt = CONSENSUS_PROMPT
650 | 
651 |         if custom_stance_prompt:
652 |             return base_prompt.replace("{stance_prompt}", custom_stance_prompt)
653 | 
654 |         stance_prompts = {
655 |             "for": """SUPPORTIVE PERSPECTIVE WITH INTEGRITY
656 | 
657 | You are tasked with advocating FOR this proposal, but with CRITICAL GUARDRAILS:
658 | 
659 | MANDATORY ETHICAL CONSTRAINTS:
660 | - This is NOT a debate for entertainment. You MUST act in good faith and in the best interest of the questioner
661 | - You MUST think deeply about whether supporting this idea is safe, sound, and passes essential requirements
662 | - You MUST be direct and unequivocal in saying "this is a bad idea" when it truly is
663 | - There must be at least ONE COMPELLING reason to be optimistic, otherwise DO NOT support it
664 | 
665 | WHEN TO REFUSE SUPPORT (MUST OVERRIDE STANCE):
666 | - If the idea is fundamentally harmful to users, project, or stakeholders
667 | - If implementation would violate security, privacy, or ethical standards
668 | - If the proposal is technically infeasible within realistic constraints
669 | - If costs/risks dramatically outweigh any potential benefits
670 | 
671 | YOUR SUPPORTIVE ANALYSIS SHOULD:
672 | - Identify genuine strengths and opportunities
673 | - Propose solutions to overcome legitimate challenges
674 | - Highlight synergies with existing systems
675 | - Suggest optimizations that enhance value
676 | - Present realistic implementation pathways
677 | 
678 | Remember: Being "for" means finding the BEST possible version of the idea IF it has merit, not blindly supporting bad ideas.""",
679 |             "against": """CRITICAL PERSPECTIVE WITH RESPONSIBILITY
680 | 
681 | You are tasked with critiquing this proposal, but with ESSENTIAL BOUNDARIES:
682 | 
683 | MANDATORY FAIRNESS CONSTRAINTS:
684 | - You MUST NOT oppose genuinely excellent, common-sense ideas just to be contrarian
685 | - You MUST acknowledge when a proposal is fundamentally sound and well-conceived
686 | - You CANNOT give harmful advice or recommend against beneficial changes
687 | - If the idea is outstanding, say so clearly while offering constructive refinements
688 | 
689 | WHEN TO MODERATE CRITICISM (MUST OVERRIDE STANCE):
690 | - If the proposal addresses critical user needs effectively
691 | - If it follows established best practices with good reason
692 | - If benefits clearly and substantially outweigh risks
693 | - If it's the obvious right solution to the problem
694 | 
695 | YOUR CRITICAL ANALYSIS SHOULD:
696 | - Identify legitimate risks and failure modes
697 | - Point out overlooked complexities
698 | - Suggest more efficient alternatives
699 | - Highlight potential negative consequences
700 | - Question assumptions that may be flawed
701 | 
702 | Remember: Being "against" means rigorous scrutiny to ensure quality, not undermining good ideas that deserve support.""",
703 |             "neutral": """BALANCED PERSPECTIVE
704 | 
705 | Provide objective analysis considering both positive and negative aspects. However, if there is overwhelming evidence
706 | that the proposal clearly leans toward being exceptionally good or particularly problematic, you MUST accurately
707 | reflect this reality. Being "balanced" means being truthful about the weight of evidence, not artificially creating
708 | 50/50 splits when the reality is 90/10.
709 | 
710 | Your analysis should:
711 | - Present all significant pros and cons discovered
712 | - Weight them according to actual impact and likelihood
713 | - If evidence strongly favors one conclusion, clearly state this
714 | - Provide proportional coverage based on the strength of arguments
715 | - Help the questioner see the true balance of considerations
716 | 
717 | Remember: Artificial balance that misrepresents reality is not helpful. True balance means accurate representation
718 | of the evidence, even when it strongly points in one direction.""",
719 |         }
720 | 
721 |         stance_prompt = stance_prompts.get(stance, stance_prompts["neutral"])
722 |         return base_prompt.replace("{stance_prompt}", stance_prompt)
723 | 
724 |     def customize_workflow_response(self, response_data: dict, request) -> dict:
725 |         """Customize response for consensus workflow."""
726 |         # Store model responses in the response for tracking
727 |         if self.accumulated_responses:
728 |             response_data["accumulated_responses"] = self.accumulated_responses
729 | 
730 |         # Add consensus-specific fields
731 |         if request.step_number == 1:
732 |             response_data["consensus_workflow_status"] = "initial_analysis_complete"
733 |         elif request.step_number < request.total_steps - 1:
734 |             response_data["consensus_workflow_status"] = "consulting_models"
735 |         else:
736 |             response_data["consensus_workflow_status"] = "ready_for_synthesis"
737 | 
738 |         # Customize metadata for consensus workflow
739 |         self._customize_consensus_metadata(response_data, request)
740 | 
741 |         return response_data
742 | 
743 |     def _customize_consensus_metadata(self, response_data: dict, request) -> None:
744 |         """
745 |         Customize metadata for consensus workflow to accurately reflect multi-model nature.
746 | 
747 |         The default workflow metadata shows the model running Agent's analysis steps,
748 |         but consensus is a multi-model tool that consults different models. We need
749 |         to provide accurate metadata that reflects this.
750 |         """
751 |         if "metadata" not in response_data:
752 |             response_data["metadata"] = {}
753 | 
754 |         metadata = response_data["metadata"]
755 | 
756 |         # Always preserve tool_name
757 |         metadata["tool_name"] = self.get_name()
758 | 
759 |         if request.step_number == request.total_steps:
760 |             # Final step - show comprehensive consensus metadata
761 |             models_consulted = []
762 |             if self.models_to_consult:
763 |                 models_consulted = [f"{m['model']}:{m.get('stance', 'neutral')}" for m in self.models_to_consult]
764 | 
765 |             metadata.update(
766 |                 {
767 |                     "workflow_type": "multi_model_consensus",
768 |                     "models_consulted": models_consulted,
769 |                     "consensus_complete": True,
770 |                     "total_models": len(self.models_to_consult) if self.models_to_consult else 0,
771 |                 }
772 |             )
773 | 
774 |             # Remove the misleading single model metadata
775 |             metadata.pop("model_used", None)
776 |             metadata.pop("provider_used", None)
777 | 
778 |         else:
779 |             # Intermediate steps - show consensus workflow in progress
780 |             models_to_consult = []
781 |             if self.models_to_consult:
782 |                 models_to_consult = [f"{m['model']}:{m.get('stance', 'neutral')}" for m in self.models_to_consult]
783 | 
784 |             metadata.update(
785 |                 {
786 |                     "workflow_type": "multi_model_consensus",
787 |                     "models_to_consult": models_to_consult,
788 |                     "consultation_step": request.step_number,
789 |                     "total_consultation_steps": request.total_steps,
790 |                 }
791 |             )
792 | 
793 |             # Remove the misleading single model metadata that shows Agent's execution model
794 |             # instead of the models being consulted
795 |             metadata.pop("model_used", None)
796 |             metadata.pop("provider_used", None)
797 | 
798 |     def _add_workflow_metadata(self, response_data: dict, arguments: dict[str, Any]) -> None:
799 |         """
800 |         Override workflow metadata addition for consensus tool.
801 | 
802 |         The consensus tool doesn't use single model metadata because it's a multi-model
803 |         workflow. Instead, we provide consensus-specific metadata that accurately
804 |         reflects the models being consulted.
805 |         """
806 |         # Initialize metadata if not present
807 |         if "metadata" not in response_data:
808 |             response_data["metadata"] = {}
809 | 
810 |         # Add basic tool metadata
811 |         response_data["metadata"]["tool_name"] = self.get_name()
812 | 
813 |         # The consensus-specific metadata is already added by _customize_consensus_metadata
814 |         # which is called from customize_workflow_response. We don't add the standard
815 |         # single-model metadata (model_used, provider_used) because it's misleading
816 |         # for a multi-model consensus workflow.
817 | 
818 |         logger.debug(
819 |             f"[CONSENSUS_METADATA] {self.get_name()}: Using consensus-specific metadata instead of single-model metadata"
820 |         )
821 | 
822 |     def store_initial_issue(self, step_description: str):
823 |         """Store initial prompt for model consultations."""
824 |         self.original_proposal = step_description
825 |         self.initial_prompt = step_description  # Keep for backward compatibility
826 | 
827 |     # Required abstract methods from BaseTool
828 |     def get_request_model(self):
829 |         """Return the consensus workflow-specific request model."""
830 |         return ConsensusRequest
831 | 
832 |     async def prepare_prompt(self, request) -> str:  # noqa: ARG002
833 |         """Not used - workflow tools use execute_workflow()."""
834 |         return ""  # Workflow tools use execute_workflow() directly
835 | 
```

--------------------------------------------------------------------------------
/tools/precommit.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Precommit Workflow tool - Step-by-step pre-commit validation with expert analysis
  3 | 
  4 | This tool provides a structured workflow for comprehensive pre-commit validation.
  5 | It guides the CLI agent through systematic investigation steps with forced pauses between each step
  6 | to ensure thorough code examination, git change analysis, and issue detection before proceeding.
  7 | The tool supports finding updates and expert analysis integration.
  8 | 
  9 | Key features:
 10 | - Step-by-step pre-commit investigation workflow with progress tracking
 11 | - Context-aware file embedding (references during investigation, full content for analysis)
 12 | - Automatic git repository discovery and change analysis
 13 | - Expert analysis integration with external models (default)
 14 | - Support for multiple repositories and change types
 15 | - Configurable validation type (external with expert model or internal only)
 16 | """
 17 | 
 18 | import logging
 19 | from typing import TYPE_CHECKING, Any, Literal, Optional
 20 | 
 21 | from pydantic import Field, model_validator
 22 | 
 23 | if TYPE_CHECKING:
 24 |     from tools.models import ToolModelCategory
 25 | 
 26 | from config import TEMPERATURE_ANALYTICAL
 27 | from systemprompts import PRECOMMIT_PROMPT
 28 | from tools.shared.base_models import WorkflowRequest
 29 | 
 30 | from .workflow.base import WorkflowTool
 31 | 
 32 | logger = logging.getLogger(__name__)
 33 | 
 34 | # Tool-specific field descriptions for precommit workflow
 35 | PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS = {
 36 |     "step": (
 37 |         "Step 1: outline how you'll validate the git changes. Later steps: report findings. Review diffs and impacts, use `relevant_files`, and avoid pasting large snippets."
 38 |     ),
 39 |     "step_number": "Current pre-commit step number (starts at 1).",
 40 |     "total_steps": (
 41 |         "Planned number of validation steps. External validation: use at most three (analysis → follow-ups → summary). Internal validation: a single step. Honour these limits when resuming via continuation_id."
 42 |     ),
 43 |     "next_step_required": (
 44 |         "True to continue with another step, False when validation is complete. "
 45 |         "CRITICAL: If total_steps>=3 or when `precommit_type = external`, set to True until the final step. "
 46 |         "When continuation_id is provided: Follow the same validation rules based on precommit_type."
 47 |     ),
 48 |     "findings": "Record git diff insights, risks, missing tests, security concerns, and positives; update previous notes as you go.",
 49 |     "files_checked": "Absolute paths for every file examined, including ruled-out candidates.",
 50 |     "relevant_files": "Absolute paths of files involved in the change or validation (code, configs, tests, docs). Must be absolute full non-abbreviated paths.",
 51 |     "relevant_context": "Key functions/methods touched by the change (e.g. 'Class.method', 'function_name').",
 52 |     "issues_found": "List issues with severity (critical/high/medium/low) plus descriptions (bugs, security, performance, coverage).",
 53 |     "precommit_type": "'external' (default, triggers expert model) or 'internal' (local-only validation).",
 54 |     "images": "Optional absolute paths to screenshots or diagrams that aid validation.",
 55 |     "path": "Absolute path to the repository root. Required in step 1.",
 56 |     "compare_to": "Optional git ref (branch/tag/commit) to diff against; falls back to staged/unstaged changes.",
 57 |     "include_staged": "Whether to inspect staged changes (ignored when `compare_to` is set).",
 58 |     "include_unstaged": "Whether to inspect unstaged changes (ignored when `compare_to` is set).",
 59 |     "focus_on": "Optional emphasis areas such as security, performance, or test coverage.",
 60 |     "severity_filter": "Lowest severity to include when reporting issues.",
 61 | }
 62 | 
 63 | 
 64 | class PrecommitRequest(WorkflowRequest):
 65 |     """Request model for precommit workflow investigation steps"""
 66 | 
 67 |     # Required fields for each investigation step
 68 |     step: str = Field(..., description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["step"])
 69 |     step_number: int = Field(..., description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["step_number"])
 70 |     total_steps: int = Field(..., description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["total_steps"])
 71 |     next_step_required: bool = Field(..., description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"])
 72 | 
 73 |     # Investigation tracking fields
 74 |     findings: str = Field(..., description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["findings"])
 75 |     files_checked: list[str] = Field(
 76 |         default_factory=list, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["files_checked"]
 77 |     )
 78 |     relevant_files: list[str] = Field(
 79 |         default_factory=list, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"]
 80 |     )
 81 |     relevant_context: list[str] = Field(
 82 |         default_factory=list, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["relevant_context"]
 83 |     )
 84 |     issues_found: list[dict] = Field(
 85 |         default_factory=list, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["issues_found"]
 86 |     )
 87 |     precommit_type: Optional[Literal["external", "internal"]] = Field(
 88 |         "external", description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["precommit_type"]
 89 |     )
 90 | 
 91 |     # Optional images for visual validation
 92 |     images: Optional[list[str]] = Field(default=None, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["images"])
 93 | 
 94 |     # Precommit-specific fields (only used in step 1 to initialize)
 95 |     # Required for step 1, validated in model_validator
 96 |     path: Optional[str] = Field(None, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["path"])
 97 |     compare_to: Optional[str] = Field(None, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["compare_to"])
 98 |     include_staged: Optional[bool] = Field(True, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["include_staged"])
 99 |     include_unstaged: Optional[bool] = Field(
100 |         True, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["include_unstaged"]
101 |     )
102 |     focus_on: Optional[str] = Field(None, description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["focus_on"])
103 |     severity_filter: Optional[Literal["critical", "high", "medium", "low", "all"]] = Field(
104 |         "all", description=PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["severity_filter"]
105 |     )
106 | 
107 |     # Override inherited fields to exclude them from schema (except model which needs to be available)
108 |     temperature: Optional[float] = Field(default=None, exclude=True)
109 |     thinking_mode: Optional[str] = Field(default=None, exclude=True)
110 | 
111 |     @model_validator(mode="after")
112 |     def validate_step_one_requirements(self):
113 |         """Ensure step 1 has required path field."""
114 |         if self.step_number == 1 and not self.path:
115 |             raise ValueError("Step 1 requires 'path' field to specify git repository location")
116 |         return self
117 | 
118 | 
119 | class PrecommitTool(WorkflowTool):
120 |     """
121 |     Precommit workflow tool for step-by-step pre-commit validation and expert analysis.
122 | 
123 |     This tool implements a structured pre-commit validation workflow that guides users through
124 |     methodical investigation steps, ensuring thorough change examination, issue identification,
125 |     and validation before reaching conclusions. It supports complex validation scenarios including
126 |     multi-repository analysis, security review, performance validation, and integration testing.
127 |     """
128 | 
129 |     def __init__(self):
130 |         super().__init__()
131 |         self.initial_request = None
132 |         self.git_config = {}
133 | 
134 |     def get_name(self) -> str:
135 |         return "precommit"
136 | 
137 |     def get_description(self) -> str:
138 |         return (
139 |             "Validates git changes and repository state before committing with systematic analysis. "
140 |             "Use for multi-repository validation, security review, change impact assessment, and completeness verification. "
141 |             "Guides through structured investigation with expert analysis."
142 |         )
143 | 
144 |     def get_system_prompt(self) -> str:
145 |         return PRECOMMIT_PROMPT
146 | 
147 |     def get_default_temperature(self) -> float:
148 |         return TEMPERATURE_ANALYTICAL
149 | 
150 |     def get_model_category(self) -> "ToolModelCategory":
151 |         """Precommit requires thorough analysis and reasoning"""
152 |         from tools.models import ToolModelCategory
153 | 
154 |         return ToolModelCategory.EXTENDED_REASONING
155 | 
156 |     def get_workflow_request_model(self):
157 |         """Return the precommit workflow-specific request model."""
158 |         return PrecommitRequest
159 | 
160 |     def get_input_schema(self) -> dict[str, Any]:
161 |         """Generate input schema using WorkflowSchemaBuilder with precommit-specific overrides."""
162 |         from .workflow.schema_builders import WorkflowSchemaBuilder
163 | 
164 |         # Precommit workflow-specific field overrides
165 |         precommit_field_overrides = {
166 |             "step": {
167 |                 "type": "string",
168 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["step"],
169 |             },
170 |             "step_number": {
171 |                 "type": "integer",
172 |                 "minimum": 1,
173 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["step_number"],
174 |             },
175 |             "total_steps": {
176 |                 "type": "integer",
177 |                 "minimum": 3,
178 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["total_steps"],
179 |             },
180 |             "next_step_required": {
181 |                 "type": "boolean",
182 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"],
183 |             },
184 |             "findings": {
185 |                 "type": "string",
186 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["findings"],
187 |             },
188 |             "files_checked": {
189 |                 "type": "array",
190 |                 "items": {"type": "string"},
191 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["files_checked"],
192 |             },
193 |             "relevant_files": {
194 |                 "type": "array",
195 |                 "items": {"type": "string"},
196 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"],
197 |             },
198 |             "precommit_type": {
199 |                 "type": "string",
200 |                 "enum": ["external", "internal"],
201 |                 "default": "external",
202 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["precommit_type"],
203 |             },
204 |             "issues_found": {
205 |                 "type": "array",
206 |                 "items": {"type": "object"},
207 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["issues_found"],
208 |             },
209 |             "images": {
210 |                 "type": "array",
211 |                 "items": {"type": "string"},
212 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["images"],
213 |             },
214 |             # Precommit-specific fields (for step 1)
215 |             "path": {
216 |                 "type": "string",
217 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["path"],
218 |             },
219 |             "compare_to": {
220 |                 "type": "string",
221 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["compare_to"],
222 |             },
223 |             "include_staged": {
224 |                 "type": "boolean",
225 |                 "default": True,
226 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["include_staged"],
227 |             },
228 |             "include_unstaged": {
229 |                 "type": "boolean",
230 |                 "default": True,
231 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["include_unstaged"],
232 |             },
233 |             "focus_on": {
234 |                 "type": "string",
235 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["focus_on"],
236 |             },
237 |             "severity_filter": {
238 |                 "type": "string",
239 |                 "enum": ["critical", "high", "medium", "low", "all"],
240 |                 "default": "all",
241 |                 "description": PRECOMMIT_WORKFLOW_FIELD_DESCRIPTIONS["severity_filter"],
242 |             },
243 |         }
244 | 
245 |         # Use WorkflowSchemaBuilder with precommit-specific tool fields
246 |         return WorkflowSchemaBuilder.build_schema(
247 |             tool_specific_fields=precommit_field_overrides,
248 |             model_field_schema=self.get_model_field_schema(),
249 |             auto_mode=self.is_effective_auto_mode(),
250 |             tool_name=self.get_name(),
251 |         )
252 | 
253 |     def get_required_actions(
254 |         self, step_number: int, confidence: str, findings: str, total_steps: int, request=None
255 |     ) -> list[str]:
256 |         """Define required actions for each investigation phase.
257 | 
258 |         Now includes request parameter for continuation-aware decisions.
259 |         """
260 |         # Check for continuation - fast track mode
261 |         if request:
262 |             continuation_id = self.get_request_continuation_id(request)
263 |             precommit_type = self.get_precommit_type(request)
264 |             if continuation_id and precommit_type == "external":
265 |                 if step_number == 1:
266 |                     return [
267 |                         "Execute git status to see all changes",
268 |                         "Execute git diff --cached for staged changes (exclude binary files)",
269 |                         "Execute git diff for unstaged changes (exclude binary files)",
270 |                         "List any relevant untracked files as well.",
271 |                     ]
272 |                 else:
273 |                     return ["Complete validation and proceed to expert analysis with changeset file"]
274 | 
275 |         # Extract counts for normal flow
276 |         findings_count = len(findings.split("\n")) if findings else 0
277 |         issues_count = self.get_consolidated_issues_count()
278 | 
279 |         if step_number == 1:
280 |             # Initial pre-commit investigation tasks
281 |             return [
282 |                 "Search for all git repositories in the specified path using appropriate tools",
283 |                 "Check git status to identify staged, unstaged, and untracked changes as required",
284 |                 "Execute git status to see all changes",
285 |                 "Execute git diff --cached for staged changes (exclude binary files)",
286 |                 "Execute git diff for unstaged changes (exclude binary files)",
287 |                 "List any relevant untracked files as well.",
288 |                 "Understand what functionality was added, modified, or removed",
289 |                 "Identify the scope and intent of the changes being committed",
290 |                 "CRITICAL: You are on step 1 - you MUST set next_step_required=True and continue to at least step 3 minimum",
291 |             ]
292 |         elif step_number == 2:
293 |             # Need deeper investigation
294 |             actions = [
295 |                 "Examine the specific files you've identified as changed or relevant",
296 |                 "Analyze the logic and implementation details of modifications",
297 |                 "Check for potential issues: bugs, security risks, performance problems",
298 |                 "Verify that changes align with good coding practices and patterns",
299 |                 "Look for missing tests, documentation, or configuration updates",
300 |             ]
301 | 
302 |             # Add step validation reminder
303 |             if request and request.total_steps >= 3:
304 |                 actions.append(
305 |                     f"CRITICAL: You are on step 2 of {request.total_steps} minimum steps - you MUST set next_step_required=True unless this is the final step"
306 |                 )
307 | 
308 |             return actions
309 |         elif step_number >= 2 and (findings_count > 2 or issues_count > 0):
310 |             # Close to completion - need final verification
311 |             actions = [
312 |                 "Verify all identified issues have been properly documented",
313 |                 "Check for any missed dependencies or related files that need review",
314 |                 "Confirm the completeness and correctness of your assessment",
315 |                 "Ensure all security, performance, and quality concerns are captured",
316 |                 "Validate that your findings are comprehensive and actionable",
317 |             ]
318 | 
319 |             # Add step validation reminder
320 |             if request and request.total_steps >= 3 and step_number < request.total_steps:
321 |                 actions.append(
322 |                     f"CRITICAL: You are on step {step_number} of {request.total_steps} minimum steps - set next_step_required=True to continue"
323 |                 )
324 |             elif request and request.total_steps >= 3 and step_number >= request.total_steps:
325 |                 actions.append(
326 |                     f"You are on final step {step_number} - you may now set next_step_required=False to complete"
327 |                 )
328 | 
329 |             return actions
330 |         else:
331 |             # General investigation needed
332 |             actions = [
333 |                 "Continue examining the changes and their potential impact",
334 |                 "Gather more evidence using appropriate investigation tools",
335 |                 "Test your assumptions about the changes and their effects",
336 |                 "Look for patterns that confirm or refute your current assessment",
337 |             ]
338 | 
339 |             # Add step validation reminder for all other cases
340 |             if request and request.total_steps >= 3:
341 |                 if step_number < request.total_steps:
342 |                     actions.append(
343 |                         f"CRITICAL: You are on step {step_number} of {request.total_steps} minimum steps - set next_step_required=True to continue"
344 |                     )
345 |                 else:
346 |                     actions.append(
347 |                         f"You are on final step {step_number} - you may now set next_step_required=False to complete"
348 |                     )
349 | 
350 |             return actions
351 | 
352 |     def should_call_expert_analysis(self, consolidated_findings, request=None) -> bool:
353 |         """
354 |         Decide when to call external model based on investigation completeness.
355 | 
356 |         For continuations with external type, always proceed with expert analysis.
357 |         """
358 |         # Check if user requested to skip assistant model
359 |         if request and not self.get_request_use_assistant_model(request):
360 |             return False
361 | 
362 |         # For continuations with external type, always proceed with expert analysis
363 |         continuation_id = self.get_request_continuation_id(request)
364 |         if continuation_id and request.precommit_type == "external":
365 |             return True  # Always perform expert analysis for external continuations
366 | 
367 |         # Check if we have meaningful investigation data
368 |         return (
369 |             len(consolidated_findings.relevant_files) > 0
370 |             or len(consolidated_findings.findings) >= 2
371 |             or len(consolidated_findings.issues_found) > 0
372 |         )
373 | 
374 |     def prepare_expert_analysis_context(self, consolidated_findings) -> str:
375 |         """Prepare context for external model call for final pre-commit validation."""
376 |         context_parts = [
377 |             f"=== PRE-COMMIT ANALYSIS REQUEST ===\\n{self.initial_request or 'Pre-commit validation initiated'}\\n=== END REQUEST ==="
378 |         ]
379 | 
380 |         # Add investigation summary
381 |         investigation_summary = self._build_precommit_summary(consolidated_findings)
382 |         context_parts.append(
383 |             f"\\n=== AGENT'S PRE-COMMIT INVESTIGATION ===\\n{investigation_summary}\\n=== END INVESTIGATION ==="
384 |         )
385 | 
386 |         # Add git configuration context if available
387 |         if self.git_config:
388 |             config_text = "\\n".join(f"- {key}: {value}" for key, value in self.git_config.items())
389 |             context_parts.append(f"\\n=== GIT CONFIGURATION ===\\n{config_text}\\n=== END CONFIGURATION ===")
390 | 
391 |         # Add relevant methods/functions if available
392 |         if consolidated_findings.relevant_context:
393 |             methods_text = "\\n".join(f"- {method}" for method in consolidated_findings.relevant_context)
394 |             context_parts.append(f"\\n=== RELEVANT CODE ELEMENTS ===\\n{methods_text}\\n=== END CODE ELEMENTS ===")
395 | 
396 |         # Add issues found evolution if available
397 |         if consolidated_findings.issues_found:
398 |             issues_text = "\\n".join(
399 |                 f"[{issue.get('severity', 'unknown').upper()}] {issue.get('description', 'No description')}"
400 |                 for issue in consolidated_findings.issues_found
401 |             )
402 |             context_parts.append(f"\\n=== ISSUES IDENTIFIED ===\\n{issues_text}\\n=== END ISSUES ===")
403 | 
404 |         # Add assessment evolution if available
405 |         if consolidated_findings.hypotheses:
406 |             assessments_text = "\\n".join(
407 |                 f"Step {h['step']}: {h['hypothesis']}" for h in consolidated_findings.hypotheses
408 |             )
409 |             context_parts.append(f"\\n=== ASSESSMENT EVOLUTION ===\\n{assessments_text}\\n=== END ASSESSMENTS ===")
410 | 
411 |         # Add images if available
412 |         if consolidated_findings.images:
413 |             images_text = "\\n".join(f"- {img}" for img in consolidated_findings.images)
414 |             context_parts.append(
415 |                 f"\\n=== VISUAL VALIDATION INFORMATION ===\\n{images_text}\\n=== END VISUAL INFORMATION ==="
416 |             )
417 | 
418 |         return "\\n".join(context_parts)
419 | 
420 |     def _build_precommit_summary(self, consolidated_findings) -> str:
421 |         """Prepare a comprehensive summary of the pre-commit investigation."""
422 |         summary_parts = [
423 |             "=== SYSTEMATIC PRE-COMMIT INVESTIGATION SUMMARY ===",
424 |             f"Total steps: {len(consolidated_findings.findings)}",
425 |             f"Files examined: {len(consolidated_findings.files_checked)}",
426 |             f"Relevant files identified: {len(consolidated_findings.relevant_files)}",
427 |             f"Code elements analyzed: {len(consolidated_findings.relevant_context)}",
428 |             f"Issues identified: {len(consolidated_findings.issues_found)}",
429 |             "",
430 |             "=== INVESTIGATION PROGRESSION ===",
431 |         ]
432 | 
433 |         for finding in consolidated_findings.findings:
434 |             summary_parts.append(finding)
435 | 
436 |         return "\\n".join(summary_parts)
437 | 
438 |     def should_include_files_in_expert_prompt(self) -> bool:
439 |         """Include files in expert analysis for comprehensive validation."""
440 |         return True
441 | 
442 |     def should_embed_system_prompt(self) -> bool:
443 |         """Embed system prompt in expert analysis for proper context."""
444 |         return True
445 | 
446 |     def get_expert_thinking_mode(self) -> str:
447 |         """Use high thinking mode for thorough pre-commit analysis."""
448 |         return "high"
449 | 
450 |     def get_expert_analysis_instruction(self) -> str:
451 |         """Get specific instruction for pre-commit expert analysis."""
452 |         return (
453 |             "Please provide comprehensive pre-commit validation based on the investigation findings. "
454 |             "Focus on identifying any remaining issues, validating the completeness of the analysis, "
455 |             "and providing final recommendations for commit readiness."
456 |         )
457 | 
458 |     # Hook method overrides for precommit-specific behavior
459 | 
460 |     def prepare_step_data(self, request) -> dict:
461 |         """
462 |         Map precommit-specific fields for internal processing.
463 |         """
464 |         step_data = {
465 |             "step": request.step,
466 |             "step_number": request.step_number,
467 |             "findings": request.findings,
468 |             "files_checked": request.files_checked,
469 |             "relevant_files": request.relevant_files,
470 |             "relevant_context": request.relevant_context,
471 |             "issues_found": request.issues_found,
472 |             "precommit_type": request.precommit_type,
473 |             "hypothesis": request.findings,  # Map findings to hypothesis for compatibility
474 |             "images": request.images or [],
475 |             "confidence": "high",  # Dummy value for workflow_mixin compatibility
476 |         }
477 |         return step_data
478 | 
479 |     def should_skip_expert_analysis(self, request, consolidated_findings) -> bool:
480 |         """
481 |         Precommit workflow skips expert analysis only when precommit_type is "internal".
482 |         Default is always to use expert analysis (external).
483 |         For continuations with external type, always perform expert analysis immediately.
484 |         """
485 |         # If it's a continuation and precommit_type is external, don't skip
486 |         continuation_id = self.get_request_continuation_id(request)
487 |         if continuation_id and request.precommit_type != "internal":
488 |             return False  # Always do expert analysis for external continuations
489 | 
490 |         return request.precommit_type == "internal" and not request.next_step_required
491 | 
492 |     def store_initial_issue(self, step_description: str):
493 |         """Store initial request for expert analysis."""
494 |         self.initial_request = step_description
495 | 
496 |     # Override inheritance hooks for precommit-specific behavior
497 | 
498 |     def get_completion_status(self) -> str:
499 |         """Precommit tools use precommit-specific status."""
500 |         return "validation_complete_ready_for_commit"
501 | 
502 |     def get_completion_data_key(self) -> str:
503 |         """Precommit uses 'complete_validation' key."""
504 |         return "complete_validation"
505 | 
506 |     def get_final_analysis_from_request(self, request):
507 |         """Precommit tools use 'findings' field."""
508 |         return request.findings
509 | 
510 |     def get_precommit_type(self, request) -> str:
511 |         """Get precommit type from request. Hook method for clean inheritance."""
512 |         try:
513 |             return request.precommit_type or "external"
514 |         except AttributeError:
515 |             return "external"  # Default to external validation
516 | 
517 |     def get_consolidated_issues_count(self) -> int:
518 |         """Get count of issues from consolidated findings. Hook method for clean access."""
519 |         try:
520 |             return len(self.consolidated_findings.issues_found)
521 |         except AttributeError:
522 |             return 0
523 | 
524 |     def get_completion_message(self) -> str:
525 |         """Precommit-specific completion message."""
526 |         return (
527 |             "Pre-commit validation complete. You have identified all issues "
528 |             "and verified commit readiness. MANDATORY: Present the user with the complete validation results "
529 |             "and IMMEDIATELY proceed with commit if no critical issues found, or provide specific fix guidance "
530 |             "if issues need resolution. Focus on actionable next steps."
531 |         )
532 | 
533 |     def get_skip_reason(self) -> str:
534 |         """Precommit-specific skip reason."""
535 |         return (
536 |             "Completed comprehensive pre-commit validation with internal analysis only (no external model validation)"
537 |         )
538 | 
539 |     def get_skip_expert_analysis_status(self) -> str:
540 |         """Precommit-specific expert analysis skip status."""
541 |         return "skipped_due_to_internal_analysis_type"
542 | 
543 |     def prepare_work_summary(self) -> str:
544 |         """Precommit-specific work summary."""
545 |         return self._build_precommit_summary(self.consolidated_findings)
546 | 
547 |     def get_completion_next_steps_message(self, expert_analysis_used: bool = False) -> str:
548 |         """
549 |         Precommit-specific completion message.
550 | 
551 |         Args:
552 |             expert_analysis_used: True if expert analysis was successfully executed
553 |         """
554 |         base_message = (
555 |             "PRE-COMMIT VALIDATION IS COMPLETE. You may delete any `zen_precommit.changeset` created. You MUST now summarize "
556 |             "and present ALL validation results, identified issues with their severity levels, and exact commit recommendations. "
557 |             "Clearly state whether the changes are ready for commit or require fixes first. Provide concrete, actionable guidance for "
558 |             "any issues that need resolution—make it easy for a developer to understand exactly what needs to be "
559 |             "done before committing."
560 |         )
561 | 
562 |         # Add expert analysis guidance only when expert analysis was actually used
563 |         if expert_analysis_used:
564 |             expert_guidance = self.get_expert_analysis_guidance()
565 |             if expert_guidance:
566 |                 return f"{base_message}\n\n{expert_guidance}"
567 | 
568 |         return base_message
569 | 
570 |     def get_expert_analysis_guidance(self) -> str:
571 |         """
572 |         Get additional guidance for handling expert analysis results in pre-commit context.
573 | 
574 |         Returns:
575 |             Additional guidance text for validating and using expert analysis findings
576 |         """
577 |         return (
578 |             "IMPORTANT: Expert analysis has been provided above. You MUST carefully review "
579 |             "the expert's validation findings and security assessments. Cross-reference the "
580 |             "expert's analysis with your own investigation to ensure all critical issues are "
581 |             "addressed. Pay special attention to any security vulnerabilities, performance "
582 |             "concerns, or architectural issues identified by the expert review."
583 |         )
584 | 
585 |     def get_step_guidance_message(self, request) -> str:
586 |         """
587 |         Precommit-specific step guidance with detailed investigation instructions.
588 |         """
589 |         step_guidance = self.get_precommit_step_guidance(request.step_number, request)
590 |         return step_guidance["next_steps"]
591 | 
592 |     def get_precommit_step_guidance(self, step_number: int, request) -> dict[str, Any]:
593 |         """
594 |         Provide step-specific guidance for precommit workflow.
595 |         Uses get_required_actions to determine what needs to be done,
596 |         then formats those actions into appropriate guidance messages.
597 |         """
598 |         # Get the required actions from the single source of truth
599 |         required_actions = self.get_required_actions(
600 |             step_number,
601 |             request.precommit_type or "external",  # Using precommit_type as confidence proxy
602 |             request.findings or "",
603 |             request.total_steps,
604 |             request,  # Pass request for continuation-aware decisions
605 |         )
606 | 
607 |         # Check if this is a continuation to provide context-aware guidance
608 |         continuation_id = self.get_request_continuation_id(request)
609 |         is_external_continuation = continuation_id and request.precommit_type == "external"
610 |         is_internal_continuation = continuation_id and request.precommit_type == "internal"
611 | 
612 |         # Format the guidance based on step number and continuation status
613 |         if step_number == 1:
614 |             if is_external_continuation:
615 |                 # Fast-track mode for external continuations
616 |                 next_steps = (
617 |                     "You are on step 1 of MAXIMUM 2 steps. CRITICAL: Gather and save the complete git changeset NOW. "
618 |                     "MANDATORY ACTIONS:\\n"
619 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
620 |                     + "\\n\\nMANDATORY: The changeset may be large. You MUST save the required changeset as a 'zen_precommit.changeset' file "
621 |                     "(replacing any existing one) in your work directory and include the FULL absolute path in relevant_files (exclude any "
622 |                     "binary files). ONLY include the code changes, no extra commentary."
623 |                     "Set next_step_required=True and step_number=2 for the next call."
624 |                 )
625 |             elif is_internal_continuation:
626 |                 # Internal validation mode
627 |                 next_steps = (
628 |                     "Continuing previous conversation with internal validation only. The analysis will build "
629 |                     "upon the prior findings without external model validation. REQUIRED ACTIONS:\\n"
630 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
631 |                 )
632 |             else:
633 |                 # Normal flow for new validations
634 |                 next_steps = (
635 |                     f"MANDATORY: DO NOT call the {self.get_name()} tool again immediately. You MUST first investigate "
636 |                     f"the git repositories and changes using appropriate tools. CRITICAL AWARENESS: You need to:\\n"
637 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
638 |                     + f"\\n\\nOnly call {self.get_name()} again AFTER completing your investigation. "
639 |                     f"When you call {self.get_name()} next time, use step_number: {step_number + 1} "
640 |                     f"and report specific files examined, changes analyzed, and validation findings discovered."
641 |                 )
642 | 
643 |         elif step_number == 2:
644 |             # CRITICAL: Check if violating minimum step requirement
645 |             if (
646 |                 request.total_steps >= 3
647 |                 and request.step_number < request.total_steps
648 |                 and not request.next_step_required
649 |             ):
650 |                 next_steps = (
651 |                     f"ERROR: You set total_steps={request.total_steps} but next_step_required=False on step {request.step_number}. "
652 |                     f"This violates the minimum step requirement. You MUST set next_step_required=True until you reach the final step. "
653 |                     f"Call {self.get_name()} again with next_step_required=True and continue your investigation."
654 |                 )
655 |             elif is_external_continuation or (not request.next_step_required and request.precommit_type == "external"):
656 |                 # Fast-track completion or about to complete - ensure changeset is saved
657 |                 next_steps = (
658 |                     "Proceeding immediately to expert analysis. "
659 |                     f"MANDATORY: call {self.get_name()} tool immediately again, and set next_step_required=False to "
660 |                     f"trigger external validation NOW. "
661 |                     f"MANDATORY: Include the entire changeset! The changeset may be large. You MUST save the required "
662 |                     f"changeset as a 'zen_precommit.changeset' file (replacing any existing one) in your work directory "
663 |                     f"and include the FULL absolute path in relevant_files so the expert can access the complete changeset. "
664 |                     f"ONLY include the code changes, no extra commentary."
665 |                 )
666 |             else:
667 |                 # Normal flow - deeper analysis needed
668 |                 next_steps = (
669 |                     f"STOP! Do NOT call {self.get_name()} again yet. You are on step 2 of {request.total_steps} minimum required steps. "
670 |                     f"MANDATORY ACTIONS before calling {self.get_name()} step {step_number + 1}:\\n"
671 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
672 |                     + f"\\n\\nRemember: You MUST set next_step_required=True until step {request.total_steps}. "
673 |                     + f"Only call {self.get_name()} again with step_number: {step_number + 1} AFTER completing these validations."
674 |                 )
675 | 
676 |         elif step_number >= 3:
677 |             if not request.next_step_required and request.precommit_type == "external":
678 |                 # About to complete - ensure changeset is saved
679 |                 next_steps = (
680 |                     "Completing validation and proceeding to expert analysis. "
681 |                     "MANDATORY: Save the complete git changeset as a 'zen_precommit.changeset' file "
682 |                     "in your work directory and include the FULL absolute path in relevant_files."
683 |                 )
684 |             else:
685 |                 # Later steps - final verification
686 |                 next_steps = (
687 |                     f"WAIT! Your validation needs final verification. DO NOT call {self.get_name()} immediately. REQUIRED ACTIONS:\\n"
688 |                     + "\\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
689 |                     + f"\\n\\nREMEMBER: Ensure you have identified all potential issues and verified commit readiness. "
690 |                     f"Document findings with specific file references and issue descriptions, then call {self.get_name()} "
691 |                     f"with step_number: {step_number + 1}."
692 |                 )
693 |         else:
694 |             # Fallback for any other case - check minimum step violation first
695 |             if (
696 |                 request.total_steps >= 3
697 |                 and request.step_number < request.total_steps
698 |                 and not request.next_step_required
699 |             ):
700 |                 next_steps = (
701 |                     f"ERROR: You set total_steps={request.total_steps} but next_step_required=False on step {request.step_number}. "
702 |                     f"This violates the minimum step requirement. You MUST set next_step_required=True until step {request.total_steps}."
703 |                 )
704 |             elif not request.next_step_required and request.precommit_type == "external":
705 |                 next_steps = (
706 |                     "Completing validation. "
707 |                     "MANDATORY: Save complete git changeset as 'zen_precommit.changeset' file and include path in relevant_files, "
708 |                     "excluding any binary files."
709 |                 )
710 |             else:
711 |                 next_steps = (
712 |                     f"PAUSE VALIDATION. Before calling {self.get_name()} step {step_number + 1}, you MUST examine more code and changes. "
713 |                     + "Required: "
714 |                     + ", ".join(required_actions[:2])
715 |                     + ". "
716 |                     + f"Your next {self.get_name()} call (step_number: {step_number + 1}) must include "
717 |                     f"NEW evidence from actual change analysis, not just theories. NO recursive {self.get_name()} calls "
718 |                     f"without investigation work!"
719 |                 )
720 | 
721 |         return {"next_steps": next_steps}
722 | 
723 |     def customize_workflow_response(self, response_data: dict, request) -> dict:
724 |         """
725 |         Customize response to match precommit workflow format.
726 |         """
727 |         # Store initial request on first step
728 |         if request.step_number == 1:
729 |             self.initial_request = request.step
730 |             # Store git configuration for expert analysis
731 |             if request.path:
732 |                 self.git_config = {
733 |                     "path": request.path,
734 |                     "compare_to": request.compare_to,
735 |                     "include_staged": request.include_staged,
736 |                     "include_unstaged": request.include_unstaged,
737 |                     "severity_filter": request.severity_filter,
738 |                 }
739 | 
740 |         # Convert generic status names to precommit-specific ones
741 |         tool_name = self.get_name()
742 |         status_mapping = {
743 |             f"{tool_name}_in_progress": "validation_in_progress",
744 |             f"pause_for_{tool_name}": "pause_for_validation",
745 |             f"{tool_name}_required": "validation_required",
746 |             f"{tool_name}_complete": "validation_complete",
747 |         }
748 | 
749 |         if response_data["status"] in status_mapping:
750 |             response_data["status"] = status_mapping[response_data["status"]]
751 | 
752 |         # Rename status field to match precommit workflow
753 |         if f"{tool_name}_status" in response_data:
754 |             response_data["validation_status"] = response_data.pop(f"{tool_name}_status")
755 |             # Add precommit-specific status fields
756 |             response_data["validation_status"]["issues_identified"] = len(self.consolidated_findings.issues_found)
757 |             response_data["validation_status"]["precommit_type"] = request.precommit_type or "external"
758 | 
759 |         # Map complete_precommitworkflow to complete_validation
760 |         if f"complete_{tool_name}" in response_data:
761 |             response_data["complete_validation"] = response_data.pop(f"complete_{tool_name}")
762 | 
763 |         # Map the completion flag to match precommit workflow
764 |         if f"{tool_name}_complete" in response_data:
765 |             response_data["validation_complete"] = response_data.pop(f"{tool_name}_complete")
766 | 
767 |         return response_data
768 | 
769 |     # Required abstract methods from BaseTool
770 |     def get_request_model(self):
771 |         """Return the precommit workflow-specific request model."""
772 |         return PrecommitRequest
773 | 
774 |     async def prepare_prompt(self, request) -> str:
775 |         """Not used - workflow tools use execute_workflow()."""
776 |         return ""  # Workflow tools use execute_workflow() directly
777 | 
```
Page 19/25FirstPrevNextLast