main 7afc7c1cc96e cached
360 files
3.7 MB
991.5k tokens
2687 symbols
6 requests
Download .txt
Showing preview only (3,961K chars total). Download the full file or copy to clipboard to get everything.
Repository: BeehiveInnovations/pal-mcp-server
Branch: main
Commit: 7afc7c1cc96e
Files: 360
Total size: 3.7 MB

Directory structure:
gitextract_qj0m35_8/

├── .claude/
│   ├── commands/
│   │   └── fix-github-issue.md
│   └── settings.json
├── .coveragerc
├── .dockerignore
├── .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.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── claude_config_example.json
├── 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
├── docs/
│   ├── adding_providers.md
│   ├── adding_tools.md
│   ├── advanced-usage.md
│   ├── ai-collaboration.md
│   ├── ai_banter.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
│   ├── name-change.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
├── pal-mcp-server
├── providers/
│   ├── __init__.py
│   ├── azure_openai.py
│   ├── base.py
│   ├── custom.py
│   ├── dial.py
│   ├── gemini.py
│   ├── openai.py
│   ├── openai_compatible.py
│   ├── openrouter.py
│   ├── registries/
│   │   ├── __init__.py
│   │   ├── azure.py
│   │   ├── base.py
│   │   ├── custom.py
│   │   ├── dial.py
│   │   ├── gemini.py
│   │   ├── openai.py
│   │   ├── openrouter.py
│   │   └── xai.py
│   ├── registry.py
│   ├── registry_provider_mixin.py
│   ├── shared/
│   │   ├── __init__.py
│   │   ├── model_capabilities.py
│   │   ├── model_response.py
│   │   ├── provider_type.py
│   │   └── temperature.py
│   └── xai.py
├── pyproject.toml
├── pytest.ini
├── requirements-dev.txt
├── requirements.txt
├── run-server.ps1
├── run-server.sh
├── run_integration_tests.ps1
├── run_integration_tests.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.py
│   ├── test_planner_validation_old.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.txt
│   │   ├── default_codereviewer.txt
│   │   └── default_planner.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/
│   ├── CASSETTE_MAINTENANCE.md
│   ├── __init__.py
│   ├── 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_gpt51_for.json
│   │   ├── consensus_step1_gpt52_for.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.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_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.py
│   ├── test_consensus_integration.py
│   ├── test_consensus_schema.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.py
│   ├── test_listmodels_restrictions.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_openrouter_store_parameter.py
│   ├── test_parse_model_option.py
│   ├── test_path_traversal_security.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
├── 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

================================================
FILE CONTENTS
================================================

================================================
FILE: .claude/commands/fix-github-issue.md
================================================
Please analyze and fix the GitHub issue: $ARGUMENTS.

Follow these steps:

1. Use `gh issue view` to get the issue details
2. Understand the problem described in the issue
3. Search the codebase for relevant files
4. Implement the necessary changes to fix the issue
5. Write and run tests to verify the fix
6. Ensure code passes linting and type checking
7. Create a descriptive commit message
8. Push and create a PR

Remember to use the GitHub CLI (`gh`) for all GitHub-related tasks.


================================================
FILE: .claude/settings.json
================================================
{
  "permissions": {
    "allow": [
    ],
    "deny": []
  }
}

================================================
FILE: .coveragerc
================================================
[run]
source = gemini_server
omit = 
    */tests/*
    */venv/*
    */__pycache__/*
    */site-packages/*

[report]
exclude_lines =
    pragma: no cover
    def __repr__
    if self.debug:
    if settings.DEBUG
    raise AssertionError
    raise NotImplementedError
    if 0:
    if __name__ == .__main__.:
    if TYPE_CHECKING:
    class .*\bProtocol\):
    @(abc\.)?abstractmethod

[html]
directory = htmlcov

================================================
FILE: .dockerignore
================================================
# Git
.git
.gitignore

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/
.pal_venv/
ENV/
env.bak/
venv.bak/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
logs/*.log*
*.log

# Docker
Dockerfile*
docker-compose*
.dockerignore

# Documentation
docs/
README.md
*.md

# Tests
tests/
simulator_tests/
test_simulation_files/
pytest.ini

# Development
.env
.env.local
examples/
code_quality_checks.sh
run_integration_tests.sh

# Security - Sensitive files
*.key
*.pem
*.p12
*.pfx
*.crt
*.csr
secrets/
private/


================================================
FILE: .gitattributes
================================================
# Ensure shell scripts always have LF line endings on checkout
*.sh text eol=lf
*.bash text eol=lf

# Python files
*.py text eol=lf

# Shell script without extension
run-server text eol=lf
code_quality_checks text eol=lf
run_integration_tests text eol=lf

# General text files
*.md text
*.txt text
*.yml text
*.yaml text
*.json text
*.xml text

# Binary files
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [guidedways]


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: 🐞 Bug Report
description: Create a report to help us improve
labels: ["bug", "needs-triage"]
body:
  - type: markdown
    attributes:
      value: |
        Thank you for taking the time to file a bug report! Please provide as much detail as possible to help us reproduce and fix the issue.

  - type: input
    id: version
    attributes:
      label: Project Version
      description: "Which version are you using? (To see version: ./run-server.sh -v)"
      placeholder: "e.g., 9.4.1"
    validations:
      required: true

  - type: textarea
    id: description
    attributes:
      label: Bug Description
      description: A clear and concise description of what the bug is.
      placeholder: "When I run the `codereview` nothing happens"
    validations:
      required: true

  - type: textarea
    id: logs
    attributes:
      label: Relevant Log Output
      description: "Please copy and paste any relevant log output. Logs are stored under the `logs` folder in the pal folder. You an also use `./run-server.sh -f` to see logs"
      render: shell

  - type: dropdown
    id: environment
    attributes:
      label: Operating System
      description: What operating system are you running the Docker client on?
      options:
        - macOS
        - Windows
        - Linux
    validations:
      required: true

  - type: checkboxes
    id: no-duplicate-issues
    attributes:
      label: Sanity Checks
      description: "Before submitting, please confirm the following:"
      options:
        - label: I have searched the existing issues and this is not a duplicate.
          required: true
        - label: I am using `GEMINI_API_KEY`
          required: true
        - label: I am using `OPENAI_API_KEY`
          required: true
        - label: I am using `OPENROUTER_API_KEY`
          required: true
        - label: I am using `CUSTOM_API_URL`
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 💬 General Discussion
    url: https://github.com/BeehiveInnovations/pal-mcp-server/discussions
    about: Ask questions, share ideas, or discuss usage patterns with the community
  - name: 📚 Documentation
    url: https://github.com/BeehiveInnovations/pal-mcp-server/blob/main/README.md
    about: Check the README for setup instructions and usage examples
  - name: 🤝 Contributing Guide
    url: https://github.com/BeehiveInnovations/pal-mcp-server/blob/main/CONTRIBUTING.md
    about: Learn how to contribute to the project



================================================
FILE: .github/ISSUE_TEMPLATE/documentation.yml
================================================
name: 📖 Documentation Improvement
description: Report an issue or suggest an improvement for the documentation
labels: ["documentation", "good first issue"]
body:
  - type: input
    id: location
    attributes:
      label: Documentation Location
      description: "Which file or page has the issue? (e.g., README.md, CONTRIBUTING.md, CLAUDE.md)"
      placeholder: "e.g., README.md"
    validations:
      required: true

  - type: dropdown
    id: issue-type
    attributes:
      label: Type of Documentation Issue
      description: What kind of documentation improvement is this?
      options:
        - Typo or grammar error
        - Unclear or confusing explanation
        - Outdated information
        - Missing information
        - Code example doesn't work
        - Installation/setup instructions unclear
        - Tool usage examples need improvement
        - Other
    validations:
      required: true

  - type: textarea
    id: problem
    attributes:
      label: What is wrong with the documentation?
      description: "Please describe the problem. Be specific about what is unclear, incorrect, or missing."
      placeholder: "README is missing some details"
    validations:
      required: true

  - type: textarea
    id: suggestion
    attributes:
      label: Suggested Improvement
      description: "How can we make it better? If you can, please provide the exact text or changes you'd like to see."
      placeholder: "Please improve...."


  - type: dropdown
    id: audience
    attributes:
      label: Target Audience
      description: Which audience would benefit most from this improvement?
      options:
        - New users (first-time setup)
        - Developers (contributing to the project)
        - Advanced users (complex workflows)
        - All users
    validations:
      required: true



================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: ✨ Feature Request
description: Suggest an idea for this project
labels: ["enhancement", "needs-triage"]
body:
  - type: textarea
    id: problem-description
    attributes:
      label: What problem is this feature trying to solve?
      description: "A clear and concise description of the problem or user need. Why is this change needed?"
      placeholder: "Currently, I can only use one Gemini tool at a time. I want to be able to chain multiple tools together (e.g., analyze -> codereview -> thinkdeep) in a single workflow."
    validations:
      required: true

  - type: textarea
    id: proposed-solution
    attributes:
      label: Describe the solution you'd like
      description: A clear and concise description of what you want to happen. How would it work from a user's perspective?
      placeholder: "I'd like to be able to specify a workflow like 'analyze src/ then codereview the findings then use thinkdeep to suggest improvements' in a single command or configuration."
    validations:
      required: true

  - type: textarea
    id: alternatives
    attributes:
      label: Describe alternatives you've considered
      description: A clear and concise description of any alternative solutions or features you've considered.
      placeholder: "I considered manually running each tool sequentially, but automatic workflow chaining would be more efficient and ensure context is preserved between steps."

  - type: dropdown
    id: feature-type
    attributes:
      label: Feature Category
      description: What type of enhancement is this?
      options:
        - New tool (chat, codereview, debug, etc.)
        - Workflow improvement
        - Integration enhancement
        - Performance optimization
        - User experience improvement
        - Documentation enhancement
        - Other
    validations:
      required: true

  - type: checkboxes
    id: contribution
    attributes:
      label: Contribution
      options:
        - label: I am willing to submit a Pull Request to implement this feature.



================================================
FILE: .github/ISSUE_TEMPLATE/tool_addition.yml
================================================
name: 🛠️ New Gemini Tool Proposal
description: Propose a new PAL MCP tool (e.g., `summarize`, `fixer`, `refactor`)
labels: ["enhancement", "new-tool"]
body:
  - type: input
    id: tool-name
    attributes:
      label: Proposed Tool Name
      description: "What would the tool be called? (e.g., `summarize`, `docgen`, `refactor`)"
      placeholder: "e.g., `docgen`"
    validations:
      required: true

  - type: textarea
    id: purpose
    attributes:
      label: What is the primary purpose of this tool?
      description: "Explain the tool's core function and the value it provides to developers using Claude + PAL."
      placeholder: "This tool will automatically generate comprehensive documentation from code, extracting class and function signatures, docstrings, and creating usage examples."
    validations:
      required: true

  - type: textarea
    id: example-usage
    attributes:
      label: Example Usage in Claude Desktop
      description: "Show how a user would invoke this tool through Claude and what the expected output would look like."
      placeholder: |
        **User prompt to Claude:**
        "Use pal to generate documentation for my entire src/ directory"

        **Expected behavior:**
        - Analyze all Python files in src/
        - Extract classes, functions, and their docstrings
        - Generate structured markdown documentation
        - Include usage examples where possible
        - Return organized documentation with table of contents
      render: markdown
    validations:
      required: true

  - type: dropdown
    id: tool-category
    attributes:
      label: Tool Category
      description: What category does this tool fit into?
      options:
        - Code Analysis (like analyze)
        - Code Quality (like codereview)
        - Code Generation/Refactoring
        - Documentation Generation
        - Testing Support
        - Debugging Support (like debug)
        - Workflow Automation
        - Architecture Planning (like thinkdeep)
        - Other
    validations:
      required: true

  - type: textarea
    id: system-prompt
    attributes:
      label: Proposed System Prompt (Optional)
      description: "If you have ideas for how pal should be prompted for this tool, share them here."
      placeholder: |
        You are an expert technical documentation generator. Your task is to create comprehensive, user-friendly documentation from source code...

  - type: checkboxes
    id: contribution
    attributes:
      label: Contribution
      options:
        - label: I am willing to submit a Pull Request to implement this new tool.
        - label: I have checked that this tool doesn't overlap significantly with existing tools (analyze, codereview, debug, thinkdeep, chat).



================================================
FILE: .github/pull_request_template.md
================================================
## PR Title Format

**Please ensure your PR title follows [Conventional Commits](https://www.conventionalcommits.org/) format:**

### Version Bumping Types (trigger semantic release):
- `feat: <description>` - New features → **MINOR** version bump (1.1.0 → 1.2.0)
- `fix: <description>` - Bug fixes → **PATCH** version bump (1.1.0 → 1.1.1) 
- `perf: <description>` - Performance improvements → **PATCH** version bump (1.1.0 → 1.1.1)

### Breaking Changes (trigger MAJOR version bump):
For breaking changes, use any commit type above with `BREAKING CHANGE:` in the commit body or `!` after the type:
- `feat!: <description>` → **MAJOR** version bump (1.1.0 → 2.0.0)
- `fix!: <description>` → **MAJOR** version bump (1.1.0 → 2.0.0)

### Non-Versioning Types (no release):
- `build: <description>` - Build system changes
- `chore: <description>` - Maintenance tasks
- `ci: <description>` - CI/CD changes
- `docs: <description>` - Documentation only
- `refactor: <description>` - Code refactoring (no functional changes)
- `style: <description>` - Code style/formatting changes
- `test: <description>` - Test additions/changes

### Docker Build Triggering:

Docker builds are **independent** of versioning and trigger based on:

**Automatic**: When PRs modify relevant files:
- Python files (`*.py`), `requirements*.txt`, `pyproject.toml`
- Docker files (`Dockerfile`, `docker-compose.yml`, `.dockerignore`)

**Manual**: Add the `docker-build` label to force builds for any PR.

## Description

Please provide a clear and concise description of what this PR does.

## Changes Made

- [ ] List the specific changes made
- [ ] Include any breaking changes
- [ ] Note any dependencies added/removed

## Testing

**Please review our [Testing Guide](../docs/testing.md) before submitting.**

### Run all linting and tests (required):
```bash
# Activate virtual environment first
source venv/bin/activate

# Run comprehensive code quality checks (recommended)
./code_quality_checks.sh

# If you made tool changes, also run simulator tests
python communication_simulator_test.py
```

- [ ] All linting passes (ruff, black, isort)
- [ ] All unit tests pass
- [ ] **For new features**: Unit tests added in `tests/`
- [ ] **For tool changes**: Simulator tests added in `simulator_tests/`
- [ ] **For bug fixes**: Tests added to prevent regression
- [ ] Simulator tests pass (if applicable)
- [ ] Manual testing completed with realistic scenarios

## Related Issues

Fixes #(issue number)

## Checklist

- [ ] PR title follows the format guidelines above
- [ ] **Activated venv and ran code quality checks: `source venv/bin/activate && ./code_quality_checks.sh`**
- [ ] Self-review completed
- [ ] **Tests added for ALL changes** (see Testing section above)
- [ ] Documentation updated as needed
- [ ] All unit tests passing
- [ ] Relevant simulator tests passing (if tool changes)
- [ ] Ready for review

## Additional Notes

Any additional information that reviewers should know.

================================================
FILE: .github/workflows/docker-pr.yml
================================================
name: PR Docker Build

on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]
    paths:
      - '**.py'
      - 'requirements*.txt'
      - 'pyproject.toml'
      - 'Dockerfile'
      - 'docker-compose.yml'
      - '.dockerignore'

permissions:
  contents: read
  packages: write
  pull-requests: write

jobs:
  docker:
    name: Build Docker Image
    runs-on: ubuntu-latest
    if: |
      github.event.action == 'opened' ||
      github.event.action == 'synchronize' ||
      github.event.action == 'reopened' ||
      contains(github.event.pull_request.labels.*.name, 'docker-build')
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        if: github.event.pull_request.head.repo.full_name == github.repository
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            # PR-specific tag for testing
            type=raw,value=pr-${{ github.event.number }}-${{ github.sha }}
            type=raw,value=pr-${{ github.event.number }}

      - name: Build and push Docker image (internal PRs)
        if: github.event.pull_request.head.repo.full_name == github.repository
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build Docker image (fork PRs)
        if: github.event.pull_request.head.repo.full_name != github.repository
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: false
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Add Docker build comment (internal PRs)
        if: github.event.pull_request.head.repo.full_name == github.repository
        uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3
        with:
          header: docker-build
          message: |
            ## 🐳 Docker Build Complete
            
            **PR**: #${{ github.event.number }} | **Commit**: `${{ github.sha }}`
            
            ```
            ${{ steps.meta.outputs.tags }}
            ```
            
            **Test:** `docker pull ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}`
            
            **Claude config:**
            ```json
            {
              "mcpServers": {
                "pal": {
                  "command": "docker",
                  "args": ["run", "--rm", "-i", "-e", "GEMINI_API_KEY", "ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}"],
                  "env": { "GEMINI_API_KEY": "your-key" }
                }
              }
            }
            ```
            
            💡 Add `docker-build` label to manually trigger builds


      - name: Update job summary (internal PRs)
        if: github.event.pull_request.head.repo.full_name == github.repository
        run: |
          {
            echo "## 🐳 Docker Build Complete"
            echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}"
            echo '```'
            echo "${{ steps.meta.outputs.tags }}"
            echo '```'
          } >> $GITHUB_STEP_SUMMARY

      - name: Update job summary (fork PRs)
        if: github.event.pull_request.head.repo.full_name != github.repository
        run: |
          {
            echo "## 🐳 Docker Build Complete (Build Only)"
            echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}"
            echo "✅ Multi-platform Docker build successful"
            echo "Note: Fork PRs only build (no push) for security"
          } >> $GITHUB_STEP_SUMMARY


================================================
FILE: .github/workflows/docker-release.yml
================================================
name: Docker Release Build

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      tag:
        description: 'Tag to build (leave empty for latest release)'
        required: false
        type: string

permissions:
  contents: read
  packages: write

jobs:
  docker:
    name: Build and Push Docker Image
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # If triggered by workflow_dispatch with a tag, checkout that tag
          ref: ${{ inputs.tag || github.event.release.tag_name }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            # Tag with the release version
            type=semver,pattern={{version}},value=${{ inputs.tag || github.event.release.tag_name }}
            type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag || github.event.release.tag_name }}
            type=semver,pattern={{major}},value=${{ inputs.tag || github.event.release.tag_name }}
            # Also tag as latest for the most recent release
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Update release with Docker info
        if: github.event_name == 'release'
        run: |
          RELEASE_TAG="${{ github.event.release.tag_name }}"
          DOCKER_TAGS=$(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' ')
          
          # Add Docker information to the release
          gh release edit "$RELEASE_TAG" --notes-file - << EOF
          ${{ github.event.release.body }}
          
          ---
          
          ## 🐳 Docker Images
          
          This release is available as Docker images:
          
          $(echo "$DOCKER_TAGS" | sed 's/ghcr.io/- `ghcr.io/g' | sed 's/ /`\n/g')
          
          **Quick start with Docker:**
          \`\`\`bash
          docker pull ghcr.io/${{ github.repository }}:$RELEASE_TAG
          \`\`\`
          
          **Claude Desktop configuration:**
          \`\`\`json
          {
            "mcpServers": {
              "pal-mcp-server": {
                "command": "docker",
                "args": [
                  "run", "--rm", "-i",
                  "-e", "GEMINI_API_KEY",
                  "ghcr.io/${{ github.repository }}:$RELEASE_TAG"
                ],
                "env": {
                  "GEMINI_API_KEY": "your-api-key-here"
                }
              }
            }
          }
          \`\`\`
          EOF
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create deployment summary
        run: |
          echo "## 🐳 Docker Release Build Complete" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "**Release**: ${{ inputs.tag || github.event.release.tag_name }}" >> $GITHUB_STEP_SUMMARY
          echo "**Images built:**" >> $GITHUB_STEP_SUMMARY
          echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
          echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
          echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

================================================
FILE: .github/workflows/semantic-pr.yml
================================================
---
name: Semantic PR

on:
  pull_request:
    types: [opened, edited, synchronize]

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

permissions:
  contents: read
  pull-requests: write

jobs:
  semantic-pr:
    name: Validate PR
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Check PR Title
        id: lint-pr-title
        uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Add PR error comment
        uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3
        if: always() && (steps.lint-pr-title.outputs.error_message != null)
        with:
          header: pr-title-lint-error
          message: |
            We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.

            Details:

            ```
            ${{ steps.lint-pr-title.outputs.error_message }}
            ```

      - name: Delete PR error comment
        uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3
        if: ${{ steps.lint-pr-title.outputs.error_message == null }}
        with:
          header: pr-title-lint-error
          delete: true

================================================
FILE: .github/workflows/semantic-release.yml
================================================
name: Semantic Release

on:
  push:
    branches:
      - main

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    concurrency: release

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
          persist-credentials: true

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install python-semantic-release

      - name: Verify tests pass
        run: |
          pip install -r requirements.txt
          pip install -r requirements-dev.txt
          python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration"

      - name: Run semantic release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          semantic-release version
          semantic-release publish
          
      - name: Sync version to config.py
        run: |
          pip install toml
          python scripts/sync_version.py
          if git diff --quiet config.py; then
            echo "No version changes in config.py"
          else
            git add config.py
            git commit -m "chore: sync version to config.py [skip ci]"
            git push
          fi

      - name: Upload build artifacts to release
        if: hashFiles('dist/*') != ''
        run: |
          # Get the latest release tag
          LATEST_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName')
          if [ ! -z "$LATEST_TAG" ]; then
            echo "Uploading artifacts to release $LATEST_TAG"
            gh release upload "$LATEST_TAG" dist/* --clobber
          fi
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/test.yml
================================================
name: Tests

on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Run unit tests
        run: |
          # Run only unit tests (exclude simulation tests and integration tests)
          # Integration tests require local-llama which isn't available in CI
          python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration"
        env:
          # Ensure no API key is accidentally used in CI
          GEMINI_API_KEY: ""
          OPENAI_API_KEY: ""

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements-dev.txt

      - name: Run black formatter check
        run: black --check . --exclude="test_simulation_files/"

      - name: Run ruff linter
        run: ruff check . --exclude test_simulation_files


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
Pipfile.lock

# poetry
poetry.lock

# pdm
.pdm.toml
.pdm-python
pdm.lock

# PEP 582
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.env~
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
.idea/

# VS Code
.vscode/

# macOS
.DS_Store

# API Keys and secrets
*.key
*.pem
.env.local
.env.*.local

# Test outputs
test_output/
*.test.log
.coverage
htmlcov/
coverage.xml
.pytest_cache/

# Test simulation artifacts (dynamically created during testing)
test_simulation_files/.claude/

# Temporary test directories
test-setup/

# Scratch feature documentation files
FEATURE_*.md
# Temporary files
/tmp/

# Local user instructions
CLAUDE.local.md

# Claude Code personal settings
.claude/settings.local.json

# Standalone mode files
.pal_venv/
.docker_cleaned
logs/
*.backup
*.backup-*.json
/.desktop_configured

/worktrees/
test_simulation_files/
.mcp.json


================================================
FILE: .pre-commit-config.yaml
================================================
---
default_stages: [pre-commit, pre-push]
repos:
  - repo: https://github.com/psf/black
    rev: 25.1.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/isort
    rev: 6.0.1
    hooks:
      - id: isort
        args: ["--profile", "black"]

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.12.8
    hooks:
      - id: ruff
        args: [--fix]

# Configuration for specific tools
default_language_version:
  python: python3

# Exclude patterns
exclude: |
  (?x)^(
    \.git/|
    \.venv/|
    venv/|
    \.pal_venv/|
    __pycache__/|
    \.pytest_cache/|
    logs/|
    dist/|
    build/|
    test_simulation_files/
  )


================================================
FILE: AGENTS.md
================================================
# Repository Guidelines

See `requirements.txt` and `requirements-dev.txt`

Also read CLAUDE.md and CLAUDE.local.md if available.

## Project Structure & Module Organization
PAL MCP Server centers on `server.py`, which exposes MCP entrypoints and coordinates multi-model workflows. 
Feature-specific tools live in `tools/`, provider integrations in `providers/`, and shared helpers in `utils/`. 
Prompt and system context assets stay in `systemprompts/`, while configuration templates and automation scripts live under `conf/`, `scripts/`, and `docker/`. 
Unit tests sit in `tests/`; simulator-driven scenarios and log utilities are in `simulator_tests/` with the `communication_simulator_test.py` harness. 
Authoritative documentation and samples live in `docs/`, and runtime diagnostics are rotated in `logs/`.

## Build, Test, and Development Commands
- `source .pal_venv/bin/activate` – activate the managed Python environment.
- `./run-server.sh` – install dependencies, refresh `.env`, and launch the MCP server locally.
- `./code_quality_checks.sh` – run Ruff autofix, Black, isort, and the default pytest suite.
- `python communication_simulator_test.py --quick` – smoke-test orchestration across tools and providers.
- `./run_integration_tests.sh [--with-simulator]` – exercise provider-dependent flows against remote or Ollama models.

Run code quality checks:
```bash
.pal_venv/bin/activate && ./code_quality_checks.sh
```

For example, this is how we run an individual / all tests:

```bash
.pal_venv/bin/activate && pytest tests/test_auto_mode_model_listing.py -q
.pal_venv/bin/activate && pytest -q
```

## Coding Style & Naming Conventions
Target Python 3.9+ with Black and isort using a 120-character line limit; Ruff enforces pycodestyle, pyflakes, bugbear, comprehension, and pyupgrade rules. Prefer explicit type hints, snake_case modules, and imperative commit-time docstrings. Extend workflows by defining hook or abstract methods instead of checking `hasattr()`/`getattr()`—inheritance-backed contracts keep behavior discoverable and testable.

## Testing Guidelines
Mirror production modules inside `tests/` and name tests `test_<behavior>` or `Test<Feature>` classes. Run `python -m pytest tests/ -v -m "not integration"` before every commit, adding `--cov=. --cov-report=html` for coverage-sensitive changes. Use `python communication_simulator_test.py --verbose` or `--individual <case>` to validate cross-agent flows, and reserve `./run_integration_tests.sh` for provider or transport modifications. Capture relevant excerpts from `logs/mcp_server.log` or `logs/mcp_activity.log` when documenting failures.

## Commit & Pull Request Guidelines
Follow Conventional Commits: `type(scope): summary`, where `type` is one of `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, or `chore`. Keep commits focused, referencing issues or simulator cases when helpful. Pull requests should outline intent, list validation commands executed, flag configuration or tool toggles, and attach screenshots or log snippets when user-visible behavior changes.

## GitHub CLI Commands
The GitHub CLI (`gh`) streamlines issue and PR management directly from the terminal.

### Viewing Issues
```bash
# View issue details in current repository
gh issue view <issue-number>

# View issue from specific repository
gh issue view <issue-number> --repo owner/repo-name

# View issue with all comments
gh issue view <issue-number> --comments

# Get issue data as JSON for scripting
gh issue view <issue-number> --json title,body,author,state,labels,comments

# Open issue in web browser
gh issue view <issue-number> --web
```

### Managing Issues
```bash
# List all open issues
gh issue list

# List issues with filters
gh issue list --label bug --state open

# Create a new issue
gh issue create --title "Issue title" --body "Description"

# Close an issue
gh issue close <issue-number>

# Reopen an issue
gh issue reopen <issue-number>
```

### Pull Request Operations
```bash
# View PR details
gh pr view <pr-number>

# List pull requests
gh pr list

# Create a PR from current branch
gh pr create --title "PR title" --body "Description"

# Check out a PR locally
gh pr checkout <pr-number>

# Merge a PR
gh pr merge <pr-number>
```

Install GitHub CLI: `brew install gh` (macOS) or visit https://cli.github.com for other platforms.

## Security & Configuration Tips
Store API keys and provider URLs in `.env` or your MCP client config; never commit secrets or generated log artifacts. Use `run-server.sh` to regenerate environments and verify connectivity after dependency changes. When adding providers or tools, sanitize prompts and responses, document required environment variables in `docs/`, and update `claude_config_example.json` if new capabilities ship by default.


================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG

<!-- version list -->

## v9.8.2 (2025-12-15)

### Bug Fixes

- Allow home subdirectories through is_dangerous_path()
  ([`e5548ac`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e5548acb984ca4f8b2ae8381f879a0285094257f))

- Path traversal vulnerability - use prefix matching in is_dangerous_path()
  ([`9ed15f4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9ed15f405a9462b4db7aa44ca2d989e092c008e4))

- Use Path.is_relative_to() for cross-platform dangerous path detection
  ([`91ffb51`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/91ffb51564e5655ec91111938039ed81e0d8e4c6))

- **security**: Handle macOS symlinked system dirs
  ([`ba08308`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ba08308a23d1c1491099c5d0eae548077bd88f9f))

### Chores

- Sync version to config.py [skip ci]
  ([`c492735`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c4927358720277efa0373b339bd8e06ee06498d0))


## v9.8.1 (2025-12-15)

### Bug Fixes

- **providers**: Omit store parameter for OpenRouter responses endpoint
  ([`1f8b58d`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1f8b58d607c2809b9fa78860718a69207cb66e32))

### Chores

- Sync version to config.py [skip ci]
  ([`69a42a7`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/69a42a71d19d66f1d94d51fa27db29323e3d9a63))

### Refactoring

- **tests**: Address code review feedback
  ([`0c3e63c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/0c3e63c0c7f1556f4b6686f9c6f30e4bb4a48c7c))

- **tests**: Remove unused setUp method
  ([`b6a8d68`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b6a8d682d920c2283724b588818bc1162a865d74))


## v9.8.0 (2025-12-15)

### Chores

- Sync version to config.py [skip ci]
  ([`cb97a89`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/cb97a891dec6ab7c56b8b35c277ab3680af384d9))

### Features

- Add Claude Opus 4.5 model via OpenRouter
  ([`813ce5c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/813ce5c9f7db2910eb12d8c84d3d99f464c430ed))

### Testing

- Add comprehensive test coverage for Opus 4.5 aliases
  ([`cf63fd2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/cf63fd25440d599f2ec006bb8cfda5b8a6f61524))


## v9.7.0 (2025-12-15)

### Chores

- Sync version to config.py [skip ci]
  ([`aa85644`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/aa85644c9b15893443107c3a62ec58cd7b9dc532))

### Features

- Re-enable web search for clink codex using correct --enable flag
  ([`e7b9f3a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e7b9f3a5d7e06c690c82b9fd13a93310bcf388ed))


## v9.6.0 (2025-12-15)

### Chores

- Sync version to config.py [skip ci]
  ([`94ff26c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/94ff26c673a64087eb29f8f54c1828f1157c594a))

### Features

- Support native installed Claude CLI detection
  ([`adc6231`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/adc6231b98886f0bc35cb04d04d948eba2f0f058))


## v9.5.0 (2025-12-11)

### Bug Fixes

- Grok test
  ([`39c7721`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/39c77215e5d6892269e523ff25b706dd5671c042))

### Chores

- Sync version to config.py [skip ci]
  ([`5c3dd75`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/5c3dd75ca6b259f590bfd5078ea8e2f684e52de4))

- Sync version to config.py [skip ci]
  ([`605633b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/605633b2a2b044bbc5e41f2994dde27409a5b9b4))

### Documentation

- Cleanup
  ([`74f26e8`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/74f26e82e7a9c8a0214deef1cb18a3b2fa074050))

- Cleanup
  ([`2b22174`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2b221746fee6f7749d8aed8d07a85e428ac8e00f))

- Update subheading
  ([`591287c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/591287cb2f442a1fa34cd1139e3a0ad887388e5b))

### Features

- GPT-5.2 support
  ([`8b16405`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/8b16405f0609e232ff808361dc2a4d8ec258b0f3))

- Grok-4.1 support https://github.com/BeehiveInnovations/pal-mcp-server/issues/339
  ([`514c9c5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/514c9c58fcc91933348d2188ed8c82bbe98132f2))


## v9.4.2 (2025-12-04)

### Bug Fixes

- Rebranding, see [docs/name-change.md](docs/name-change.md) for details
  ([`b2dc849`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b2dc84992d70839b29b611178b3871f4922b747f))

### Chores

- Sync version to config.py [skip ci]
  ([`bcfacce`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bcfaccecd490859fe189f45df4cf5b8e102d7874))


## v9.4.1 (2025-11-21)

### Bug Fixes

- Regression https://github.com/BeehiveInnovations/pal-mcp-server/issues/338
  ([`aceddb6`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/aceddb655fc36918108b3da1f926bdd4e94875a2))

### Chores

- Sync version to config.py [skip ci]
  ([`c4461a4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c4461a466fab9c647b0a5035328c4d0f3e28f647))


## v9.4.0 (2025-11-18)

### Bug Fixes

- Failing test for gemini 3.0 pro open router
  ([`19a2a89`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/19a2a89b12c5dec53aea21a4244aff7796a5e049))

### Chores

- Sync version to config.py [skip ci]
  ([`d3de61f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d3de61f8787ab60261d09f2c7f362c50d2093799))

### Features

- Gemini 3.0 Pro Preview for Open Router
  ([`bbfdfac`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bbfdfac511668e8ae60f9b9b5d41eb9ab55d74cf))

### Refactoring

- Enable search on codex CLI
  ([`1579d9f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1579d9f806a653bb04c9c73ab304cdd0e78fbdfa))


## v9.3.1 (2025-11-18)

### Chores

- Sync version to config.py [skip ci]
  ([`d256098`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d2560983402abf084608f7750f05407a8d3e20a0))


## v9.3.0 (2025-11-18)

### Chores

- Sync version to config.py [skip ci]
  ([`3748d47`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3748d47faba7d871f2dd379f2c8646aa8cd3c6e9))


## v9.2.2 (2025-11-18)

### Bug Fixes

- **build**: Include clink resources in package
  ([`e9ac1ce`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e9ac1ce3354fbb124a72190702618f94266b8459))

### Chores

- Sync version to config.py [skip ci]
  ([`749bc73`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/749bc7307949fa0b0e026bfcfbd546d7619eba8b))


## v9.2.1 (2025-11-18)

### Bug Fixes

- **server**: Iterate provider instances during shutdown
  ([`d40fc83`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d40fc83d7549293372f3d20cc599a79ec355acef))

### Chores

- Sync version to config.py [skip ci]
  ([`84f6c4f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/84f6c4fb241257b611f4b954c22a6b9340007a73))


## v9.2.0 (2025-11-18)

### Chores

- Sync version to config.py [skip ci]
  ([`7a1de64`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7a1de6477aae88bfe7a2f677faf0794169651354))

### Documentation

- Streamline advanced usage guide by reorganizing table of contents for improved navigation
  ([`698d391`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/698d391b26a0dd565eada8bfa6e67e549ce1dd20))

- Update .env.example to include new GPT-5.1 model options and clarify existing model descriptions
  ([`dbbfef2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/dbbfef292c67ed54f90f7612c9c14d4095bd6c45))

- Update advanced usage and configuration to include new GPT-5.1 models and enhance tool parameters
  ([`807c9df`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/807c9df70e3b54031ec6beea10f3975455b36dfb))

### Features

- Add new GPT-5.1 models to configuration files and update model selection logic in OpenAI provider
  ([`8e9aa23`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/8e9aa2304d5e9ea9a9f8dc2a13a27a1ced6b1608))

- Enhance model support by adding GPT-5.1 to .gitignore and updating cassette maintenance
  documentation for dual-model testing
  ([`f713d8a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f713d8a354a37c32a806c98994e6f949ecd64237))


## v9.1.4 (2025-11-18)

### Bug Fixes

- Replaced deprecated Codex web search configuration
  ([`2ec64ba`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2ec64ba7489acc586846b25eedf94a4f05d5bd2d))

### Chores

- Sync version to config.py [skip ci]
  ([`4d3d177`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4d3d177d91370097ca7ac4f922fa3a8b69ce3250))


## v9.1.3 (2025-10-22)

### Bug Fixes

- Reduced token usage, removed parameters from schema that CLIs never seem to use
  ([`3e27319`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3e27319e60b0287df918856b58b2bbf042c948a8))

- Telemetry option no longer available in gemini 0.11
  ([`2a8dff0`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2a8dff0cc8a3f33111533cdb971d654637ed0578))

### Chores

- Sync version to config.py [skip ci]
  ([`9e163f9`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9e163f9dc0654fc28961c9897b7c787a2b96e57d))

- Sync version to config.py [skip ci]
  ([`557e443`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/557e443a63ffd733fb41faaa8696f6f4bb2c2fd1))

### Refactoring

- Improved precommit system prompt
  ([`3efff60`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3efff6056e322ee1531d7bed5601038c129a8b29))


## v9.1.2 (2025-10-21)

### Bug Fixes

- Configure codex with a longer timeout
  ([`d2773f4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d2773f488af28986632846652874de9ff633049c))

- Handle claude's array style JSON https://github.com/BeehiveInnovations/pal-mcp-server/issues/295
  ([`d5790a9`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d5790a9bfef719f03d17f2d719f1882e55d13b3b))

### Chores

- Sync version to config.py [skip ci]
  ([`04132f1`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/04132f1459f1e086afd8e3d456f671b63338f846))


## v9.1.1 (2025-10-17)

### Bug Fixes

- Failing test
  ([`aed3e3e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/aed3e3ee80c440ac8ab0d4abbf235b84df723d18))

- Handler for parsing multiple generated code blocks
  ([`f4c20d2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f4c20d2a20e1c57d8b10e8f508e07e2a8d72f94a))

- Improved error reporting; codex cli would at times fail to figure out how to handle plain-text /
  JSON errors
  ([`95e69a7`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/95e69a7cb234305dcd37dcdd2f22be715922e9a8))

### Chores

- Sync version to config.py [skip ci]
  ([`942757a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/942757a360a74c021b2a1aa63e394f18f5abcecd))


## v9.1.0 (2025-10-17)

### Chores

- Sync version to config.py [skip ci]
  ([`3ee0c8f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3ee0c8f555cb51b975700290919c2a8e2ada8cc4))

### Features

- Enhance review prompts to emphasize static analysis
  ([`36e66e2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/36e66e2e9a44a73a466545d4d3477ecb2cb3e669))


## v9.0.4 (2025-10-17)

### Chores

- Sync version to config.py [skip ci]
  ([`8c6f653`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/8c6f6532d843f7f1b283ce9b6472e5ba991efe16))


## v9.0.3 (2025-10-16)

### Bug Fixes

- Remove duplicate -o json flag in gemini CLI config
  ([`3b2eff5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3b2eff58ac0e2388045a7442c63f56ce259b54ba))

### Chores

- Sync version to config.py [skip ci]
  ([`b205d71`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b205d7159b674ce47ebc11af7255d1e3556fff93))


## v9.0.2 (2025-10-15)

### Bug Fixes

- Update Claude CLI commands to new mcp syntax
  ([`a2189cb`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/a2189cb88a295ebad6268b9b08c893cd65bc1d89))

### Chores

- Sync version to config.py [skip ci]
  ([`d08cdc6`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d08cdc6691e0f68917f2824945905b7256e0e568))


## v9.0.1 (2025-10-14)

### Bug Fixes

- Add JSON output flag to gemini CLI configuration
  ([`eb3dff8`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/eb3dff845828f60ff2659586883af622b8b035eb))

### Chores

- Sync version to config.py [skip ci]
  ([`b9408aa`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b9408aae8860d43b1da0ba67f9db98db7e4de2cf))


## v9.0.0 (2025-10-08)

### Chores

- Sync version to config.py [skip ci]
  ([`23c9b35`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/23c9b35d5226b07b59a4c4b3d7833ba81b019ea8))

### Features

- Claude Code as a CLI agent now supported. Mix and match: spawn claude code from within claude
  code, or claude code from within codex.
  ([`4cfaa0b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4cfaa0b6060769adfbd785a072526a5368421a73))


## v8.0.2 (2025-10-08)

### Bug Fixes

- Restore run-server quote trimming regex
  ([`1de4542`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1de454224c105891137134e2a25c2ee4f00dba45))

### Chores

- Sync version to config.py [skip ci]
  ([`728fb43`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/728fb439b929c9dc37646b24537ae043208fda7d))


## v8.0.1 (2025-10-08)

### Bug Fixes

- Resolve executable path for cross-platform compatibility in CLI agent
  ([`f98046c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f98046c2fccaa7f9a24665a0d705a98006461da5))

### Chores

- Sync version to config.py [skip ci]
  ([`52245b9`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/52245b91eaa5d720f8c3b21ead55248dd8e8bd57))

### Testing

- Fix clink agent tests to mock shutil.which() for executable resolution
  ([`4370be3`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4370be33b4b69a40456527213bcd62321a925a57))


## v8.0.0 (2025-10-07)

### Chores

- Sync version to config.py [skip ci]
  ([`4c34541`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4c3454121c3c678cdfe8ea03fa77f4dd414df9bc))


## v7.8.1 (2025-10-07)

### Bug Fixes

- Updated model description to fix test
  ([`04f7ce5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/04f7ce5b03804564263f53a765931edba9c320cd))

### Chores

- Sync version to config.py [skip ci]
  ([`c27e81d`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c27e81d6d2f22978816f798a161a869d1ab5f025))

### Refactoring

- Moved registries into a separate module and code cleanup
  ([`7c36b92`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7c36b9255a13007a10af4fadefc21aadfce482b0))


## v7.8.0 (2025-10-07)

### Chores

- Sync version to config.py [skip ci]
  ([`3e5fa96`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3e5fa96c981bbd7b844a9887a518ffe266b78e9b))

### Documentation

- Consensus video
  ([`2352684`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/23526841922a73c68094e5205e19af04a1f6c8cc))

- Formatting
  ([`7d7c74b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7d7c74b5a38b7d1adf132b8e28034017df7aa852))

- Link to videos from main page
  ([`e8ef193`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e8ef193daba393b55a3beaaba49721bb9182378a))

- Update README.md
  ([`7b13543`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7b13543824fc0af729daf753ecdddba9ee7d9f1e))

### Features

- All native providers now read from catalog files like OpenRouter / Custom configs. Allows for
  greater control over the capabilities
  ([`2a706d5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2a706d5720c0bf97b71c3e0fc95c15f78015bedf))

- Provider cleanup
  ([`9268dda`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9268ddad2a07306351765b47098134512739f49f))

### Refactoring

- New base class for model registry / loading
  ([`02d13da`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/02d13da897016d7491b4a10a1195983385d66654))


## v7.7.0 (2025-10-07)

### Chores

- Sync version to config.py [skip ci]
  ([`70ae62a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/70ae62a2cd663c3abcabddd1be1bc6ed9512d7df))

### Documentation

- Video
  ([`ed5dda7`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ed5dda7c5a9439c2835cc69d76e6377169ad048a))

### Features

- More aliases
  ([`5f0aaf5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/5f0aaf5f69c9d188d817b5ffbf6738c61da40ec7))


## v7.6.0 (2025-10-07)

### Chores

- Sync version to config.py [skip ci]
  ([`c1c75ba`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c1c75ba304c2840329650c46273e87eab9b05906))

- Sync version to config.py [skip ci]
  ([`0fa9b66`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/0fa9b6658099c8e0d79fda0c7d2347f62d0e6137))

### Documentation

- Info about AI client timeouts
  ([`3ddfed5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3ddfed5ef09000791e1c94b041c43dc273ed53a8))

### Features

- Add support for openai/gpt-5-pro model
  ([`abed075`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/abed075b2eaa99e9618202f47ff921094baae952))


## v7.5.2 (2025-10-06)

### Bug Fixes

- Handle 429 response https://github.com/BeehiveInnovations/pal-mcp-server/issues/273
  ([`cbe1d79`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/cbe1d7993276bd014b495cbd2d0ece1f5d7583d9))

### Chores

- Sync version to config.py [skip ci]
  ([`74fdd36`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/74fdd36de92d34681fcc5a2f772c3d05634f0a55))


## v7.5.1 (2025-10-06)

### Chores

- Sync version to config.py [skip ci]
  ([`004e379`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/004e379cf2f1853829dccb15fa72ec18d282f1a4))


## v7.5.0 (2025-10-06)

### Chores

- Sync version to config.py [skip ci]
  ([`71e7cd5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/71e7cd55b1f4955a6d718fddc0de419414d133b6))

### Documentation

- Video
  ([`775e4d5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/775e4d50b826858095c5f2a61a07fc01c4a00816))

- Videos
  ([`bb2066c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bb2066c909f6581ba40fc5ddef3870954ae553ab))

### Features

- Support for GPT-5-Pro highest reasoning model
  https://github.com/BeehiveInnovations/pal-mcp-server/issues/275
  ([`a65485a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/a65485a1e52fc79739000426295a27d096f4c9d8))


## v7.4.0 (2025-10-06)

### Chores

- Sync version to config.py [skip ci]
  ([`76bf98e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/76bf98e5cd972dabd3c79b25fcb9b9a717b23f6d))

### Features

- Improved prompt
  ([`b1e9963`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b1e9963991a41dff082ec1dce5691c318f105e6d))


## v7.3.0 (2025-10-06)

### Chores

- Sync version to config.py [skip ci]
  ([`e7920d0`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e7920d0ed16c0e6de9d1ccaa0b58d3fb5cbd7f2f))

### Documentation

- Fixed typo
  ([`3ab0aa8`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3ab0aa8314ad5992bcb00de549a0fab2e522751d))

- Fixed typo
  ([`c17ce3c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c17ce3cf958d488b97fa7127942542ab514b58bd))

- Update apilookup.md
  ([`1918679`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/19186794edac4fce5523e671310aecff4cbfdc81))

- Update README.md
  ([`23c6c78`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/23c6c78bf152ede6e7b5f7b7770b12a8442845a3))

### Features

- Codex supports web-search natively but needs to be turned on, run-server script asks if the user
  would like this done
  ([`97ba7e4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/97ba7e44ce7e3fd874759514ed2f0738033fc801))


## v7.2.0 (2025-10-06)

### Chores

- Sync version to config.py [skip ci]
  ([`1854b1e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1854b1e26b705cda0dc3f4d733647f1454aa0352))

### Documentation

- Updated
  ([`bb57f71`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bb57f719666ab6a586d835688ff8086282a5a0dc))

### Features

- New tool to perform apilookup (latest APIs / SDKs / language features etc)
  https://github.com/BeehiveInnovations/pal-mcp-server/issues/204
  ([`5bea595`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/5bea59540f58b3c45044828c10f131aed104dd1c))

### Refactoring

- De-duplicate roles to avoid explosion when more CLIs get added
  ([`c42e9e9`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c42e9e9c34d7ae4732e2e4fbed579b681a6d170d))


## v7.1.1 (2025-10-06)

### Bug Fixes

- Clink missing in toml
  ([`1ff77fa`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1ff77faa800ad6c2dde49cad98dfa72035fe1c81))

### Chores

- Sync version to config.py [skip ci]
  ([`e02e78d`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e02e78d903b35f4c01b8039f4157e97b38d3ec7b))

### Documentation

- Example for codex cli
  ([`344c42b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/344c42bcbfb543bfd05cbc27fd5b419c76b77954))

- Example for codex cli
  ([`c3044de`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c3044de7424e638dde5c8ec49adb6c3c7c5a60b2))

- Update README.md
  ([`2e719ae`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2e719ae35e7979f7b83bd910867e79863a7f9ceb))


## v7.1.0 (2025-10-05)

### Chores

- Sync version to config.py [skip ci]
  ([`d54bfdd`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d54bfdd49797d076ec9cade44c56292a8089c744))

### Features

- Support for codex as external CLI
  ([`561e4aa`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/561e4aaaa8a89eb89c03985b9e7720cc98ef666c))


## v7.0.2 (2025-10-05)

### Chores

- Sync version to config.py [skip ci]
  ([`f2142a2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f2142a22ec50abc54b464eedd6b8239d20c509be))


## v7.0.1 (2025-10-05)

### Bug Fixes

- --yolo needed for running shell commands, documentation added
  ([`15ae3f2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/15ae3f24babccf42f43be5028bf8c60c05a6beaf))

### Chores

- Sync version to config.py [skip ci]
  ([`bc4a27b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bc4a27b18a4a3f45afb22178e61ea0be4d6a273c))

### Documentation

- Updated intro
  ([`fb668c3`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/fb668c39b5f6e3dd37f7027f953f6004f258f2bf))


## v7.0.0 (2025-10-05)

### Chores

- Sync version to config.py [skip ci]
  ([`0d46976`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/0d46976a8aa85254e4dbe06f5e71161cd3b13938))

- Sync version to config.py [skip ci]
  ([`8296bf8`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/8296bf871c39597a904c70e7d98c72fcb4dc5a84))

### Documentation

- Instructions for OpenCode
  ([`bd66622`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bd666227c8f7557483f7e24fb8544fc0456600dc))

- Updated intro
  ([`615873c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/615873c3db2ecf5ce6475caa3445e1da9a2517bd))

### Features

- Huge update - Link another CLI (such as `gemini` directly from with Claude Code / Codex).
  https://github.com/BeehiveInnovations/pal-mcp-server/issues/208
  ([`a2ccb48`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/a2ccb48e9a5080a75dbfd483b5f09fc719c887e5))

### Refactoring

- Fixed test
  ([`9c99b9b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9c99b9b35219f54db8d7be0958d4390a106631ae))

- Include file modification dates too
  ([`47973e9`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/47973e945efa2cdbdb8f3404d467d7f1abc62b0a))


## v6.1.0 (2025-10-04)

### Chores

- Sync version to config.py [skip ci]
  ([`18095d7`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/18095d7d398e4bf3d24c57a52c81ac619acb1b89))

### Documentation

- Updated intro
  ([`aa65394`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/aa6539472c4ddf1c3c1bac446fdee03e75e1cb50))

### Features

- Support for Qwen Code
  ([`fe9968b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/fe9968b633d0312b82426e9ebddfe1d6515be3c5))


## v6.0.0 (2025-10-04)

### Chores

- Sync version to config.py [skip ci]
  ([`ae8749a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ae8749ab37bdaa7e225b5219820adeb74ca9a552))

### Documentation

- Updated
  ([`e91ed2a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e91ed2a924b1702edf9e1417479ac0dee0ca1553))

### Features

- Azure OpenAI / Azure AI Foundry support. Models should be defined in conf/azure_models.json (or a
  custom path). See .env.example for environment variables or see readme.
  https://github.com/BeehiveInnovations/pal-mcp-server/issues/265
  ([`ff9a07a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))

- Breaking change - OpenRouter models are now read from conf/openrouter_models.json while Custom /
  Self-hosted models are read from conf/custom_models.json
  ([`ff9a07a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))

- OpenAI/compatible models (such as Azure OpenAI) can declare if they use the response API instead
  via `use_openai_responses_api`
  ([`3824d13`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3824d131618683572e9e8fffa6b25ccfabf4cf50))

- OpenRouter / Custom Models / Azure can separately also use custom config paths now (see
  .env.example )
  ([`ff9a07a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))

### Refactoring

- Breaking change: `is_custom` property has been removed from model_capabilities.py (and thus
  custom_models.json) given each models are now read from separate configuration files
  ([`ff9a07a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))

- Model registry class made abstract, OpenRouter / Custom Provider / Azure OpenAI now subclass these
  ([`ff9a07a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ff9a07a37adf7a24aa87c63b3ba9db88bdff467b))


## v5.22.0 (2025-10-04)

### Bug Fixes

- CI test
  ([`bc93b53`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bc93b5343bbd8657b95ab47c00a2cb99a68a009f))

- Listmodels to always honor restricted models
  ([`4015e91`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4015e917ed32ae374ec6493b74993fcb34f4a971))

### Chores

- Sync version to config.py [skip ci]
  ([`054e34e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/054e34e31ca5bee5a11c0e3e6537f58e8897c79c))

- Sync version to config.py [skip ci]
  ([`c0334d7`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c0334d77922f1b05e3fd755851da112567fb9ae6))

### Features

- Centralized environment handling, ensures PAL_MCP_FORCE_ENV_OVERRIDE is honored correctly
  ([`2c534ac`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2c534ac06e4c6078b96781dfb55c5759b982afe8))

### Refactoring

- Don't retry on 429
  ([`d184024`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d18402482087f52b7bd07755c9304ed00ed20592))

- Improved retry logic and moved core logic to base class
  ([`f955100`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f955100f3a82973ccd987607e1d8a1bbe07828c8))

- Removed subclass override when the base class should be resolving the model name
  ([`06d7701`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/06d7701cc3ee09732ab713fa9c7c004199154483))


## v5.21.0 (2025-10-03)

### Chores

- Sync version to config.py [skip ci]
  ([`ddb20a6`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ddb20a6cdb8cdeee27c0aacb0b9c794283b5774c))


## v5.20.1 (2025-10-03)

### Chores

- Sync version to config.py [skip ci]
  ([`03addcf`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/03addcfa2d3aed5086fe4c94e8b9ae56229a93ae))


## v5.20.0 (2025-10-03)

### Chores

- Sync version to config.py [skip ci]
  ([`539bc72`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/539bc72f1ca2a2cadcccad02de1fd5fc22cd0415))


## v5.19.0 (2025-10-03)

### Bug Fixes

- Add GPT-5-Codex to Responses API routing and simplify comments
  ([`82b021d`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/82b021d75acc791e68c7afb35f6492f68cf02bec))

### Chores

- Sync version to config.py [skip ci]
  ([`8e32ef3`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/8e32ef33e3ce7ab2a9d7eb5c90fe5b93b12d5c26))

### Documentation

- Bumped defaults
  ([`95d98a9`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/95d98a9bc0a5bafadccb9f6d1e4eda97a0dd2ce7))

### Features

- Add GPT-5-Codex support with Responses API integration
  ([`f265342`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f2653427ca829368e7145325d20a98df3ee6d6b4))

### Testing

- Cross tool memory recall, testing continuation via cassette recording
  ([`88493bd`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/88493bd357c6a12477c3160813100dae1bc46493))


## v5.18.3 (2025-10-03)

### Bug Fixes

- External model name now recorded properly in responses
  ([`d55130a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d55130a430401e106cd86f3e830b3d756472b7ff))

### Chores

- Sync version to config.py [skip ci]
  ([`5714e20`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/5714e2016405f7607b44d78f85081c7ccee706e5))

### Documentation

- Updated docs
  ([`b4e5090`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b4e50901ba60c88137a29d00ecf99718582856d3))

### Refactoring

- Generic name for the CLI agent
  ([`e9b6947`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e9b69476cd922c12931d62ccc3be9082bbbf6014))

- Generic name for the CLI agent
  ([`7a6fa0e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7a6fa0e77a8c4a682dc11c9bbb16bdaf86d9edf4))

- Generic name for the CLI agent
  ([`b692da2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b692da2a82facce7455b8f2ec0108e1db84c07c3))

- Generic name for the CLI agent
  ([`f76ebbf`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f76ebbf280cc78ffcfe17cb4590aeaa231db8aa1))

- Generic name for the CLI agent
  ([`c05913a`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c05913a09e53e195b9a108647c09c061ced19d17))

- Generic name for the CLI agent
  ([`0dfaa63`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/0dfaa6312ed95ac3d1ae0032334ae1286871b15e))

### Testing

- Fixed integration tests, removed magicmock
  ([`87ccb6b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/87ccb6b25ba32a3cb9c4cc64fc0e96294f492c04))


## v5.18.2 (2025-10-02)

### Bug Fixes

- Https://github.com/BeehiveInnovations/pal-mcp-server/issues/194
  ([`8b3a286`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/8b3a2867fb83eccb3a8e8467e7e3fc5b8ebe1d0c))

### Chores

- Sync version to config.py [skip ci]
  ([`bf2196c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bf2196cdd58ae8d8d93597f2be69c798265d678f))


## v5.18.1 (2025-10-02)

### Chores

- Sync version to config.py [skip ci]
  ([`e434a26`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e434a2614af82efd15de4dd94b2c30559c91414e))


## v5.18.0 (2025-10-02)

### Chores

- Sync version to config.py [skip ci]
  ([`e78fe35`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e78fe35a1b64cc0ed89664440ef7c7b94495d7dc))

### Features

- Added `intelligence_score` to the model capabilities schema; a 1-20 number that can be specified
  to influence the sort order of models presented to the CLI in `auto selection` mode
  ([`6cab9e5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/6cab9e56fc5373da5c11d4545bcb85371d4803a4))


## v5.17.4 (2025-10-02)

### Chores

- Sync version to config.py [skip ci]
  ([`a6c9b92`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/a6c9b9212c77852d9e9a8780f4bc3e53b3bfed2f))


## v5.17.3 (2025-10-02)

### Chores

- Sync version to config.py [skip ci]
  ([`722f6f8`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/722f6f86ae228206ce0094d109a3b20499d4e11a))


## v5.17.2 (2025-10-02)

### Chores

- Sync version to config.py [skip ci]
  ([`e47a7e8`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e47a7e89d5bfad0bb0150cb3207f1a37dc91b170))


## v5.17.1 (2025-10-02)

### Bug Fixes

- Baseclass should return MODEL_CAPABILITIES
  ([`82a03ce`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/82a03ce63f28fece17bfc1d70bdb75aadec4c6bb))

### Chores

- Sync version to config.py [skip ci]
  ([`7ce66bd`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7ce66bd9508865cef64dc30936e86e37c1a306d0))

### Documentation

- Document custom timeout values
  ([`218fbdf`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/218fbdf49cb90f2353f58bbaef567519dd876634))

### Refactoring

- Clean temperature inference
  ([`9c11ecc`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9c11ecc4bf37562aa08dc3ecfa70f380e0ead357))

- Cleanup
  ([`6ec2033`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/6ec2033f34c74ad139036de83a34cf6d374db77b))

- Cleanup provider base class; cleanup shared responsibilities; cleanup public contract
  ([`693b84d`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/693b84db2b87271ac809abcf02100eee7405720b))

- Cleanup token counting
  ([`7fe9fc4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7fe9fc49f8e3cd92be4c45a6645d5d4ab3014091))

- Code cleanup
  ([`bb138e2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/bb138e2fb552f837b0f9f466027580e1feb26f7c))

- Code cleanup
  ([`182aa62`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/182aa627dfba6c578089f83444882cdd2635a7e3))

- Moved image related code out of base provider into a separate utility
  ([`14a35af`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/14a35afa1d25408e62b968d9846be7bffaede327))

- Moved temperature method from base provider to model capabilities
  ([`6d237d0`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/6d237d09709f757a042baf655f47eb4ddfc078ad))

- Moved temperature method from base provider to model capabilities
  ([`f461cb4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f461cb451953f882bbde096a9ecf0584deb1dde8))

- Removed hard coded checks, use model capabilities instead
  ([`250545e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/250545e34f8d4f8026bfebb3171f3c2bc40f4692))

- Removed hook from base class, turned into helper static method instead
  ([`2b10adc`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2b10adcaf2b8741f0da5de84cc3483eae742a014))

- Removed method from provider, should use model capabilities instead
  ([`a254ff2`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/a254ff2220ba00ec30f5110c69a4841419917382))

- Renaming to reflect underlying type
  ([`1dc25f6`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1dc25f6c3d4cdbf01f041cc424e3b5235c23175b))


## v5.17.0 (2025-10-02)

### Bug Fixes

- Use types.HttpOptions from module imports instead of local import
  ([`956e8a6`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/956e8a6927837f5c7f031a0db1dd0b0b5483c626))

### Chores

- Sync version to config.py [skip ci]
  ([`0836213`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/0836213071d0037d8a6d2e64d34ab5df79b8e684))

### Code Style

- Apply Black formatting to use double quotes
  ([`33ea896`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/33ea896c511764904bf2b6b22df823928f88a148))

### Features

- Add custom Gemini endpoint support
  ([`462bce0`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/462bce002e2141b342260969588e69f55f8bb46a))

### Refactoring

- Simplify Gemini provider initialization using kwargs dict
  ([`023940b`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/023940be3e38a7eedbc8bf8404a4a5afc50f8398))


## v5.16.0 (2025-10-01)

### Bug Fixes

- Resolve logging timing and import organization issues
  ([`d34c299`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d34c299f02a233af4f17bdcc848219bf07799723))

### Chores

- Sync version to config.py [skip ci]
  ([`b6c4bca`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b6c4bca158e4cee1ae4abd08b7e55216ebffba2d))

### Code Style

- Fix ruff import sorting issue
  ([`4493a69`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4493a693332e0532d04ad3634de2a2f5b1249b64))

### Features

- Add configurable environment variable override system
  ([`93ce698`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/93ce6987b6e7d8678ffa5ac51f5106a7a21ce67b))


## v5.15.0 (2025-10-01)

### Chores

- Sync version to config.py [skip ci]
  ([`b0fe956`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b0fe956f8a50240507e0fc911f0800634c15e9f7))

### Features

- Depending on the number of tools in use, this change should save ~50% of overall tokens used.
  fixes https://github.com/BeehiveInnovations/pal-mcp-server/issues/255 but also refactored
  individual tools to instead encourage the agent to use the listmodels tool if needed.
  ([`d9449c7`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d9449c7bb607caff3f0454f210ddfc36256c738a))

### Performance Improvements

- Tweaks to schema descriptions, aiming to reduce token usage without performance degradation
  ([`cc8a4df`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/cc8a4dfd21b6f3dae4972a833b619e53c964693b))

### Refactoring

- Trimmed some prompts
  ([`f69ff03`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/f69ff03c4d10e606a1dfed2a167f3ba2e2236ba8))


## v5.14.1 (2025-10-01)

### Bug Fixes

- Https://github.com/BeehiveInnovations/pal-mcp-server/issues/258
  ([`696b45f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/696b45f25e80faccb67034254cf9a8fc4c643dbd))

### Chores

- Sync version to config.py [skip ci]
  ([`692016c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/692016c6205ed0a0c3d9e830482d88231aca2e31))


## v5.14.0 (2025-10-01)

### Chores

- Sync version to config.py [skip ci]
  ([`c0f822f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c0f822ffa23292d668f7b5dd3cb62e3f23fb29af))

### Features

- Add Claude Sonnet 4.5 and update alias configuration
  ([`95c4822`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/95c4822af2dc55f59c0e4ed9454673d6ca964731))

### Testing

- Update tests to match new Claude Sonnet 4.5 alias configuration
  ([`7efb409`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7efb4094d4eb7db006340d3d9240b9113ac25cd3))


## v5.13.0 (2025-10-01)

### Bug Fixes

- Add sonnet alias for Claude Sonnet 4.1 to match opus/haiku pattern
  ([`dc96344`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/dc96344db043e087ee4f8bf264a79c51dc2e0b7a))

- Missing "optenai/" in name
  ([`7371ed6`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/7371ed6487b7d90a1b225a67dca2a38c1a52f2ad))

### Chores

- Sync version to config.py [skip ci]
  ([`b8479fc`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/b8479fc638083d6caa4bad6205e3d3fcab830aca))

### Features

- Add comprehensive GPT-5 series model support
  ([`4930824`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/493082405237e66a2f033481a5f8bf8293b0d553))


## v5.12.1 (2025-10-01)

### Bug Fixes

- Resolve consensus tool model_context parameter missing issue
  ([`9044b63`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9044b63809113047fe678d659e4fcd175f58e87a))

### Chores

- Sync version to config.py [skip ci]
  ([`e3ebf4e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/e3ebf4e94eba63acdc4df5a0b0493e44e3343dd1))

### Code Style

- Fix trailing whitespace in consensus.py
  ([`0760b31`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/0760b31f8a6d03c4bea3fd2a94dfbbfab0ad5079))

### Refactoring

- Optimize ModelContext creation in consensus tool
  ([`30a8952`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/30a8952fbccd22bebebd14eb2c8005404b79bcd6))


## v5.12.0 (2025-10-01)

### Bug Fixes

- Removed use_websearch; this parameter was confusing Codex. It started using this to prompt the
  external model to perform searches! web-search is enabled by Claude / Codex etc by default and the
  external agent can ask claude to search on its behalf.
  ([`cff6d89`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/cff6d8998f64b73265c4e31b2352462d6afe377f))

### Chores

- Sync version to config.py [skip ci]
  ([`28cabe0`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/28cabe0833661b0bab56d4227781ee2da332b00c))

### Features

- Implement semantic cassette matching for o3 models
  ([`70fa088`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/70fa088c32ac4e6153d5e7b30a3e32022be2f908))


## v5.11.2 (2025-10-01)

### Chores

- Sync version to config.py [skip ci]
  ([`4d6f1b4`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4d6f1b41005dee428c955e33f04f8f9f6259e662))


## v5.11.1 (2025-10-01)

### Bug Fixes

- Remove duplicate OpenAI models from listmodels output
  ([`c29e762`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/c29e7623ace257eb45396cdf8c19e1659e29edb9))

### Chores

- Sync version to config.py [skip ci]
  ([`1209064`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/12090646ee83f2368311d595d87ae947e46ddacd))

### Testing

- Update OpenAI provider alias tests to match new format
  ([`d13700c`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d13700c14c7ee3d092302837cb1726d17bab1ab8))


## v5.11.0 (2025-08-26)

### Chores

- Sync version to config.py [skip ci]
  ([`9735469`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/973546990f2c45afa93f1aa6de33ff461ecf1a83))

### Features

- Codex CLI support
  ([`ce56d16`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/ce56d16240ddcc476145a512561efe5c66438f0d))


## v5.10.3 (2025-08-24)

### Bug Fixes

- Address test failures and PR feedback
  ([`6bd9d67`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/6bd9d6709acfb584ab30a0a4d6891cabdb6d3ccf))

- Resolve temperature handling issues for O3/custom models
  ([#245](https://github.com/BeehiveInnovations/pal-mcp-server/pull/245),
  [`3b4fd88`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/3b4fd88d7e9a3f09fea616a10cb3e9d6c1a0d63b))

### Chores

- Sync version to config.py [skip ci]
  ([`d6e6808`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d6e6808be525192ab8388c0f01bc1bbd016fc23a))


## v5.10.2 (2025-08-24)

### Bug Fixes

- Another fix for https://github.com/BeehiveInnovations/pal-mcp-server/issues/251
  ([`a07036e`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/a07036e6805042895109c00f921c58a09caaa319))

### Chores

- Sync version to config.py [skip ci]
  ([`9da5c37`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/9da5c37809cbde19d0c7ffed273ae93ca883a016))


## v5.10.0 (2025-08-22)

### Chores

- Sync version to config.py [skip ci]
  ([`1254205`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/12542054a214022d3f515e53367f5bf3a77fb289))

### Features

- Refactored and tweaked model descriptions / schema to use fewer tokens at launch (average
  reduction per field description: 60-80%) without sacrificing tool effectiveness
  ([`4b202f5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4b202f5d1d24cea1394adab26a976188f847bd09))


## v5.9.0 (2025-08-21)

### Documentation

- Update instructions for precommit
  ([`90821b5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/90821b51ff653475d9fb1bc70b57951d963e8841))

### Features

- Refactored and improved codereview in line with precommit. Reviews are now either external
  (default) or internal. Takes away anxiety and loss of tokens when Claude incorrectly decides to be
  'confident' about its own changes and bungle things up.
  ([`80d21e5`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/80d21e57c0246762c0a306ede5b93d6aeb2315d8))

### Refactoring

- Minor prompt tweaks
  ([`d30c212`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/d30c212029c05b767d99b5391c1dd4cee78ef336))


## v5.8.6 (2025-08-20)

### Bug Fixes

- Escape backslashes in TOML regex pattern
  ([`1c973af`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/1c973afb002650b9bbee8a831b756bef848915a1))

- Establish version 5.8.6 and add version sync automation
  ([`90a4195`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/90a419538128b54fbd30da4b8a8088ac59f8c691))

- Restore proper version 5.8.6
  ([`340b58f`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/340b58f2e790b84c3736aa96df7f6f5f2d6a13c9))

### Chores

- Sync version to config.py [skip ci]
  ([`4f82f65`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/4f82f6500502b7b6ba41875a560c41f6a63b683b))


## v1.1.0 (2025-08-20)

### Features

- Improvements to precommit
  ([`2966dcf`](https://github.com/BeehiveInnovations/pal-mcp-server/commit/2966dcf2682feb7eef4073738d0c225a44ce0533))


## v1.0.0 (2025-08-20)

- Initial Release


================================================
FILE: CLAUDE.md
================================================
# Claude Development Guide for PAL MCP Server

This file contains essential commands and workflows for developing and maintaining the PAL MCP Server when working with Claude. Use these instructions to efficiently run quality checks, manage the server, check logs, and run tests.

## Quick Reference Commands

### Code Quality Checks

Before making any changes or submitting PRs, always run the comprehensive quality checks:

```bash
# Activate virtual environment first
source venv/bin/activate

# Run all quality checks (linting, formatting, tests)
./code_quality_checks.sh
```

This script automatically runs:
- Ruff linting with auto-fix
- Black code formatting 
- Import sorting with isort
- Complete unit test suite (excluding integration tests)
- Verification that all checks pass 100%

**Run Integration Tests (requires API keys):**
```bash
# Run integration tests that make real API calls
./run_integration_tests.sh

# Run integration tests + simulator tests
./run_integration_tests.sh --with-simulator
```

### Server Management

#### Setup/Update the Server
```bash
# Run setup script (handles everything)
./run-server.sh
```

This script will:
- Set up Python virtual environment
- Install all dependencies
- Create/update .env file
- Configure MCP with Claude
- Verify API keys

#### View Logs
```bash
# Follow logs in real-time
./run-server.sh -f

# Or manually view logs
tail -f logs/mcp_server.log
```

### Log Management

#### View Server Logs
```bash
# View last 500 lines of server logs
tail -n 500 logs/mcp_server.log

# Follow logs in real-time
tail -f logs/mcp_server.log

# View specific number of lines
tail -n 100 logs/mcp_server.log

# Search logs for specific patterns
grep "ERROR" logs/mcp_server.log
grep "tool_name" logs/mcp_activity.log
```

#### Monitor Tool Executions Only
```bash
# View tool activity log (focused on tool calls and completions)
tail -n 100 logs/mcp_activity.log

# Follow tool activity in real-time
tail -f logs/mcp_activity.log

# Use simple tail commands to monitor logs
tail -f logs/mcp_activity.log | grep -E "(TOOL_CALL|TOOL_COMPLETED|ERROR|WARNING)"
```

#### Available Log Files

**Current log files (with proper rotation):**
```bash
# Main server log (all activity including debug info) - 20MB max, 10 backups
tail -f logs/mcp_server.log

# Tool activity only (TOOL_CALL, TOOL_COMPLETED, etc.) - 20MB max, 5 backups  
tail -f logs/mcp_activity.log
```

**For programmatic log analysis (used by tests):**
```python
# Import the LogUtils class from simulator tests
from simulator_tests.log_utils import LogUtils

# Get recent logs
recent_logs = LogUtils.get_recent_server_logs(lines=500)

# Check for errors
errors = LogUtils.check_server_logs_for_errors()

# Search for specific patterns
matches = LogUtils.search_logs_for_pattern("TOOL_CALL.*debug")
```

### Testing

Simulation tests are available to test the MCP server in a 'live' scenario, using your configured
API keys to ensure the models are working and the server is able to communicate back and forth. 

**IMPORTANT**: After any code changes, restart your Claude session for the changes to take effect.

#### Run All Simulator Tests
```bash
# Run the complete test suite
python communication_simulator_test.py

# Run tests with verbose output
python communication_simulator_test.py --verbose
```

#### Quick Test Mode (Recommended for Time-Limited Testing)
```bash
# Run quick test mode - 6 essential tests that provide maximum functionality coverage
python communication_simulator_test.py --quick

# Run quick test mode with verbose output
python communication_simulator_test.py --quick --verbose
```

**Quick mode runs these 6 essential tests:**
- `cross_tool_continuation` - Cross-tool conversation memory testing (chat, thinkdeep, codereview, analyze, debug)
- `conversation_chain_validation` - Core conversation threading and memory validation
- `consensus_workflow_accurate` - Consensus tool with flash model and stance testing
- `codereview_validation` - CodeReview tool with flash model and multi-step workflows
- `planner_validation` - Planner tool with flash model and complex planning workflows
- `token_allocation_validation` - Token allocation and conversation history buildup testing

**Why these 6 tests:** They cover the core functionality including conversation memory (`utils/conversation_memory.py`), chat tool functionality, file processing and deduplication, model selection (flash/flashlite/o3), and cross-tool conversation workflows. These tests validate the most critical parts of the system in minimal time.

**Note:** Some workflow tools (analyze, codereview, planner, consensus, etc.) require specific workflow parameters and may need individual testing rather than quick mode testing.

#### Run Individual Simulator Tests (For Detailed Testing)
```bash
# List all available tests
python communication_simulator_test.py --list-tests

# RECOMMENDED: Run tests individually for better isolation and debugging
python communication_simulator_test.py --individual basic_conversation
python communication_simulator_test.py --individual content_validation
python communication_simulator_test.py --individual cross_tool_continuation
python communication_simulator_test.py --individual memory_validation

# Run multiple specific tests
python communication_simulator_test.py --tests basic_conversation content_validation

# Run individual test with verbose output for debugging
python communication_simulator_test.py --individual memory_validation --verbose
```

Available simulator tests include:
- `basic_conversation` - Basic conversation flow with chat tool
- `content_validation` - Content validation and duplicate detection
- `per_tool_deduplication` - File deduplication for individual tools
- `cross_tool_continuation` - Cross-tool conversation continuation scenarios
- `cross_tool_comprehensive` - Comprehensive cross-tool file deduplication and continuation
- `line_number_validation` - Line number handling validation across tools
- `memory_validation` - Conversation memory validation
- `model_thinking_config` - Model-specific thinking configuration behavior
- `o3_model_selection` - O3 model selection and usage validation
- `ollama_custom_url` - Ollama custom URL endpoint functionality
- `openrouter_fallback` - OpenRouter fallback behavior when only provider
- `openrouter_models` - OpenRouter model functionality and alias mapping
- `token_allocation_validation` - Token allocation and conversation history validation
- `testgen_validation` - TestGen tool validation with specific test function
- `refactor_validation` - Refactor tool validation with codesmells
- `conversation_chain_validation` - Conversation chain and threading validation
- `consensus_stance` - Consensus tool validation with stance steering (for/against/neutral)

**Note**: All simulator tests should be run individually for optimal testing and better error isolation.

#### Run Unit Tests Only
```bash
# Run all unit tests (excluding integration tests that require API keys)
python -m pytest tests/ -v -m "not integration"

# Run specific test file
python -m pytest tests/test_refactor.py -v

# Run specific test function
python -m pytest tests/test_refactor.py::TestRefactorTool::test_format_response -v

# Run tests with coverage
python -m pytest tests/ --cov=. --cov-report=html -m "not integration"
```

#### Run Integration Tests (Uses Free Local Models)

**Setup Requirements:**
```bash
# 1. Install Ollama (if not already installed)
# Visit https://ollama.ai or use brew install ollama

# 2. Start Ollama service
ollama serve

# 3. Pull a model (e.g., llama3.2)
ollama pull llama3.2

# 4. Set environment variable for custom provider
export CUSTOM_API_URL="http://localhost:11434"
```

**Run Integration Tests:**
```bash
# Run integration tests that make real API calls to local models
python -m pytest tests/ -v -m "integration"

# Run specific integration test
python -m pytest tests/test_prompt_regression.py::TestPromptIntegration::test_chat_normal_prompt -v

# Run all tests (unit + integration)
python -m pytest tests/ -v
```

**Note**: Integration tests use the local-llama model via Ollama, which is completely FREE to run unlimited times. Requires `CUSTOM_API_URL` environment variable set to your local Ollama endpoint. They can be run safely in CI/CD but are excluded from code quality checks to keep them fast.

### Development Workflow

#### Before Making Changes
1. Ensure virtual environment is activated: `source .pal_venv/bin/activate`
2. Run quality checks: `./code_quality_checks.sh`
3. Check logs to ensure server is healthy: `tail -n 50 logs/mcp_server.log`

#### After Making Changes
1. Run quality checks again: `./code_quality_checks.sh`
2. Run integration tests locally: `./run_integration_tests.sh`
3. Run quick test mode for fast validation: `python communication_simulator_test.py --quick`
4. Run relevant specific simulator tests if needed: `python communication_simulator_test.py --individual <test_name>`
5. Check logs for any issues: `tail -n 100 logs/mcp_server.log`
6. Restart Claude session to use updated code

#### Before Committing/PR
1. Final quality check: `./code_quality_checks.sh`
2. Run integration tests: `./run_integration_tests.sh`
3. Run quick test mode: `python communication_simulator_test.py --quick`
4. Run full simulator test suite (optional): `./run_integration_tests.sh --with-simulator`
5. Verify all tests pass 100%

### Common Troubleshooting

#### Server Issues
```bash
# Check if Python environment is set up correctly
./run-server.sh

# View recent errors
grep "ERROR" logs/mcp_server.log | tail -20

# Check virtual environment
which python
# Should show: .../pal-mcp-server/.pal_venv/bin/python
```

#### Test Failures
```bash
# First try quick test mode to see if it's a general issue
python communication_simulator_test.py --quick --verbose

# Run individual failing test with verbose output
python communication_simulator_test.py --individual <test_name> --verbose

# Check server logs during test execution
tail -f logs/mcp_server.log

# Run tests with debug output
LOG_LEVEL=DEBUG python communication_simulator_test.py --individual <test_name>
```

#### Linting Issues
```bash
# Auto-fix most linting issues
ruff check . --fix
black .
isort .

# Check what would be changed without applying
ruff check .
black --check .
isort --check-only .
```

### File Structure Context

- `./code_quality_checks.sh` - Comprehensive quality check script
- `./run-server.sh` - Server setup and management
- `communication_simulator_test.py` - End-to-end testing framework
- `simulator_tests/` - Individual test modules
- `tests/` - Unit test suite
- `tools/` - MCP tool implementations
- `providers/` - AI provider implementations
- `systemprompts/` - System prompt definitions
- `logs/` - Server log files

### Environment Requirements

- Python 3.9+ with virtual environment
- All dependencies from `requirements.txt` installed
- Proper API keys configured in `.env` file

This guide provides everything needed to efficiently work with the PAL MCP Server codebase using Claude. Always run quality checks before and after making changes to ensure code integrity.

================================================
FILE: Dockerfile
================================================
# ===========================================
# STAGE 1: Build dependencies
# ===========================================
FROM python:3.11-slim AS builder

# Install system dependencies for building
RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Copy requirements files
COPY requirements.txt ./

# Create virtual environment and install dependencies
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Install Python dependencies
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
    pip install --no-cache-dir -r requirements.txt

# ===========================================
# STAGE 2: Runtime image
# ===========================================
FROM python:3.11-slim AS runtime

# Add metadata labels for traceability
LABEL maintainer="PAL MCP Server Team"
LABEL version="1.0.0"
LABEL description="PAL MCP Server - AI-powered Model Context Protocol server"
LABEL org.opencontainers.image.title="pal-mcp-server"
LABEL org.opencontainers.image.description="AI-powered Model Context Protocol server with multi-provider support"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.source="https://github.com/BeehiveInnovations/pal-mcp-server"
LABEL org.opencontainers.image.documentation="https://github.com/BeehiveInnovations/pal-mcp-server/blob/main/README.md"
LABEL org.opencontainers.image.licenses="Apache 2.0 License"

# Create non-root user for security
RUN groupadd -r paluser && useradd -r -g paluser paluser

# Install minimal runtime dependencies
RUN apt-get update && apt-get install -y \
    ca-certificates \
    procps \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Set working directory
WORKDIR /app

# Copy application code
COPY --chown=paluser:paluser . .

# Create logs directory with proper permissions
RUN mkdir -p logs && chown -R paluser:paluser logs

# Create tmp directory for container operations
RUN mkdir -p tmp && chown -R paluser:paluser tmp

# Copy health check script
COPY --chown=paluser:paluser docker/scripts/healthcheck.py /usr/local/bin/healthcheck.py
RUN chmod +x /usr/local/bin/healthcheck.py

# Switch to non-root user
USER paluser

# Health check configuration
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python /usr/local/bin/healthcheck.py

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app

# Default command
CMD ["python", "server.py"]


================================================
FILE: LICENSE
================================================
Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship covered by this License,
      whether in source or binary form, which is made available under the
      License, as indicated by a copyright notice that is included in or
      attached to the work. (The copyright notice requirement does not
      apply to derivative works of the License holder.)

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based upon (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and derivative works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control
      systems, and issue tracking systems that are managed by, or on behalf
      of, the Licensor for the purpose of discussing and improving the Work,
      but excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to use, reproduce, modify, distribute, and otherwise
      transfer the Work as part of a Derivative Work.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright notice to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Support. You may choose to offer, and to
      charge a fee for, warranty, support, indemnity or other liability
      obligations and/or rights consistent with this License. However,
      in accepting such obligations, You may act only on Your own behalf
      and on Your sole responsibility, not on behalf of any other
      Contributor, and only if You agree to indemnify, defend, and hold
      each Contributor harmless for any liability incurred by, or claims
      asserted against, such Contributor by reason of your accepting any
      such warranty or support.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in comments for the
      particular file format. An identification line is also useful.

      Copyright 2025 Beehive Innovations
      https://github.com/BeehiveInnovations

      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at

           http://www.apache.org/licenses/LICENSE-2.0

      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License.

================================================
FILE: README.md
================================================
# PAL MCP: Many Workflows. One Context.

<div align="center">

  <em>Your AI's PAL – a Provider Abstraction Layer</em><br />
  <sub><a href="docs/name-change.md">Formerly known as Zen MCP</a></sub>

  [PAL in action](https://github.com/user-attachments/assets/0d26061e-5f21-4ab1-b7d0-f883ddc2c3da)

👉 **[Watch more examples](#-watch-tools-in-action)**

### Your CLI + Multiple Models = Your AI Dev Team

**Use the 🤖 CLI you love:**  
[Claude Code](https://www.anthropic.com/claude-code) · [Gemini CLI](https://github.com/google-gemini/gemini-cli) · [Codex CLI](https://github.com/openai/codex) · [Qwen Code CLI](https://qwenlm.github.io/qwen-code-docs/) · [Cursor](https://cursor.com) · _and more_

**With multiple models within a single prompt:**  
Gemini · OpenAI · Anthropic · Grok · Azure · Ollama · OpenRouter · DIAL · On-Device Model

</div>

---

## 🆕 Now with CLI-to-CLI Bridge

The new **[`clink`](docs/tools/clink.md)** (CLI + Link) tool connects external AI CLIs directly into your workflow:

- **Connect external CLIs** like [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Codex CLI](https://github.com/openai/codex), and [Claude Code](https://www.anthropic.com/claude-code) directly into your workflow
- **CLI Subagents** - Launch isolated CLI instances from _within_ your current CLI! Claude Code can spawn Codex subagents, Codex can spawn Gemini CLI subagents, etc. Offload heavy tasks (code reviews, bug hunting) to fresh contexts while your main session's context window remains unpolluted. Each subagent returns only final results.
- **Context Isolation** - Run separate investigations without polluting your primary workspace
- **Role Specialization** - Spawn `planner`, `codereviewer`, or custom role agents with specialized system prompts
- **Full CLI Capabilities** - Web search, file inspection, MCP tool access, latest documentation lookups
- **Seamless Continuity** - Sub-CLIs participate as first-class members with full conversation context between tools

```bash
# Codex spawns Codex subagent for isolated code review in fresh context
clink with codex codereviewer to audit auth module for security issues
# Subagent reviews in isolation, returns final report without cluttering your context as codex reads each file and walks the directory structure

# Consensus from different AI models → Implementation handoff with full context preservation between tools
Use consensus with gpt-5 and gemini-pro to decide: dark mode or offline support next
Continue with clink gemini - implement the recommended feature
# Gemini receives full debate context and starts coding immediately
```

👉 **[Learn more about clink](docs/tools/clink.md)**

---

## Why PAL MCP?

**Why rely on one AI model when you can orchestrate them all?**

A Model Context Protocol server that supercharges tools like [Claude Code](https://www.anthropic.com/claude-code), [Codex CLI](https://developers.openai.com/codex/cli), and IDE clients such
as [Cursor](https://cursor.com) or the [Claude Dev VS Code extension](https://marketplace.visualstudio.com/items?itemName=Anthropic.claude-vscode). **PAL MCP connects your favorite AI tool
to multiple AI models** for enhanced code analysis, problem-solving, and collaborative development.

### True AI Collaboration with Conversation Continuity

PAL supports **conversation threading** so your CLI can **discuss ideas with multiple AI models, exchange reasoning, get second opinions, and even run collaborative debates between models** to help you reach deeper insights and better solutions.

Your CLI always stays in control but gets perspectives from the best AI for each subtask. Context carries forward seamlessly across tools and models, enabling complex workflows like: code reviews with multiple models → automated planning → implementation → pre-commit validation.

> **You're in control.** Your CLI of choice orchestrates the AI team, but you decide the workflow. Craft powerful prompts that bring in Gemini Pro, GPT 5, Flash, or local offline models exactly when needed.

<details>
<summary><b>Reasons to Use PAL MCP</b></summary>

A typical workflow with Claude Code as an example:

1. **Multi-Model Orchestration** - Claude coordinates with Gemini Pro, O3, GPT-5, and 50+ other models to get the best analysis for each task

2. **Context Revival Magic** - Even after Claude's context resets, continue conversations seamlessly by having other models "remind" Claude of the discussion

3. **Guided Workflows** - Enforces systematic investigation phases that prevent rushed analysis and ensure thorough code examination

4. **Extended Context Windows** - Break Claude's limits by delegating to Gemini (1M tokens) or O3 (200K tokens) for massive codebases

5. **True Conversation Continuity** - Full context flows across tools and models - Gemini remembers what O3 said 10 steps ago

6. **Model-Specific Strengths** - Extended thinking with Gemini Pro, blazing speed with Flash, strong reasoning with O3, privacy with local Ollama

7. **Professional Code Reviews** - Multi-pass analysis with severity levels, actionable feedback, and consensus from multiple AI experts

8. **Smart Debugging Assistant** - Systematic root cause analysis with hypothesis tracking and confidence levels

9. **Automatic Model Selection** - Claude intelligently picks the right model for each subtask (or you can specify)

10. **Vision Capabilities** - Analyze screenshots, diagrams, and visual content with vision-enabled models

11. **Local Model Support** - Run Llama, Mistral, or other models locally for complete privacy and zero API costs

12. **Bypass MCP Token Limits** - Automatically works around MCP's 25K limit for large prompts and responses

**The Killer Feature:** When Claude's context resets, just ask to "continue with O3" - the other model's response magically revives Claude's understanding without re-ingesting documents!

#### Example: Multi-Model Code Review Workflow

1. `Perform a codereview using gemini pro and o3 and use planner to generate a detailed plan, implement the fixes and do a final precommit check by continuing from the previous codereview`
2. This triggers a [`codereview`](docs/tools/codereview.md) workflow where Claude walks the code, looking for all kinds of issues
3. After multiple passes, collects relevant code and makes note of issues along the way
4. Maintains a `confidence` level between `exploring`, `low`, `medium`, `high` and `certain` to track how confidently it's been able to find and identify issues
5. Generates a detailed list of critical -> low issues
6. Shares the relevant files, findings, etc with **Gemini Pro** to perform a deep dive for a second [`codereview`](docs/tools/codereview.md)
7. Comes back with a response and next does the same with o3, adding to the prompt if a new discovery comes to light
8. When done, Claude takes in all the feedback and combines a single list of all critical -> low issues, including good patterns in your code. The final list includes new findings or revisions in case Claude misunderstood or missed something crucial and one of the other models pointed this out
9. It then uses the [`planner`](docs/tools/planner.md) workflow to break the work down into simpler steps if a major refactor is required
10. Claude then performs the actual work of fixing highlighted issues
11. When done, Claude returns to Gemini Pro for a [`precommit`](docs/tools/precommit.md) review

All within a single conversation thread! Gemini Pro in step 11 _knows_ what was recommended by O3 in step 7! Taking that context
and review into consideration to aid with its final pre-commit review.

**Think of it as Claude Code _for_ Claude Code.** This MCP isn't magic. It's just **super-glue**.

> **Remember:** Claude stays in full control — but **YOU** call the shots.
> PAL is designed to have Claude engage other models only when needed — and to follow through with meaningful back-and-forth.
> **You're** the one who crafts the powerful prompt that makes Claude bring in Gemini, Flash, O3 — or fly solo.
> You're the guide. The prompter. The puppeteer.
> #### You are the AI - **Actually Intelligent**.
</details>

#### Recommended AI Stack

<details>
<summary>For Claude Code Users</summary>

For best results when using [Claude Code](https://claude.ai/code):  

- **Sonnet 4.5** - All agentic work and orchestration
- **Gemini 3.0 Pro** OR **GPT-5.2 / Pro** - Deep thinking, additional code reviews, debugging and validations, pre-commit analysis
</details>

<details>
<summary>For Codex Users</summary>

For best results when using [Codex CLI](https://developers.openai.com/codex/cli):  

- **GPT-5.2 Codex Medium** - All agentic work and orchestration
- **Gemini 3.0 Pro** OR **GPT-5.2-Pro** - Deep thinking, additional code reviews, debugging and validations, pre-commit analysis
</details>

## Quick Start (5 minutes)

**Prerequisites:** Python 3.10+, Git, [uv installed](https://docs.astral.sh/uv/getting-started/installation/)

**1. Get API Keys** (choose one or more):
- **[OpenRouter](https://openrouter.ai/)** - Access multiple models with one API
- **[Gemini](https://makersuite.google.com/app/apikey)** - Google's latest models
- **[OpenAI](https://platform.openai.com/api-keys)** - O3, GPT-5 series
- **[Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/)** - Enterprise deployments of GPT-4o, GPT-4.1, GPT-5 family
- **[X.AI](https://console.x.ai/)** - Grok models
- **[DIAL](https://dialx.ai/)** - Vendor-agnostic model access
- **[Ollama](https://ollama.ai/)** - Local models (free)

**2. Install** (choose one):

**Option A: Clone and Automatic Setup** (recommended)
```bash
git clone https://github.com/BeehiveInnovations/pal-mcp-server.git
cd pal-mcp-server

# Handles everything: setup, config, API keys from system environment. 
# Auto-configures Claude Desktop, Claude Code, Gemini CLI, Codex CLI, Qwen CLI
# Enable / disable additional settings in .env
./run-server.sh  
```

**Option B: Instant Setup with [uvx](https://docs.astral.sh/uv/getting-started/installation/)**
```json
// Add to ~/.claude/settings.json or .mcp.json
// Don't forget to add your API keys under env
{
  "mcpServers": {
    "pal": {
      "command": "bash",
      "args": ["-c", "for p in $(which uvx 2>/dev/null) $HOME/.local/bin/uvx /opt/homebrew/bin/uvx /usr/local/bin/uvx uvx; do [ -x \"$p\" ] && exec \"$p\" --from git+https://github.com/BeehiveInnovations/pal-mcp-server.git pal-mcp-server; done; echo 'uvx not found' >&2; exit 1"],
      "env": {
        "PATH": "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:~/.local/bin",
        "GEMINI_API_KEY": "your-key-here",
        "DISABLED_TOOLS": "analyze,refactor,testgen,secaudit,docgen,tracer",
        "DEFAULT_MODEL": "auto"
      }
    }
  }
}
```

**3. Start Using!**
```
"Use pal to analyze this code for security issues with gemini pro"
"Debug this error with o3 and then get flash to suggest optimizations"
"Plan the migration strategy with pal, get consensus from multiple models"
"clink with cli_name=\"gemini\" role=\"planner\" to draft a phased rollout plan"
```

👉 **[Complete Setup Guide](docs/getting-started.md)** with detailed installation, configuration for Gemini / Codex / Qwen, and troubleshooting
👉 **[Cursor & VS Code Setup](docs/getting-started.md#ide-clients)** for IDE integration instructions
📺 **[Watch tools in action](#-watch-tools-in-action)** to see real-world examples

## Provider Configuration

PAL activates any provider that has credentials in your `.env`. See `.env.example` for deeper customization.

## Core Tools

> **Note:** Each tool comes with its own multi-step workflow, parameters, and descriptions that consume valuable context window space even when not in use. To optimize performance, some tools are disabled by default. See [Tool Configuration](#tool-configuration) below to enable them.

**Collaboration & Planning** *(Enabled by default)*
- **[`clink`](docs/tools/clink.md)** - Bridge requests to external AI CLIs (Gemini planner, codereviewer, etc.)
- **[`chat`](docs/tools/chat.md)** - Brainstorm ideas, get second opinions, validate approaches. With capable models (GPT-5.2 Pro, Gemini 3.0 Pro), generates complete code / implementation
- **[`thinkdeep`](docs/tools/thinkdeep.md)** - Extended reasoning, edge case analysis, alternative perspectives
- **[`planner`](docs/tools/planner.md)** - Break down complex projects into structured, actionable plans
- **[`consensus`](docs/tools/consensus.md)** - Get expert opinions from multiple AI models with stance steering

**Code Analysis & Quality**
- **[`debug`](docs/tools/debug.md)** - Systematic investigation and root cause analysis
- **[`precommit`](docs/tools/precommit.md)** - Validate changes before committing, prevent regressions
- **[`codereview`](docs/tools/codereview.md)** - Professional reviews with severity levels and actionable feedback
- **[`analyze`](docs/tools/analyze.md)** *(disabled by default - [enable](#tool-configuration))* - Understand architecture, patterns, dependencies across entire codebases

**Development Tools** *(Disabled by default - [enable](#tool-configuration))*
- **[`refactor`](docs/tools/refactor.md)** - Intelligent code refactoring with decomposition focus
- **[`testgen`](docs/tools/testgen.md)** - Comprehensive test generation with edge cases
- **[`secaudit`](docs/tools/secaudit.md)** - Security audits with OWASP Top 10 analysis
- **[`docgen`](docs/tools/docgen.md)** - Generate documentation with complexity analysis

**Utilities**
- **[`apilookup`](docs/tools/apilookup.md)** - Forces current-year API/SDK documentation lookups in a sub-process (saves tokens within the current context window), prevents outdated training data responses
- **[`challenge`](docs/tools/challenge.md)** - Prevent "You're absolutely right!" responses with critical analysis
- **[`tracer`](docs/tools/tracer.md)** *(disabled by default - [enable](#tool-configuration))* - Static analysis prompts for call-flow mapping

<details>
<summary><b id="tool-configuration">👉 Tool Configuration</b></summary>

### Default Configuration

To optimize context window usage, only essential tools are enabled by default:

**Enabled by default:**
- `chat`, `thinkdeep`, `planner`, `consensus` - Core collaboration tools
- `codereview`, `precommit`, `debug` - Essential code quality tools
- `apilookup` - Rapid API/SDK information lookup
- `challenge` - Critical thinking utility

**Disabled by default:**
- `analyze`, `refactor`, `testgen`, `secaudit`, `docgen`, `tracer`

### Enabling Additional Tools

To enable additional tools, remove them from the `DISABLED_TOOLS` list:

**Option 1: Edit your .env file**
```bash
# Default configuration (from .env.example)
DISABLED_TOOLS=analyze,refactor,testgen,secaudit,docgen,tracer

# To enable specific tools, remove them from the list
# Example: Enable analyze tool
DISABLED_TOOLS=refactor,testgen,secaudit,docgen,tracer

# To enable ALL tools
DISABLED_TOOLS=
```

**Option 2: Configure in MCP settings**
```json
// In ~/.claude/settings.json or .mcp.json
{
  "mcpServers": {
    "pal": {
      "env": {
        // Tool configuration
        "DISABLED_TOOLS": "refactor,testgen,secaudit,docgen,tracer",
        "DEFAULT_MODEL": "pro",
        "DEFAULT_THINKING_MODE_THINKDEEP": "high",
        
        // API configuration
        "GEMINI_API_KEY": "your-gemini-key",
        "OPENAI_API_KEY": "your-openai-key",
        "OPENROUTER_API_KEY": "your-openrouter-key",
        
        // Logging and performance
        "LOG_LEVEL": "INFO",
        "CONVERSATION_TIMEOUT_HOURS": "6",
        "MAX_CONVERSATION_TURNS": "50"
      }
    }
  }
}
```

**Option 3: Enable all tools**
```json
// Remove or empty the DISABLED_TOOLS to enable everything
{
  "mcpServers": {
    "pal": {
      "env": {
        "DISABLED_TOOLS": ""
      }
    }
  }
}
```

**Note:**
- Essential tools (`version`, `listmodels`) cannot be disabled
- After changing tool configuration, restart your Claude session for changes to take effect
- Each tool adds to context window usage, so only enable what you need

</details>

## 📺 Watch Tools In Action

<details>
<summary><b>Chat Tool</b> - Collaborative decision making and multi-turn conversations</summary>

**Picking Redis vs Memcached:**

[Chat Redis or Memcached_web.webm](https://github.com/user-attachments/assets/41076cfe-dd49-4dfc-82f5-d7461b34705d)

**Multi-turn conversation with continuation:**

[Chat With Gemini_web.webm](https://github.com/user-attachments/assets/37bd57ca-e8a6-42f7-b5fb-11de271e95db)

</details>

<details>
<summary><b>Consensus Tool</b> - Multi-model debate and decision making</summary>

**Multi-model consensus debate:**

[PAL Consensus Debate](https://github.com/user-attachments/assets/76a23dd5-887a-4382-9cf0-642f5cf6219e)

</details>

<details>
<summary><b>PreCommit Tool</b> - Comprehensive change validation</summary>

**Pre-commit validation workflow:**

<div align="center">
  <img src="https://github.com/user-attachments/assets/584adfa6-d252-49b4-b5b0-0cd6e97fb2c6" width="950">
</div>

</details>

<details>
<summary><b>API Lookup Tool</b> - Current vs outdated API documentation</summary>

**Without PAL - outdated APIs:**

[API without PAL](https://github.com/user-attachments/assets/01a79dc9-ad16-4264-9ce1-76a56c3580ee)

**With PAL - current APIs:**

[API with PAL](https://github.com/user-attachments/assets/5c847326-4b66-41f7-8f30-f380453dce22)

</details>

<details>
<summary><b>Challenge Tool</b> - Critical thinking vs reflexive agreement</summary>

**Without PAL:**

![without_pal@2x](https://github.com/user-attachments/assets/64f3c9fb-7ca9-4876-b687-25e847edfd87)

**With PAL:**

![with_pal@2x](https://github.com/user-attachments/assets/9d72f444-ba53-4ab1-83e5-250062c6ee70)

</details>

## Key Features

**AI Orchestration**
- **Auto model selection** - Claude picks the right AI for each task
- **Multi-model workflows** - Chain different models in single conversations
- **Conversation continuity** - Context preserved across tools and models
- **[Context revival](docs/context-revival.md)** - Continue conversations even after context resets

**Model Support**
- **Multiple providers** - Gemini, OpenAI, Azure, X.AI, OpenRouter, DIAL, Ollama
- **Latest models** - GPT-5, Gemini 3.0 Pro, O3, Grok-4, local Llama
- **[Thinking modes](docs/advanced-usage.md#thinking-modes)** - Control reasoning depth vs cost
- **Vision support** - Analyze images, diagrams, screenshots

**Developer Experience**
- **Guided workflows** - Systematic investigation prevents rushed analysis
- **Smart file handling** - Auto-expand directories, manage token limits
- **Web search integration** - Access current documentation and best practices
- **[Large prompt support](docs/advanced-usage.md#working-with-large-prompts)** - Bypass MCP's 25K token limit

## Example Workflows

**Multi-model Code Review:**
```
"Perform a codereview using gemini pro and o3, then use planner to create a fix strategy"
```
→ Claude reviews code systematically → Consults Gemini Pro → Gets O3's perspective → Creates unified action plan

**Collaborative Debugging:**
```
"Debug this race condition with max thinking mode, then validate the fix with precommit"
```
→ Deep investigation → Expert analysis → Solution implementation → Pre-commit validation

**Architecture Planning:**
```
"Plan our microservices migration, get consensus from pro and o3 on the approach"
```
→ Structured planning → Multiple expert opinions → Consensus building → Implementation roadmap

👉 **[Advanced Usage Guide](docs/advanced-usage.md)** for complex workflows, model configuration, and power-user features

## Quick Links

**📖 Documentation**
- [Docs Overview](docs/index.md) - High-level map of major guides
- [Getting Started](docs/getting-started.md) - Complete setup guide
- [Tools Reference](docs/tools/) - All tools with examples
- [Advanced Usage](docs/advanced-usage.md) - Power user features
- [Configuration](docs/configuration.md) - Environment variables, restrictions
- [Adding Providers](docs/adding_providers.md) - Provider-specific setup (OpenAI, Azure, custom gateways)
- [Model Ranking Guide](docs/model_ranking.md) - How intelligence scores drive auto-mode suggestions

**🔧 Setup & Support**
- [WSL Setup](docs/wsl-setup.md) - Windows users
- [Troubleshooting](docs/troubleshooting.md) - Common issues
- [Contributing](docs/contributions.md) - Code standards, PR process

## License

Apache 2.0 License - see [LICENSE](LICENSE) file for details.

## Acknowledgments

Built with the power of **Multi-Model AI** collaboration 🤝
- **A**ctual **I**ntelligence by real Humans
- [MCP (Model Context Protocol)](https://modelcontextprotocol.com)
- [Codex CLI](https://developers.openai.com/codex/cli)
- [Claude Code](https://claude.ai/code)
- [Gemini](https://ai.google.dev/)
- [OpenAI](https://openai.com/)
- [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/)

### Star History

[![Star History Chart](https://api.star-history.com/svg?repos=BeehiveInnovations/pal-mcp-server&type=Date)](https://www.star-history.com/#BeehiveInnovations/pal-mcp-server&Date)


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported          |
| ------- | ------------------ |
| 9.x.x   | :white_check_mark: |
| < 9.0   | :x:                |

## Important Disclaimer

PAL MCP is an open-source Model Context Protocol (MCP) server that acts as middleware between AI clients (Claude Code, Codex CLI, Cursor, etc.) and various AI model providers.

**Please understand the following:**

- **No Warranty**: This software is provided "AS IS" under the Apache 2.0 License, without warranties of any kind. See the [LICENSE](LICENSE) file for full terms.
- **User Responsibility**: The AI client (not PAL MCP) controls tool invocations and workflows. Users are responsible for reviewing AI-generated outputs and actions.
- **API Key Security**: You are responsible for securing your own API keys. Never commit keys to version control or share them publicly.
- **Third-Party Services**: PAL MCP connects to external AI providers (Google, OpenAI, Azure, etc.). Their terms of service and privacy policies apply to data sent through this server.

## Reporting a Vulnerability

**Please do not report security vulnerabilities through public GitHub issues.**

### Preferred Method

Use [GitHub Security Advisories](https://github.com/BeehiveInnovations/pal-mcp-server/security/advisories/new) to report vulnerabilities privately.

### What to Include

- Description of the vulnerability
- Steps to reproduce
- Affected versions
- Potential impact
- Suggested fix (optional)

### What to Expect

- We will acknowledge your report and assess the issue
- Critical issues will be prioritized
- We'll keep you informed of progress as work proceeds

We cannot commit to specific response timelines, but we take security seriously.

### After Resolution

We welcome security researchers to submit a pull request with the fix. This is an open-source project and we appreciate community contributions to improve security.

## Disclosure Policy

We practice coordinated disclosure. Please allow reasonable time to address issues before public disclosure. We'll work with you on timing.

## Scope

### In Scope

- Authentication/authorization bypasses
- Injection vulnerabilities (command injection, prompt injection with security impact)
- Information disclosure (API keys, sensitive data leakage)
- Denial of service vulnerabilities in the MCP server itself
- Dependency vulnerabilities with exploitable impact

### Out of Scope

- Issues in upstream AI providers (report to Google, OpenAI, etc. directly)
- Issues in AI client software (report to Anthropic, OpenAI, Cursor, etc.)
- AI model behavior or outputs (this is controlled by the AI client and model providers)
- Social engineering attacks
- Rate limiting or resource exhaustion on third-party APIs

## Security Best Practices for Users

1. **Protect API Keys**: Store keys in `.env` files (gitignored) or environment variables
2. **Review AI Actions**: Always review AI-suggested code changes before applying
3. **Use Local Models**: For sensitive codebases, consider using Ollama with local models
4. **Network Security**: When self-hosting, ensure appropriate network controls
5. **Keep Updated**: Regularly update to the latest version for security fixes

## Recognition

We appreciate responsible disclosure and will credit security researchers in release notes (unless you prefer anonymity).


================================================
FILE: claude_config_example.json
================================================
{
  "comment": "Example Claude Desktop configuration for PAL MCP Server",
  "comment2": "Run './run-server.sh -c' to get the exact configuration for your system",
  "comment3": "For platform-specific examples, see the examples/ directory",
  "mcpServers": {
    "pal": {
      "command": "/path/to/pal-mcp-server/.pal_venv/bin/python",
      "args": ["/path/to/pal-mcp-server/server.py"]
    }
  }
}

================================================
FILE: clink/__init__.py
================================================
"""Public helpers for clink components."""

from __future__ import annotations

from .registry import ClinkRegistry, get_registry

__all__ = ["ClinkRegistry", "get_registry"]


================================================
FILE: clink/agents/__init__.py
================================================
"""Agent factory for clink CLI integrations."""

from __future__ import annotations

from clink.models import ResolvedCLIClient

from .base import AgentOutput, BaseCLIAgent, CLIAgentError
from .claude import ClaudeAgent
from .codex import CodexAgent
from .gemini import GeminiAgent

_AGENTS: dict[str, type[BaseCLIAgent]] = {
    "gemini": GeminiAgent,
    "codex": CodexAgent,
    "claude": ClaudeAgent,
}


def create_agent(client: ResolvedCLIClient) -> BaseCLIAgent:
    agent_key = (client.runner or client.name).lower()
    agent_cls = _AGENTS.get(agent_key, BaseCLIAgent)
    return agent_cls(client)


__all__ = [
    "AgentOutput",
    "BaseCLIAgent",
    "CLIAgentError",
    "create_agent",
]


================================================
FILE: clink/agents/base.py
================================================
"""Execute configured CLI agents for the clink tool and parse output."""

from __future__ import annotations

import asyncio
import logging
import os
import shlex
import shutil
import tempfile
import time
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path

from clink.constants import DEFAULT_STREAM_LIMIT
from clink.models import ResolvedCLIClient, ResolvedCLIRole
from clink.parsers import BaseParser, ParsedCLIResponse, ParserError, get_parser

logger = logging.getLogger("clink.agent")


@dataclass
class AgentOutput:
    """Container returned by CLI agents after successful execution."""

    parsed: ParsedCLIResponse
    sanitized_command: list[str]
    returncode: int
    stdout: str
    stderr: str
    duration_seconds: float
    parser_name: str
    output_file_content: str | None = None


class CLIAgentError(RuntimeError):
    """Raised when a CLI agent fails (non-zero exit, timeout, parse errors)."""

    def __init__(self, message: str, *, returncode: int | None = None, stdout: str = "", stderr: str = "") -> None:
        super().__init__(message)
        self.returncode = returncode
        self.stdout = stdout
        self.stderr = stderr


class BaseCLIAgent:
    """Execute a configured CLI command and parse its output."""

    def __init__(self, client: ResolvedCLIClient):
        self.client = client
        self._parser: BaseParser = get_parser(client.parser)
        self._logger = logging.getLogger(f"clink.runner.{client.name}")

    async def run(
        self,
        *,
        role: ResolvedCLIRole,
        prompt: str,
        system_prompt: str | None = None,
        files: Sequence[str],
        images: Sequence[str],
    ) -> AgentOutput:
        # Files and images are already embedded into the prompt by the tool; they are
        # accepted here only to keep parity with SimpleTool callers.
        _ = (files, images)
        # The runner simply executes the configured CLI command for the selected role.
        command = self._build_command(role=role, system_prompt=system_prompt)
        env = self._build_environment()

        # Resolve executable path for cross-platform compatibility (especially Windows)
        executable_name = command[0]
        resolved_executable = shutil.which(executable_name)
        if resolved_executable is None:
            raise CLIAgentError(
                f"Executable '{executable_name}' not found in PATH for CLI '{self.client.name}'. "
                f"Ensure the command is installed and accessible."
            )
        command[0] = resolved_executable

        sanitized_command = list(command)

        cwd = str(self.client.working_dir) if self.client.working_dir else None
        limit = DEFAULT_STREAM_LIMIT

        stdout_text = ""
        stderr_text = ""
        output_file_content: str | None = None
        start_time = time.monotonic()

        output_file_path: Path | None = None
        command_with_output_flag = list(command)

        if self.client.output_to_file:
            fd, tmp_path = tempfile.mkstemp(prefix="clink-", suffix=".json")
            os.close(fd)
            output_file_path = Path(tmp_path)
            flag_template = self.client.output_to_file.flag_template
            try:
                rendered_flag = flag_template.format(path=str(output_file_path))
            except KeyError as exc:  # pragma: no cover - defensive
                raise CLIAgentError(f"Invalid output flag template '{flag_template}': missing placeholder {exc}")
            command_with_output_flag.extend(shlex.split(rendered_flag))
            sanitized_command = list(command_with_output_flag)

        self._logger.debug("Executing CLI command: %s", " ".join(sanitized_command))
        if cwd:
            self._logger.debug("Working directory: %s", cwd)

        try:
            process = await asyncio.create_subprocess_exec(
                *command_with_output_flag,
                stdin=asyncio.subprocess.PIPE,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
                limit=limit,
                env=env,
            )
        except FileNotFoundError as exc:
            raise CLIAgentError(f"Executable not found for CLI '{self.client.name}': {exc}") from exc

        try:
            stdout_bytes, stderr_bytes = await asyncio.wait_for(
                process.communicate(prompt.encode("utf-8")),
                timeout=self.client.timeout_seconds,
            )
        except asyncio.TimeoutError as exc:
            process.kill()
            await process.communicate()
            raise CLIAgentError(
                f"CLI '{self.client.name}' timed out after {self.client.timeout_seconds} seconds",
                returncode=None,
            ) from exc

        duration = time.monotonic() - start_time
        return_code = process.returncode
        stdout_text = stdout_bytes.decode("utf-8", errors="replace")
        stderr_text = stderr_bytes.decode("utf-8", errors="replace")

        if output_file_path and output_file_path.exists():
            output_file_content = output_file_path.read_text(encoding="utf-8", errors="replace")
            if self.client.output_to_file and self.client.output_to_file.cleanup:
                try:
                    output_file_path.unlink()
                except OSError:  # pragma: no cover - best effort cleanup
                    pass

            if output_file_content and not stdout_text.strip():
                stdout_text = output_file_content

        if return_code != 0:
            recovered = self._recover_from_error(
                returncode=return_code,
                stdout=stdout_text,
                stderr=stderr_text,
                sanitized_command=sanitized_command,
                duration_seconds=duration,
                output_file_content=output_file_content,
            )
            if recovered is not None:
                return recovered

        if return_code != 0:
            raise CLIAgentError(
                f"CLI '{self.client.name}' exited with status {return_code}",
                returncode=return_code,
                stdout=stdout_text,
                stderr=stderr_text,
            )

        try:
            parsed = self._parser.parse(stdout_text, stderr_text)
        except ParserError as exc:
            raise CLIAgentError(
                f"Failed to parse output from CLI '{self.client.name}': {exc}",
                returncode=return_code,
                stdout=stdout_text,
                stderr=stderr_text,
            ) from exc

        return AgentOutput(
            parsed=parsed,
            sanitized_command=sanitized_command,
            returncode=return_code,
            stdout=stdout_text,
            stderr=stderr_text,
            duration_seconds=duration,
            parser_name=self._parser.name,
            output_file_content=output_file_content,
        )

    def _build_command(self, *, role: ResolvedCLIRole, system_prompt: str | None) -> list[str]:
        base = list(self.client.executable)
        base.extend(self.client.internal_args)
        base.extend(self.client.config_args)
        base.extend(role.role_args)

        return base

    def _build_environment(self) -> dict[str, str]:
        env = os.environ.copy()
        env.update(self.client.env)
        return env

    # ------------------------------------------------------------------
    # Error recovery hooks
    # ------------------------------------------------------------------

    def _recover_from_error(
        self,
        *,
        returncode: int,
        stdout: str,
        stderr: str,
        sanitized_command: list[str],
        duration_seconds: float,
        output_file_content: str | None,
    ) -> AgentOutput | None:
        """Hook for subclasses to convert CLI errors into successful outputs.

        Return an AgentOutput to treat the failure as success, or None to signal
        that normal error handling should proceed.
        """

        return None


================================================
FILE: clink/agents/claude.py
================================================
"""Claude-specific CLI agent hooks."""

from __future__ import annotations

from clink.models import ResolvedCLIRole
from clink.parsers.base import ParserError

from .base import AgentOutput, BaseCLIAgent


class ClaudeAgent(BaseCLIAgent):
    """Claude CLI agent with system-prompt injection support."""

    def _build_command(self, *, role: ResolvedCLIRole, system_prompt: str | None) -> list[str]:
        command = list(self.client.executable)
        command.extend(self.client.internal_args)
        command.extend(self.client.config_args)

        if system_prompt and "--append-system-prompt" not in self.client.config_args:
            command.extend(["--append-system-prompt", system_prompt])

        command.extend(role.role_args)
        return command

    def _recover_from_error(
        self,
        *,
        returncode: int,
        stdout: str,
        stderr: str,
        sanitized_command: list[str],
        duration_seconds: float,
        output_file_content: str | None,
    ) -> AgentOutput | None:
        try:
            parsed = self._parser.parse(stdout, stderr)
        except ParserError:
            return None

        return AgentOutput(
            parsed=parsed,
            sanitized_command=sanitized_command,
            returncode=returncode,
            stdout=stdout,
            stderr=stderr,
            duration_seconds=duration_seconds,
            parser_name=self._parser.name,
            output_file_content=output_file_content,
        )


================================================
FILE: clink/agents/codex.py
================================================
"""Codex-specific CLI agent hooks."""

from __future__ import annotations

from clink.models import ResolvedCLIClient
from clink.parsers.base import ParserError

from .base import AgentOutput, BaseCLIAgent


class CodexAgent(BaseCLIAgent):
    """Codex CLI agent with JSONL recovery support."""

    def __init__(self, client: ResolvedCLIClient):
        super().__init__(client)

    def _recover_from_error(
        self,
        *,
        returncode: int,
        stdout: str,
        stderr: str,
        sanitized_command: list[str],
        duration_seconds: float,
        output_file_content: str | None,
    ) -> AgentOutput | None:
        try:
            parsed = self._parser.parse(stdout, stderr)
        except ParserError:
            return None

        return AgentOutput(
            parsed=parsed,
            sanitized_command=sanitized_command,
            returncode=returncode,
            stdout=stdout,
            stderr=stderr,
            duration_seconds=duration_seconds,
            parser_name=self._parser.name,
            output_file_content=output_file_content,
        )


================================================
FILE: clink/agents/gemini.py
================================================
"""Gemini-specific CLI agent hooks."""

from __future__ import annotations

import json
from typing import Any

from clink.models import ResolvedCLIClient
from clink.parsers.base import ParsedCLIResponse

from .base import AgentOutput, BaseCLIAgent


class GeminiAgent(BaseCLIAgent):
    """Gemini-specific behaviour."""

    def __init__(self, client: ResolvedCLIClient):
        super().__init__(client)

    def _recover_from_error(
        self,
        *,
        returncode: int,
        stdout: str,
        stderr: str,
        sanitized_command: list[str],
        duration_seconds: float,
        output_file_content: str | None,
    ) -> AgentOutput | None:
        combined = "\n".join(part for part in (stderr, stdout) if part)
        if not combined:
            return None

        brace_index = combined.find("{")
        if brace_index == -1:
            return None

        json_candidate = combined[brace_index:]
        try:
            payload: dict[str, Any] = json.loads(json_candidate)
        except json.JSONDecodeError:
            return None

        error_block = payload.get("error")
        if not isinstance(error_block, dict):
            return None

        code = error_block.get("code")
        err_type = error_block.get("type")
        detail_message = error_block.get("message")

        prologue = combined[:brace_index].strip()
        lines: list[str] = []
        if prologue and (not detail_message or prologue not in detail_message):
            lines.append(prologue)
        if detail_message:
            lines.append(detail_message)

        header = "Gemini CLI reported a tool failure"
        if code:
            header = f"{header} ({code})"
        elif err_type:
            header = f"{header} ({err_type})"

        content_lines = [header.rstrip(".") + "."]
        content_lines.extend(lines)
        message = "\n".join(content_lines).strip()

        metadata = {
            "cli_error_recovered": True,
            "cli_error_code": code,
            "cli_error_type": err_type,
            "cli_error_payload": payload,
        }

        parsed = ParsedCLIResponse(content=message or header, metadata=metadata)
        return AgentOutput(
            parsed=parsed,
            sanitized_command=sanitized_command,
            returncode=returncode,
            stdout=stdout,
            stderr=stderr,
            duration_seconds=duration_seconds,
            parser_name=self._parser.name,
            output_file_content=output_file_content,
        )


================================================
FILE: clink/constants.py
================================================
"""Internal defaults and constants for clink."""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path

DEFAULT_TIMEOUT_SECONDS = 1800
DEFAULT_STREAM_LIMIT = 10 * 1024 * 1024  # 10MB per stream

PROJECT_ROOT = Path(__file__).resolve().parent.parent
BUILTIN_PROMPTS_DIR = PROJECT_ROOT / "systemprompts" / "clink"
CONFIG_DIR = PROJECT_ROOT / "conf" / "cli_clients"
USER_CONFIG_DIR = Path.home() / ".pal" / "cli_clients"


@dataclass(frozen=True)
class CLIInternalDefaults:
    """Internal defaults applied to a CLI client during registry load."""

    parser: str
    additional_args: list[str] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    default_role_prompt: str | None = None
    timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS
    runner: str | None = None


INTERNAL_DEFAULTS: dict[str, CLIInternalDefaults] = {
    "gemini": CLIInternalDefaults(
        parser="gemini_json",
        additional_args=["-o", "json"],
        default_role_prompt="systemprompts/clink/default.txt",
        runner="gemini",
    ),
    "codex": CLIInternalDefaults(
        parser="codex_jsonl",
        additional_args=["exec"],
        default_role_prompt="systemprompts/clink/default.txt",
        runner="codex",
    ),
    "claude": CLIInternalDefaults(
        parser="claude_json",
        additional_args=["--print", "--output-format", "json"],
        default_role_prompt="systemprompts/clink/default.txt",
        runner="claude",
    ),
}


================================================
FILE: clink/models.py
================================================
"""Pydantic models for clink configuration and runtime structures."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from pydantic import BaseModel, Field, PositiveInt, field_validator


class OutputCaptureConfig(BaseModel):
    """Optional configuration for CLIs that write output to disk."""

    flag_template: str = Field(..., description="Template used to inject the output path, e.g. '--output {path}'.")
    cleanup: bool = Field(
        default=True,
        description="Whether the temporary file should be removed after reading.",
    )


class CLIRoleConfig(BaseModel):
    """Role-specific configuration loaded from JSON manifests."""

    prompt_path: str | None = Field(
        default=None,
        description="Path to the prompt file that seeds this role.",
    )
    role_args: list[str] = Field(default_factory=list)
    description: str | None = Field(default=None)

    @field_validator("role_args", mode="before")
    @classmethod
    def _ensure_list(cls, value: Any) -> list[str]:
        if value is None:
            return []
        if isinstance(value, list):
            return [str(item) for item in value]
        if isinstance(value, str):
            return [value]
        raise TypeError("role_args must be a list of strings or a single string")


class CLIClientConfig(BaseModel):
    """Raw CLI client configuration before internal defaults are applied."""

    name: str
    command: str | None = None
    working_dir: str | None = None
    additional_args: list[str] = Field(default_factory=list)
    env: dict[str, str] = Field(default_factory=dict)
    timeout_seconds: PositiveInt | None = Field(default=None)
    roles: dict[str, CLIRoleConfig] = Field(default_factory=dict)
    output_to_file: OutputCaptureConfig | None = None

    @field_validator("additional_args", mode="before")
    @classmethod
    def _ensure_args_list(cls, value: Any) -> list[str]:
        if value is None:
            return []
        if isinstance(value, list):
            return [str(item) for item in value]
        if isinstance(value, str):
            return [value]
        raise TypeError("additional_args must be a list of strings or a single string")


class ResolvedCLIRole(BaseModel):
    """Runtime representation of a CLI role with resolved prompt path."""

    name: str
    prompt_path: Path
    role_args: list[str] = Field(default_factory=list)
    description: str | None = None


class ResolvedCLIClient(BaseModel):
    """Runtime configuration after merging defaults and validating paths."""

    name: str
    executable: list[str]
    working_dir: Path | None
    internal_args: list[str] = Field(default_factory=list)
    config_args: list[str] = Field(default_factory=list)
    env: dict[str, str] = Field(default_factory=dict)
    timeout_seconds: int
    parser: str
    runner: str | None = None
    roles: dict[str, ResolvedCLIRole]
    output_to_file: OutputCaptureConfig | None = None

    def list_roles(self) -> list[str]:
        return list(self.roles.keys())

    def get_role(self, role_name: str | None) -> ResolvedCLIRole:
        key = role_name or "default"
        if key not in self.roles:
            available = ", ".join(sorted(self.roles.keys()))
            raise KeyError(f"Role '{role_name}' not configured for CLI '{self.name}'. Available roles: {available}")
        return self.roles[key]


================================================
FILE: clink/parsers/__init__.py
================================================
"""Parser registry for clink."""

from __future__ import annotations

from .base import BaseParser, ParsedCLIResponse, ParserError
from .claude import ClaudeJSONParser
from .codex import CodexJSONLParser
from .gemini import GeminiJSONParser

_PARSER_CLASSES: dict[str, type[BaseParser]] = {
    CodexJSONLParser.name: CodexJSONLParser,
    GeminiJSONParser.name: GeminiJSONParser,
    ClaudeJSONParser.name: ClaudeJSONParser,
}


def get_parser(name: str) -> BaseParser:
    normalized = (name or "").lower()
    if normalized not in _PARSER_CLASSES:
        raise ParserError(f"No parser registered for '{name}'")
    parser_cls = _PARSER_CLASSES[normalized]
    return parser_cls()


__all__ = [
    "BaseParser",
    "ParsedCLIResponse",
    "ParserError",
    "get_parser",
]


================================================
FILE: clink/parsers/base.py
================================================
"""Parser interfaces for clink runner outputs."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any


@dataclass
class ParsedCLIResponse:
    """Result of parsing CLI stdout/stderr."""

    content: str
    metadata: dict[str, Any]


class ParserError(RuntimeError):
    """Raised when CLI output cannot be parsed into a structured response."""


class BaseParser:
    """Base interface for CLI output parsers."""

    name: str = "base"

    def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:
        raise NotImplementedError("Parsers must implement parse()")


================================================
FILE: clink/parsers/claude.py
================================================
"""Parser for Claude CLI JSON output."""

from __future__ import annotations

import json
from typing import Any

from .base import BaseParser, ParsedCLIResponse, ParserError


class ClaudeJSONParser(BaseParser):
    """Parse stdout produced by `claude --output-format json`."""

    name = "claude_json"

    def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:
        if not stdout.strip():
            raise ParserError("Claude CLI returned empty stdout while JSON output was expected")

        try:
            loaded = json.loads(stdout)
        except json.JSONDecodeError as exc:  # pragma: no cover - defensive logging
            raise ParserError(f"Failed to decode Claude CLI JSON output: {exc}") from exc

        events: list[dict[str, Any]] | None = None
        assistant_entry: dict[str, Any] | None = None

        if isinstance(loaded, dict):
            payload: dict[str, Any] = loaded
        elif isinstance(loaded, list):
            events = [item for item in loaded if isinstance(item, dict)]
            result_entry = next(
                (item for item in events if item.get("type") == "result" or "result" in item),
                None,
            )
            assistant_entry = next(
                (item for item in reversed(events) if item.get("type") == "assistant"),
                None,
            )
            payload = result_entry or assistant_entry or (events[-1] if events else {})
            if not payload:
                raise ParserError("Claude CLI JSON array did not contain any parsable objects")
        else:
            raise ParserError("Claude CLI returned unexpected JSON payload")

        metadata = self._build_metadata(payload, stderr)
        if events is not None:
            metadata["raw_events"] = events
            metadata["raw"] = loaded

        result = payload.get("result")
        content: str = ""
        if isinstance(result, str):
            content = result.strip()
        elif isinstance(result, list):
            # Some CLI flows may emit a list of strings; join them conservatively.
            joined = [part.strip() for part in result if isinstance(part, str) and part.strip()]
            content = "\n".join(joined)

        if content:
            return ParsedCLIResponse(content=content, metadata=metadata)

        message = self._extract_message(payload)
        if message is None and assistant_entry and assistant_entry is not payload:
            message = self._extract_message(assistant_entry)
        if message:
            return ParsedCLIResponse(content=message, metadata=metadata)

        stderr_text = stderr.strip()
        if stderr_text:
            metadata.setdefault("stderr", stderr_text)
            return ParsedCLIResponse(
                content="Claude CLI returned no textual result. Raw stderr was preserved for troubleshooting.",
                metadata=metadata,
            )

        raise ParserError("Claude CLI response did not contain a textual result")

    def _build_metadata(self, payload: dict[str, Any], stderr: str) -> dict[str, Any]:
        metadata: dict[str, Any] = {
            "raw": payload,
            "is_error": bool(payload.get("is_error")),
        }

        type_field = payload.get("type")
        if isinstance(type_field, str):
            metadata["type"] = type_field
        subtype_field = payload.get("subtype")
        if isinstance(subtype_field, str):
            metadata["subtype"] = subtype_field

        duration_ms = payload.get("duration_ms")
        if isinstance(duration_ms, (int, float)):
            metadata["duration_ms"] = duration_ms
        api_duration = payload.get("duration_api_ms")
        if isinstance(api_duration, (int, float)):
            metadata["duration_api_ms"] = api_duration

        usage = payload.get("usage")
        if isinstance(usage, dict):
            metadata["usage"] = usage

        model_usage = payload.get("modelUsage")
        if isinstance(model_usage, dict) and model_usage:
            metadata["model_usage"] = model_usage
            first_model = next(iter(model_usage.keys()))
            metadata["model_used"] = first_model

        permission_denials = payload.get("permission_denials")
        if isinstance(permission_denials, list) and permission_denials:
            metadata["permission_denials"] = permission_denials

        session_id = payload.get("session_id")
        if isinstance(session_id, str) and session_id:
            metadata["session_id"] = session_id
        uuid_field = payload.get("uuid")
        if isinstance(uuid_field, str) and uuid_field:
            metadata["uuid"] = uuid_field

        stderr_text = stderr.strip()
        if stderr_text:
            metadata.setdefault("stderr", stderr_text)

        return metadata

    def _extract_message(self, payload: dict[str, Any]) -> str | None:
        message = payload.get("message")
        if isinstance(message, str) and message.strip():
            return message.strip()

        error_field = payload.get("error")
        if isinstance(error_field, dict):
            error_message = error_field.get("message")
            if isinstance(error_message, str) and error_message.strip():
                return error_message.strip()

        return None


================================================
FILE: clink/parsers/codex.py
================================================
"""Parser for Codex CLI JSONL output."""

from __future__ import annotations

import json
from typing import Any

from .base import BaseParser, ParsedCLIResponse, ParserError


class CodexJSONLParser(BaseParser):
    """Parse stdout emitted by `codex exec --json`."""

    name = "codex_jsonl"

    def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:
        lines = [line.strip() for line in (stdout or "").splitlines() if line.strip()]
        events: list[dict[str, Any]] = []
        agent_messages: list[str] = []
        errors: list[str] = []
        usage: dict[str, Any] | None = None

        for line in lines:
            if not line.startswith("{"):
                continue
            try:
                event = json.loads(line)
            except json.JSONDecodeError:
                continue

            events.append(event)
            event_type = event.get("type")
            if event_type == "item.completed":
                item = event.get("item") or {}
                if item.get("type") == "agent_message":
                    text = item.get("text")
                    if isinstance(text, str) and text.strip():
                        agent_messages.append(text.strip())
            elif event_type == "error":
                message = event.get("message")
                if isinstance(message, str) and message.strip():
                    errors.append(message.strip())
            elif event_type == "turn.completed":
                turn_usage = event.get("usage")
                if isinstance(turn_usage, dict):
                    usage = turn_usage

        if not agent_messages and errors:
            agent_messages.extend(errors)

        if not agent_messages:
            raise ParserError("Codex CLI JSONL output did not include an agent_message item")

        content = "\n\n".join(agent_messages).strip()
        metadata: dict[str, Any] = {"events": events}
        if errors:
            metadata["errors"] = errors
        if usage:
            metadata["usage"] = usage
        if stderr and stderr.strip():
            metadata["stderr"] = stderr.strip()

        return ParsedCLIResponse(content=content, metadata=metadata)


================================================
FILE: clink/parsers/gemini.py
================================================
"""Parser for Gemini CLI JSON output."""

from __future__ import annotations

import json
from typing import Any

from .base import BaseParser, ParsedCLIResponse, ParserError


class GeminiJSONParser(BaseParser):
    """Parse stdout produced by `gemini -o json`."""

    name = "gemini_json"

    def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:
        if not stdout.strip():
            raise ParserError("Gemini CLI returned empty stdout while JSON output was expected")

        try:
            payload: dict[str, Any] = json.loads(stdout)
        except json.JSONDecodeError as exc:  # pragma: no cover - defensive logging
            raise ParserError(f"Failed to decode Gemini CLI JSON output: {exc}") from exc

        response = payload.get("response")
        response_text = response.strip() if isinstance(response, str) else ""

        metadata: dict[str, Any] = {"raw": payload}

        stats = payload.get("stats")
        if isinstance(stats, dict):
            metadata["stats"] = stats
            models = stats.get("models")
            if isinstance(models, dict) and models:
                model_name = next(iter(models.keys()))
                metadata["model_used"] = model_name
                model_stats = models.get(model_name) or {}
                tokens = model_stats.get("tokens")
                if isinstance(tokens, dict):
                    metadata["token_usage"] = tokens
                api_stats = model_stats.get("api")
                if isinstance(api_stats, dict):
                    metadata["latency_ms"] = api_stats.get("totalLatencyMs")

        if response_text:
            if stderr and stderr.strip():
                metadata["stderr"] = stderr.strip()
            return ParsedCLIResponse(content=response_text, metadata=metadata)

        fallback_message, extra_metadata = self._build_fallback_message(payload, stderr)
        if fallback_message:
            metadata.update(extra_metadata)
            if stderr and stderr.strip():
                metadata["stderr"] = stderr.strip()
            return ParsedCLIResponse(content=fallback_message, metadata=metadata)

        raise ParserError("Gemini CLI response is missing a textual 'response' field")

    def _build_fallback_message(self, payload: dict[str, Any], stderr: str) -> tuple[str | None, dict[str, Any]]:
        """Derive a human friendly message when Gemini returns empty content."""

        stderr_text = stderr.strip() if stderr else ""
        stderr_lower = stderr_text.lower()
        extra_metadata: dict[str, Any] = {"empty_response": True}

        if "429" in stderr_lower or "rate limit" in stderr_lower:
            extra_metadata["rate_limit_status"] = 429
            message = (
                "Gemini request returned no content because the API reported a 429 rate limit. "
                "Retry after reducing the request size or waiting for quota to replenish."
            )
            return message, extra_metadata

        stats = payload.get("stats")
        if isinstance(stats, dict):
            models = stats.get("models")
            if isinstance(models, dict) and models:
                first_model = next(iter(models.values()))
                if isinstance(first_model, dict):
                    api_stats = first_model.get("api")
                    if isinstance(api_stats, dict):
                        total_errors = api_stats.get("totalErrors")
                        total_requests = api_stats.get("totalRequests")
                        if isinstance(total_errors, int) and total_errors > 0:
                            extra_metadata["api_total_errors"] = total_errors
                            if isinstance(total_requests, int):
                                extra_metadata["api_total_requests"] = total_requests
                            message = (
                                "Gemini CLI returned no textual output. The API reported "
                                f"{total_errors} error(s); see stderr for details."
                            )
                            return message, extra_metadata

        if stderr_text:
            message = "Gemini CLI returned no textual output. Raw stderr was preserved for troubleshooting."
            return message, extra_metadata

        return None, extra_metadata


================================================
FILE: clink/registry.py
================================================
"""Configuration registry for clink CLI integrations."""

from __future__ import annotations

import json
import logging
import shlex
from collections.abc import Iterable
from pathlib import Path

from clink.constants import (
    CONFIG_DIR,
    DEFAULT_TIMEOUT_SECONDS,
    INTERNAL_DEFAULTS,
    PROJECT_ROOT,
    USER_CONFIG_DIR,
    CLIInternalDefaults,
)
from clink.models import (
    CLIClientConfig,
    CLIRoleConfig,
    ResolvedCLIClient,
    ResolvedCLIRole,
)
from utils.env import get_env
from utils.file_utils import read_json_file

logger = logging.getLogger("clink.registry")

CONFIG_ENV_VAR = "CLI_CLIENTS_CONFIG_PATH"


class RegistryLoadError(RuntimeError):
    """Raised when configuration files are invalid or missing critical data."""


class ClinkRegistry:
    """Loads CLI client definitions and exposes them for schema generation/runtime use."""

    def __init__(self) -> None:
        self._clients: dict[str, ResolvedCLIClient] = {}
        self._load()

    def _load(self) -> None:
        self._clients.clear()
        for config_path in self._iter_config_files():
            try:
                data = read_json_file(str(config_path))
            except json.JSONDecodeError as exc:
                raise RegistryLoadError(f"Invalid JSON in {config_path}: {exc}") from exc

            if not data:
                logger.debug("Skipping empty configuration file: %s", config_path)
                continue

            config = CLIClientConfig.model_validate(data)
            resolved = self._resolve_config(config, source_path=config_path)
            key = resolved.name.lower()
            if key in self._clients:
                logger.info("Overriding CLI configuration for '%s' from %s", resolved.name, config_path)
            else:
                logger.debug("Loaded CLI configuration for '%s' from %s", resolved.name, config_path)
            self._clients[key] = resolved

        if not self._clients:
            raise RegistryLoadError(
                "No CLI clients configured. Ensure conf/cli_clients contains at least one definition or set "
                f"{CONFIG_ENV_VAR}."
            )

    def reload(self) -> None:
        """Reload configurations from disk."""
        self._load()

    def list_clients(self) -> list[str]:
        return sorted(client.name for client in self._clients.values())

    def list_roles(self, cli_name: str) -> list[str]:
        config = self.get_client(cli_name)
        return sorted(config.roles.keys())

    def get_client(self, cli_name: str) -> ResolvedCLIClient:
        key = cli_name.lower()
        if key not in self._clients:
            available = ", ".join(self.list_clients())
            raise KeyError(f"CLI '{cli_name}' is not configured. Available clients: {available}")
        return self._clients[key]

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _iter_config_files(self) -> Iterable[Path]:
        search_paths: list[Path] = []

        # 1. Built-in configs
        search_paths.append(CONFIG_DIR)

        # 2. CLI_CLIENTS_CONFIG_PATH environment override (file or directory)
        env_path_raw = get_env(CONFIG_ENV_VAR)
        if env_path_raw:
            env_path = Path(env_path_raw).expanduser()
            search_paths.append(env_path)

        # 3. User overrides in ~/.pal/cli_clients
        search_paths.append(USER_CONFIG_DIR)

        seen: set[Path] = set()

        for base in search_paths:
            if not base:
                continue
            if base in seen:
                continue
            seen.add(base)

            if base.is_file() and base.suffix.lower() == ".json":
                yield base
                continue

            if base.is_dir():
                for path in sorted(base.glob("*.json")):
                    if path.is_file():
                        yield path
            else:
                logger.debug("Configuration path does not exist: %s", base)

    def _resolve_config(self, raw: CLIClientConfig, *, source_path: Path) -> ResolvedCLIClient:
        if not raw.name:
            raise RegistryLoadError(f"CLI configuration at {source_path} is missing a 'name' field")

        normalized_name = raw.name.strip()
        internal_defaults = INTERNAL_DEFAULTS.get(normalized_name.lower())
        if internal_defaults is None:
            raise RegistryLoadError(f"CLI '{raw.name}' is not supported by clink")

        executable = self._resolve_executable(raw, internal_defaults, source_path)

        internal_args = list(internal_defaults.additional_args) if internal_defaults else []
        config_args = list(raw.additional_args)

        timeout_seconds = raw.timeout_seconds or (
            internal_defaults.timeout_seconds if internal_defaults else DEFAULT_TIMEOUT_SECONDS
        )

        parser_name = internal_defaults.parser
        if not parser_name:
            raise RegistryLoadError(
                f"CLI '{raw.name}' must define a parser either in configuration or internal defaults"
            )

        runner_name = internal_defaults.runner if internal_defaults else None

        env = self._merge_env(raw, internal_defaults)
        working_dir = self._resolve_optional_path(raw.working_dir, source_path.parent)
        roles = self._resolve_roles(raw, internal_defaults, source_path)

        output_to_file = raw.output_to_file

        return ResolvedCLIClient(
            name=normalized_name,
            executable=executable,
            internal_args=internal_args,
            config_args=config_args,
            env=env,
            timeout_seconds=int(timeout_seconds),
            parser=parser_name,
            runner=runner_name,
            roles=roles,
            output_to_file=output_to_file,
            working_dir=working_dir,
        )

    def _resolve_executable(
        self,
        raw: CLIClientConfig,
        internal_defaults: CLIInternalDefaults | None,
        source_path: Path,
    ) -> list[str]:
        command = raw.command
        if not command:
            raise RegistryLoadError(f"CLI '{raw.name}' must specify a 'command' in configuration")
        return shlex.split(command)

    def _merge_env(
        self,
        raw: CLIClientConfig,
        internal_defaults: CLIInternalDefaults | None,
    ) -> dict[str, str]:
        merged: dict[str, str] = {}
        if internal_defaults and internal_defaults.env:
            merged.update(internal_defaults.env)
        merged.update(raw.env)
        return merged

    def _resolve_roles(
        self,
        raw: CLIClientConfig,
        internal_defaults: CLIInternalDefaults | None,
        source_path: Path,
    ) -> dict[str, ResolvedCLIRole]:
        roles: dict[str, CLIRoleConfig] = dict(raw.roles)

        default_role_prompt = internal_defaults.default_role_prompt if internal_defaults else None
        if "default" not in roles:
            roles["default"] = CLIRoleConfig(prompt_path=default_role_prompt)
        elif roles["default"].prompt_path is None and default_role_prompt:
            roles["default"].prompt_path = default_role_prompt

        resolved: dict[str, ResolvedCLIRole] = {}
        for role_name, role_config in roles.items():
            prompt_path_str = role_config.prompt_path or default_role_prompt
            if not prompt_path_str:
                raise RegistryLoadError(f"Role '{role_name}' for CLI '{raw.name}' must define a prompt_path")
            prompt_path = self._resolve_prompt_path(prompt_path_str, source_path.parent)
            resolved[role_name] = ResolvedCLIRole(
                name=role_name,
                prompt_path=prompt_path,
                role_args=list(role_config.role_args),
                description=role_config.description,
            )
        return resolved

    def _resolve_prompt_path(self, prompt_path: str, base_dir: Path) -> Path:
        resolved = self._resolve_path(prompt_path, base_dir)
        if not resolved.exists():
            raise RegistryLoadError(f"Prompt file not found: {resolved}")
        return resolved

    def _resolve_optional_path(self, candidate: str | None, base_dir: Path) -> Path | None:
        if not candidate:
            return None
        return self._resolve_path(candidate, base_dir)

    def _resolve_path(self, candidate: str, base_dir: Path) -> Path:
        path = Path(candidate)
        if path.is_absolute():
            return path

        candidate_path = (base_dir / path).resolve()
        if candidate_path.exists():
            return candidate_path

        project_relative = (PROJECT_ROOT / path).resolve()
        return project_relative


_REGISTRY: ClinkRegistry | None = None


def get_registry() -> ClinkRegistry:
    global _REGISTRY
    if _REGISTRY is None:
        _REGISTRY = ClinkRegistry()
    return _REGISTRY


================================================
FILE: code_quality_checks.ps1
================================================
<#
.SYNOPSIS
    Code quality checks script for PAL MCP server on Windows.

.DESCRIPTION
    This PowerShell script performs code quality checks for the PAL MCP server project:
    - Runs static analysis and linting tools on the codebase
    - Ensures code style compliance and detects potential issues
    - Can be integrated into CI/CD pipelines or used locally before commits

.PARAMETER Help
    Displays help information for using the script.

.PARAMETER Verbose
    Enables detailed output during code quality checks.

.EXAMPLE
    .\code_quality_checks.ps1
    Runs all code quality checks on the project.

    .\code_quality_checks.ps1 -Verbose
    Runs code quality checks with detailed output.

.NOTES
    Project Author     : BeehiveInnovations
    Script Author      : GiGiDKR (https://github.com/GiGiDKR)
    Date               : 07-05-2025
    Version            : See project documentation
    References         : https://github.com/BeehiveInnovations/pal-mcp-server
#>
#Requires -Version 5.1
[CmdletBinding()]
param(
    [switch]$SkipTests,
    [switch]$SkipLinting,
    [switch]$VerboseOutput
)

# Set error action preference
$ErrorActionPreference = "Stop"

# Colors for output
function Write-ColorText {
    param(
        [Parameter(Mandatory)]
        [string]$Text,
        [string]$Color = "White"
    )
    Write-Host $Text -ForegroundColor $Color
}

function Write-Emoji {
    param(
        [Parameter(Mandatory)]
        [string]$Emoji,
        [Parameter(Mandatory)]
        [string]$Text,
        [string]$Color = "White"
    )
    Write-Host "$Emoji " -NoNewline
    Write-ColorText $Text -Color $Color
}

Write-Emoji "🔍" "Running Code Quality Checks for PAL MCP Server" -Color Cyan
Write-ColorText "=================================================" -Color Cyan

# Determine Python command
$pythonCmd = $null
$pipCmd = $null

if (Test-Path ".pal_venv") {
    if ($IsWindows -or $env:OS -eq "Windows_NT") {
        if (Test-Path ".pal_venv\Scripts\python.exe") {
            $pythonCmd = ".pal_venv\Scripts\python.exe"
            $pipCmd = ".pal_venv\Scripts\pip.exe"
        }
    } else {
        if (Test-Path ".pal_venv/bin/python") {
            $pythonCmd = ".pal_venv/bin/python"
            $pipCmd = ".pal_venv/bin/pip"
        }
    }
    
    if ($pythonCmd) {
        Write-Emoji "✅" "Using venv" -Color Green
    }
} elseif ($env:VIRTUAL_ENV) {
    $pythonCmd = "python"
    $pipCmd = "pip"
    Write-Emoji "✅" "Using activated virtual environment: $env:VIRTUAL_ENV" -Color Green
} else {
    Write-Emoji "❌" "No virtual environment found!" -Color Red
    Write-ColorText "Please run: .\run-server.ps1 first to set up the environment" -Color Yellow
    exit 1
}

Write-Host ""

# Check and install dev dependencies if needed
Write-Emoji "🔍" "Checking development dependencies..." -Color Cyan
$devDepsNeeded = $false

# List of dev tools to check
$devTools = @("ruff", "black", "isort", "pytest")

foreach ($tool in $devTools) {
    $toolFound = $false
    
    # Check in venv
    if ($IsWindows -or $env:OS -eq "Windows_NT") {
        if (Test-Path ".pal_venv\Scripts\$tool.exe") {
            $toolFound = $true
        }
    } else {
        if (Test-Path ".pal_venv/bin/$tool") {
            $toolFound = $true
        }
    }
    
    # Check in PATH
    if (!$toolFound) {
        try {
            $null = Get-Command $tool -ErrorAction Stop
            $toolFound = $true
        } catch {
            # Tool not found
        }
    }
    
    if (!$toolFound) {
        $devDepsNeeded = $true
        break
    }
}

if ($devDepsNeeded) {
    Write-Emoji "📦" "Installing development dependencies..." -Color Yellow
    try {
        & $pipCmd install -q -r requirements-dev.txt
        if ($LASTEXITCODE -ne 0) {
            throw "Failed to install dev dependencies"
        }
        Write-Emoji "✅" "Development dependencies installed" -Color Green
    } catch {
        Write-Emoji "❌" "Failed to install development dependencies" -Color Red
        Write-ColorText "Error: $_" -Color Red
        exit 1
    }
} else {
    Write-Emoji "✅" "Development dependencies already installed" -Color Green
}

# Set tool paths
if ($IsWindows -or $env:OS -eq "Windows_NT") {
    $ruffCmd = if (Test-Path ".pal_venv\Scripts\ruff.exe") { ".pal_venv\Scripts\ruff.exe" } else { "ruff" }
    $blackCmd = if (Test-Path ".pal_venv\Scripts\black.exe") { ".pal_venv\Scripts\black.exe" } else { "black" }
    $isortCmd = if (Test-Path ".pal_venv\Scripts\isort.exe") { ".pal_venv\Scripts\isort.exe" } else { "isort" }
    $pytestCmd = if (Test-Path ".pal_venv\Scripts\pytest.exe") { ".pal_venv\Scripts\pytest.exe" } else { "pytest" }
} else {
    $ruffCmd = if (Test-Path ".pal_venv/bin/ruff") { ".pal_venv/bin/ruff" } else { "ruff" }
    $blackCmd = if (Test-Path ".pal_venv/bin/black") { ".pal_venv/bin/black" } else { "black" }
    $isortCmd = if (Test-Path ".pal_venv/bin/isort") { ".pal_venv/bin/isort" } else { "isort" }
    $pytestCmd = if (Test-Path ".pal_venv/bin/pytest") { ".pal_venv/bin/pytest" } else { "pytest" }
}

Write-Host ""

# Step 1: Linting and Formatting
if (!$SkipLinting) {
    Write-Emoji "📋" "Step 1: Running Linting and Formatting Checks" -Color Cyan
    Write-ColorText "--------------------------------------------------" -Color Cyan

    try {
        Write-Emoji "🔧" "Running ruff linting with auto-fix..." -Color Yellow
        & $ruffCmd check --fix --exclude test_simulation_files --exclude .pal_venv
        if ($LASTEXITCODE -ne 0) {
            throw "Ruff linting failed"
        }

        Write-Emoji "🎨" "Running black code formatting..." -Color Yellow
        & $blackCmd . --exclude="test_simulation_files/" --exclude=".pal_venv/"
        if ($LASTEXITCODE -ne 0) {
            throw "Black formatting failed"
        }

        Write-Emoji "📦" "Running import sorting with isort..." -Color Yellow
        & $isortCmd . --skip-glob=".pal_venv/*" --skip-glob="test_simulation_files/*"
        if ($LASTEXITCODE -ne 0) {
            throw "Import sorting failed"
        }

        Write-Emoji "✅" "Verifying all linting passes..." -Color Yellow
        & $ruffCmd check --exclude test_simulation_files --exclude .pal_venv
        if ($LASTEXITCODE -ne 0) {
            throw "Final linting verification failed"
        }

        Write-Emoji "✅" "Step 1 Complete: All linting and formatting checks passed!" -Color Green
    } catch {
        Write-Emoji "❌" "Step 1 Failed: Linting and formatting checks failed" -Color Red
        Write-ColorText "Error: $_" -Color Red
        exit 1
    }
} else {
    Write-Emoji "⏭️" "Skipping linting and formatting checks" -Color Yellow
}

Write-Host ""

# Step 2: Unit Tests
if (!$SkipTests) {
    Write-Emoji "🧪" "Step 2: Running Complete Unit Test Suite" -Color Cyan
    Write-ColorText "---------------------------------------------" -Color Cyan

    try {
        Write-Emoji "🏃" "Running unit tests (excluding integration tests)..." -Color Yellow
        
        $pytestArgs = @("tests/", "-v", "-x", "-m", "not integration")
        if ($VerboseOutput) {
            $pytestArgs += "--verbose"
        }
        
        & $pythonCmd -m pytest @pytestArgs
        if ($LASTEXITCODE -ne 0) {
            throw "Unit tests failed"
        }

        Write-Emoji "✅" "Step 2 Complete: All unit tests passed!" -Color Green
    } catch {
        Write-Emoji "❌" "Step 2 Failed: Unit tests failed" -Color Red
        Write-ColorText "Error: $_" -Color Red
        exit 1
    }
} else {
    Write-Emoji "⏭️" "Skipping unit tests" -Color Yellow
}

Write-Host ""

# Step 3: Final Summary
Write-Emoji "🎉" "All Code Quality Checks Passed!" -Color Green
Write-ColorText "==================================" -Color Green

if (!$SkipLinting) {
    Write-Emoji "✅" "Linting (ruff): PASSED" -Color Green
    Write-Emoji "✅" "Formatting (black): PASSED" -Color Green
    Write-Emoji "✅" "Import sorting (isort): PASSED" -Color Green
} else {
    Write-Emoji "⏭️" "Linting: SKIPPED" -Color Yellow
}

if (!$SkipTests) {
    Write-Emoji "✅" "Unit tests: PASSED" -Color Green
} else {
    Write-Emoji "⏭️" "Unit tests: SKIPPED" -Color Yellow
}

Write-Host ""
Write-Emoji "🚀" "Your code is ready for commit and GitHub Actions!" -Color Green
Write-Emoji "💡" "Remember to add simulator tests if you modified tools" -Color Yellow


================================================
FILE: code_quality_checks.sh
================================================
#!/bin/bash

# PAL MCP Server - Code Quality Checks
# This script runs all required linting and testing checks before committing changes.
# ALL checks must pass 100% for CI/CD to succeed.

set -e  # Exit on any error

echo "🔍 Running Code Quality Checks for PAL MCP Server"
echo "================================================="

# Determine Python command
if [[ -f ".pal_venv/bin/python" ]]; then
    PYTHON_CMD=".pal_venv/bin/python"
    PIP_CMD=".pal_venv/bin/pip"
    echo "✅ Using venv"
elif [[ -n "$VIRTUAL_ENV" ]]; then
    PYTHON_CMD="python"
    PIP_CMD="pip"
    echo "✅ Using activated virtual environment: $VIRTUAL_ENV"
else
    echo "❌ No virtual environment found!"
    echo "Please run: ./run-server.sh first to set up the environment"
    exit 1
fi
echo ""

# Check and install dev dependencies if needed
echo "🔍 Checking development dependencies..."
DEV_DEPS_NEEDED=false

# Check each dev dependency
for tool in ruff black isort pytest; do
    # Check if tool exists in venv or in PATH
    if [[ -f ".pal_venv/bin/$tool" ]] || command -v $tool &> /dev/null; then
        continue
    else
        DEV_DEPS_NEEDED=true
        break
    fi
done

if [ "$DEV_DEPS_NEEDED" = true ]; then
    echo "📦 Installing development dependencies..."
    $PIP_CMD install -q -r requirements-dev.txt
    echo "✅ Development dependencies installed"
else
    echo "✅ Development dependencies already installed"
fi

# Set tool paths
if [[ -f ".pal_venv/bin/ruff" ]]; then
    RUFF=".pal_venv/bin/ruff"
    BLACK=".pal_venv/bin/black"
    ISORT=".pal_venv/bin/isort"
    PYTEST=".pal_venv/bin/pytest"
else
    RUFF="ruff"
    BLACK="black"
    ISORT="isort"
    PYTEST="pytest"
fi
echo ""

# Step 1: Linting and Formatting
echo "📋 Step 1: Running Linting and Formatting Checks"
echo "--------------------------------------------------"

echo "🔧 Running ruff linting with auto-fix..."
$RUFF check --fix --exclude test_simulation_files --exclude .pal_venv

echo "🎨 Running black code formatting..."
$BLACK . --exclude="test_simulation_files/" --exclude=".pal_venv/"

echo "📦 Running import sorting with isort..."
$ISORT . --skip-glob=".pal_venv/*" --skip-glob="test_simulation_files/*"

echo "✅ Verifying all linting passes..."
$RUFF check --exclude test_simulation_files --exclude .pal_venv

echo "✅ Step 1 Complete: All linting and formatting checks passed!"
echo ""

# Step 2: Unit Tests
echo "🧪 Step 2: Running Complete Unit Test Suite"
echo "---------------------------------------------"

echo "🏃 Running unit tests (excluding integration tests)..."
$PYTHON_CMD -m pytest tests/ -v -x -m "not integration"

echo "✅ Step 2 Complete: All unit tests passed!"
echo ""

# Step 3: Final Summary
echo "🎉 All Code Quality Checks Passed!"
echo "=================================="
echo "✅ Linting (ruff): PASSED"
echo "✅ Formatting (black): PASSED" 
echo "✅ Import sorting (isort): PASSED"
echo "✅ Unit tests: PASSED"
echo ""
echo "🚀 Your code is ready for commit and GitHub Actions!"
echo "💡 Remember to add simulator tests if you modified tools"

================================================
FILE: communication_simulator_test.py
================================================
"""
Communication Simulator Test for PAL MCP Server

This script provides comprehensive end-to-end testing of the PAL MCP Server
by simulating real Claude CLI communications and validating conversation
continuity, file handling, deduplication features, and clarification scenarios.

Test Flow:
1. Setup standalone server environment
2. Load and run individual test modules
3. Validate system behavior through logs and memory
4. Cleanup and report results

Usage:
    python communication_simulator_test.py [--verbose] [--keep-logs] [--tests TEST_NAME...] [--individual TEST_NAME] [--setup]

    --tests: Run specific tests only (space-separated)
    --list-tests: List all available tests
    --individual: Run a single test individually
    --setup: Force setup standalone server environment using run-server.sh

Available tests:
    basic_conversation          - Basic conversation flow with chat tool
    content_validation          - Content validation and duplicate detection
    per_tool_deduplication      - File deduplication for individual tools
    cross_tool_continuation     - Cross-tool conversation continuation scenarios
    cross_tool_comprehensive    - Comprehensive cross-tool integration testing
    line_number_validation      - Line number handling validation across tools
    memory_validation           - Conversation memory validation
    model_thinking_config       - Model thinking configuration testing
    o3_model_selection          - O3 model selection and routing testing
    ollama_custom_url           - Ollama custom URL configuration testing
    openrouter_fallback         - OpenRouter fallback mechanism testing
    openrouter_models           - OpenRouter models availability testing
    token_allocation_validation - Token allocation and limits validation
    testgen_validation          - TestGen tool validation with specific test function
    refactor_validation         - Refactor tool validation with codesmells
    debug_validation            - Debug tool validation with actual bugs
    conversation_chain_validation - Conversation chain continuity validation

Quick Test Mode (for time-limited testing):
    Use --quick to run the essential 6 tests that provide maximum coverage:
    - cross_tool_continuation (cross-tool conversation memory)
    - basic_conversation (basic chat functionality)
    - content_validation (content validation and deduplication)
    - model_thinking_config (flash/flashlite model testing)
    - o3_model_selection (o3 model selection testing)
    - per_tool_deduplication (file deduplication for individual tools)

Examples:
    # Run all tests
    python communication_simulator_test.py

    # Run only basic conversation and content validation tests
    python communication_simulator_test.py --tests basic_conversation content_validation

    # Run a single test individually (with full standalone setup)
    python communication_simulator_test.py --individual content_validation

    # Run quick test mode (essential 6 tests for time-limited testing)
    python communication_simulator_test.py --quick

    # Force setup standalone server environment before running tests
    python communication_simulator_test.py --setup

    # List available tests
    python communication_simulator_test.py --list-tests
"""

import argparse
import logging
import os
import shutil
import subprocess
import sys
import tempfile


class CommunicationSimulator:
    """Simulates real-world Claude CLI communication with MCP Gemini server"""

    def __init__(
        self,
        verbose: bool = False,
        keep_logs: bool = False,
        selected_tests: list[str] = None,
        setup: bool = False,
        quick_mode: bool = False,
    ):
        self.verbose = verbose
        self.keep_logs = keep_logs
        self.selected_tests = selected_tests or []
        self.setup = setup
        self.quick_mode = quick_mode
        self.temp_dir = None
        self.server_process = None

        # Configure logging first
        log_level = logging.DEBUG if verbose else logging.INFO
        logging.basicConfig(level=log_level, format="%(asctime)s - %(levelname)s - %(message)s")
        self.logger = logging.getLogger(__name__)

        self.python_path = self._get_python_path()

        # Import test registry
        from simulator_tests import TEST_REGISTRY

        self.test_registry = TEST_REGISTRY

        # Define quick mode tests (essential tests for time-limited testing)
        # Focus on tests that work with current tool configurations
        self.quick_mode_tests = [
            "cross_tool_continuation",  # Cross-tool conversation memory
            "basic_conversation",  # Basic chat functionality
            "content_validation",  # Content validation and deduplication
            "model_thinking_config",  # Flash/flashlite model testing
            "o3_model_selection",  # O3 model selection testing
            "per_tool_deduplication",  # File deduplication for individual tools
        ]

        # If quick mode is enabled, override selected_tests
        if self.quick_mode:
            self.selected_tests = self.quick_mode_tests
            self.logger.info(f"Quick mode enabled - running {len(self.quick_mode_tests)} essential tests")

        # Available test methods mapping
        self.available_tests = {
            name: self._create_test_runner(test_class) for name, test_class in self.test_registry.items()
        }

        # Test result tracking
        self.test_results = dict.fromkeys(self.test_registry.keys(), False)

    def _get_python_path(self) -> str:
        """Get the Python path for the virtual environment"""
        current_dir = os.getcwd()

        # Try .venv first (modern convention)
        venv_python = os.path.join(current_dir, ".venv", "bin", "python")
        if os.path.exists(venv_python):
            return venv_python

        # Try venv as fallback
        venv_python = os.path.join(current_dir, "venv", "bin", "python")
        if os.path.exists(venv_python):
            return venv_python

        # Try .pal_venv as fallback
        pal_venv_python = os.path.join(current_dir, ".pal_venv", "bin", "python")
        if os.path.exists(pal_venv_python):
            return pal_venv_python

        # Fallback to system python if venv doesn't exist
        self.logger.warning("Virtual environment not found, using system python")
        return "python"

    def _create_test_runner(self, test_class):
        """Create a test runner function for a test class"""

        def run_test():
            test_instance = test_class(verbose=self.verbose)
            result = test_instance.run_test()
            # Update results
            test_name = test_instance.test_name
            self.test_results[test_name] = result
            return result

        return run_test

    def setup_test_environment(self) -> bool:
        """Setup test environment"""
        try:
            self.logger.info("Setting up test environment...")

            # Create temporary directory for test files
            self.temp_dir = tempfile.mkdtemp(prefix="mcp_test_")
            self.logger.debug(f"Created temp directory: {self.temp_dir}")

            # Only run run-server.sh if setup is requested
            if self.setup:
                if not self._run_server_script():
                    return False

            # Always verify server environment is available
            return self._verify_server_environment()

        except Exception as e:
            self.logger.error(f"Failed to setup test environment: {e}")
            return False

    def _run_server_script(self) -> bool:
        """Run the run-server.sh script"""
        try:
            self.logger.info("Running run-server.sh...")

            # Check if run-server.sh exists
            setup_script = "./run-server.sh"
            if not os.path.exists(setup_script):
                self.logger.error(f"run-server.sh not found at {setup_script}")
                return False

            # Make sure it's executable
            result = self._run_command(["chmod", "+x", setup_script], capture_output=True)
            if result.returncode != 0:
                self.logger.error(f"Failed to make run-server.sh executable: {result.stderr}")
                return False

            # Run the setup script
            result = self._run_command([setup_script], capture_output=True)
            if result.returncode != 0:
                self.logger.error(f"run-server.sh failed: {result.stderr}")
                return False

            self.logger.info("run-server.sh completed successfully")
            return True

        except Exception as e:
            self.logger.error(f"Failed to run run-server.sh: {e}")
            return False

    def _verify_server_environment(self) -> bool:
        """Verify that server environment is ready"""
        try:
            self.logger.info("Verifying standalone server environment...")

            # Check if server.py exists
            server_file = "server.py"
            if not os.path.exists(server_file):
                self.logger.error(f"Server file not found: {server_file}")
                self.logger.error("Please ensure you're in the correct directory and server.py exists")
                return False

            # Check if virtual environment is available
            if not os.path.exists(self.python_path):
                self.logger.error(f"Python executable not found: {self.python_path}")
                self.logger.error("Please run ./run-server.sh first to set up the environment")
                return False

            # Check if required dependencies are available
            try:
                result = self._run_command([self.python_path, "-c", "import json; print('OK')"], capture_output=True)
                if result.returncode != 0:
                    self.logger.error("Python environment validation failed")
                    return False
            except Exception as e:
                self.logger.error(f"Python environment check failed: {e}")
                return False

            self.logger.info("Standalone server environment is ready")
            return True

        except Exception as e:
            self.logger.error(f"Server environment verification failed: {e}")
            self.logger.error("Please ensure the server environment is set up correctly, or use --setup")
            return False

    def simulate_claude_cli_session(self) -> bool:
        """Simulate a complete Claude CLI session with conversation continuity"""
        try:
            self.logger.info("Starting Claude CLI simulation...")

            # If specific tests are selected, run only those
            if self.selected_tests:
                return self._run_selected_tests()

            # Otherwise run all tests in order
            test_sequence = list(self.test_registry.keys())

            for test_name in test_sequence:
                if not self._run_single_test(test_name):
                    return False

            self.logger.info("All tests passed")
            return True

        except Exception as e:
            self.logger.error(f"Claude CLI simulation failed: {e}")
            return False

    def _run_selected_tests(self) -> bool:
        """Run only the selected tests"""
        try:
            self.logger.info(f"Running selected tests: {', '.join(self.selecte
Download .txt
gitextract_qj0m35_8/

├── .claude/
│   ├── commands/
│   │   └── fix-github-issue.md
│   └── settings.json
├── .coveragerc
├── .dockerignore
├── .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.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── claude_config_example.json
├── 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
├── docs/
│   ├── adding_providers.md
│   ├── adding_tools.md
│   ├── advanced-usage.md
│   ├── ai-collaboration.md
│   ├── ai_banter.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
│   ├── name-change.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
├── pal-mcp-server
├── providers/
│   ├── __init__.py
│   ├── azure_openai.py
│   ├── base.py
│   ├── custom.py
│   ├── dial.py
│   ├── gemini.py
│   ├── openai.py
│   ├── openai_compatible.py
│   ├── openrouter.py
│   ├── registries/
│   │   ├── __init__.py
│   │   ├── azure.py
│   │   ├── base.py
│   │   ├── custom.py
│   │   ├── dial.py
│   │   ├── gemini.py
│   │   ├── openai.py
│   │   ├── openrouter.py
│   │   └── xai.py
│   ├── registry.py
│   ├── registry_provider_mixin.py
│   ├── shared/
│   │   ├── __init__.py
│   │   ├── model_capabilities.py
│   │   ├── model_response.py
│   │   ├── provider_type.py
│   │   └── temperature.py
│   └── xai.py
├── pyproject.toml
├── pytest.ini
├── requirements-dev.txt
├── requirements.txt
├── run-server.ps1
├── run-server.sh
├── run_integration_tests.ps1
├── run_integration_tests.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.py
│   ├── test_planner_validation_old.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.txt
│   │   ├── default_codereviewer.txt
│   │   └── default_planner.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/
│   ├── CASSETTE_MAINTENANCE.md
│   ├── __init__.py
│   ├── 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_gpt51_for.json
│   │   ├── consensus_step1_gpt52_for.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.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_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.py
│   ├── test_consensus_integration.py
│   ├── test_consensus_schema.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.py
│   ├── test_listmodels_restrictions.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_openrouter_store_parameter.py
│   ├── test_parse_model_option.py
│   ├── test_path_traversal_security.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
├── 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
Download .txt
Showing preview only (265K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2687 symbols across 222 files)

FILE: clink/agents/__init__.py
  function create_agent (line 19) | def create_agent(client: ResolvedCLIClient) -> BaseCLIAgent:

FILE: clink/agents/base.py
  class AgentOutput (line 24) | class AgentOutput:
  class CLIAgentError (line 37) | class CLIAgentError(RuntimeError):
    method __init__ (line 40) | def __init__(self, message: str, *, returncode: int | None = None, std...
  class BaseCLIAgent (line 47) | class BaseCLIAgent:
    method __init__ (line 50) | def __init__(self, client: ResolvedCLIClient):
    method run (line 55) | async def run(
    method _build_command (line 193) | def _build_command(self, *, role: ResolvedCLIRole, system_prompt: str ...
    method _build_environment (line 201) | def _build_environment(self) -> dict[str, str]:
    method _recover_from_error (line 210) | def _recover_from_error(

FILE: clink/agents/claude.py
  class ClaudeAgent (line 11) | class ClaudeAgent(BaseCLIAgent):
    method _build_command (line 14) | def _build_command(self, *, role: ResolvedCLIRole, system_prompt: str ...
    method _recover_from_error (line 25) | def _recover_from_error(

FILE: clink/agents/codex.py
  class CodexAgent (line 11) | class CodexAgent(BaseCLIAgent):
    method __init__ (line 14) | def __init__(self, client: ResolvedCLIClient):
    method _recover_from_error (line 17) | def _recover_from_error(

FILE: clink/agents/gemini.py
  class GeminiAgent (line 14) | class GeminiAgent(BaseCLIAgent):
    method __init__ (line 17) | def __init__(self, client: ResolvedCLIClient):
    method _recover_from_error (line 20) | def _recover_from_error(

FILE: clink/constants.py
  class CLIInternalDefaults (line 18) | class CLIInternalDefaults:

FILE: clink/models.py
  class OutputCaptureConfig (line 11) | class OutputCaptureConfig(BaseModel):
  class CLIRoleConfig (line 21) | class CLIRoleConfig(BaseModel):
    method _ensure_list (line 33) | def _ensure_list(cls, value: Any) -> list[str]:
  class CLIClientConfig (line 43) | class CLIClientConfig(BaseModel):
    method _ensure_args_list (line 57) | def _ensure_args_list(cls, value: Any) -> list[str]:
  class ResolvedCLIRole (line 67) | class ResolvedCLIRole(BaseModel):
  class ResolvedCLIClient (line 76) | class ResolvedCLIClient(BaseModel):
    method list_roles (line 91) | def list_roles(self) -> list[str]:
    method get_role (line 94) | def get_role(self, role_name: str | None) -> ResolvedCLIRole:

FILE: clink/parsers/__init__.py
  function get_parser (line 17) | def get_parser(name: str) -> BaseParser:

FILE: clink/parsers/base.py
  class ParsedCLIResponse (line 10) | class ParsedCLIResponse:
  class ParserError (line 17) | class ParserError(RuntimeError):
  class BaseParser (line 21) | class BaseParser:
    method parse (line 26) | def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:

FILE: clink/parsers/claude.py
  class ClaudeJSONParser (line 11) | class ClaudeJSONParser(BaseParser):
    method parse (line 16) | def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:
    method _build_metadata (line 79) | def _build_metadata(self, payload: dict[str, Any], stderr: str) -> dic...
    method _extract_message (line 126) | def _extract_message(self, payload: dict[str, Any]) -> str | None:

FILE: clink/parsers/codex.py
  class CodexJSONLParser (line 11) | class CodexJSONLParser(BaseParser):
    method parse (line 16) | def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:

FILE: clink/parsers/gemini.py
  class GeminiJSONParser (line 11) | class GeminiJSONParser(BaseParser):
    method parse (line 16) | def parse(self, stdout: str, stderr: str) -> ParsedCLIResponse:
    method _build_fallback_message (line 59) | def _build_fallback_message(self, payload: dict[str, Any], stderr: str...

FILE: clink/registry.py
  class RegistryLoadError (line 33) | class RegistryLoadError(RuntimeError):
  class ClinkRegistry (line 37) | class ClinkRegistry:
    method __init__ (line 40) | def __init__(self) -> None:
    method _load (line 44) | def _load(self) -> None:
    method reload (line 71) | def reload(self) -> None:
    method list_clients (line 75) | def list_clients(self) -> list[str]:
    method list_roles (line 78) | def list_roles(self, cli_name: str) -> list[str]:
    method get_client (line 82) | def get_client(self, cli_name: str) -> ResolvedCLIClient:
    method _iter_config_files (line 93) | def _iter_config_files(self) -> Iterable[Path]:
    method _resolve_config (line 128) | def _resolve_config(self, raw: CLIClientConfig, *, source_path: Path) ...
    method _resolve_executable (line 174) | def _resolve_executable(
    method _merge_env (line 185) | def _merge_env(
    method _resolve_roles (line 196) | def _resolve_roles(
    method _resolve_prompt_path (line 224) | def _resolve_prompt_path(self, prompt_path: str, base_dir: Path) -> Path:
    method _resolve_optional_path (line 230) | def _resolve_optional_path(self, candidate: str | None, base_dir: Path...
    method _resolve_path (line 235) | def _resolve_path(self, candidate: str, base_dir: Path) -> Path:
  function get_registry (line 251) | def get_registry() -> ClinkRegistry:

FILE: communication_simulator_test.py
  class CommunicationSimulator (line 79) | class CommunicationSimulator:
    method __init__ (line 82) | def __init__(
    method _get_python_path (line 134) | def _get_python_path(self) -> str:
    method _create_test_runner (line 157) | def _create_test_runner(self, test_class):
    method setup_test_environment (line 170) | def setup_test_environment(self) -> bool:
    method _run_server_script (line 191) | def _run_server_script(self) -> bool:
    method _verify_server_environment (line 221) | def _verify_server_environment(self) -> bool:
    method simulate_claude_cli_session (line 257) | def simulate_claude_cli_session(self) -> bool:
    method _run_selected_tests (line 280) | def _run_selected_tests(self) -> bool:
    method _run_single_test (line 296) | def _run_single_test(self, test_name: str) -> bool:
    method run_individual_test (line 319) | def run_individual_test(self, test_name: str) -> bool:
    method get_available_tests (line 352) | def get_available_tests(self) -> dict[str, str]:
    method print_test_summary (line 361) | def print_test_summary(self):
    method run_full_test_suite (line 388) | def run_full_test_suite(self) -> bool:
    method cleanup (line 415) | def cleanup(self):
    method _run_command (line 443) | def _run_command(self, cmd: list[str], check: bool = True, capture_out...
  function parse_arguments (line 451) | def parse_arguments():
  function list_available_tests (line 469) | def list_available_tests():
  function run_individual_test (line 481) | def run_individual_test(simulator, test_name):
  function run_test_suite (line 504) | def run_test_suite(simulator):
  function main (line 528) | def main():

FILE: config.py
  function _calculate_mcp_prompt_limit (line 117) | def _calculate_mcp_prompt_limit() -> int:

FILE: docker/scripts/healthcheck.py
  function check_process (line 20) | def check_process():
  function check_python_imports (line 29) | def check_python_imports():
  function check_log_directory (line 45) | def check_log_directory():
  function check_environment (line 63) | def check_environment():
  function main (line 91) | def main():

FILE: providers/azure_openai.py
  class AzureOpenAIProvider (line 23) | class AzureOpenAIProvider(OpenAICompatibleProvider):
    method __init__ (line 35) | def __init__(
    method get_all_model_capabilities (line 78) | def get_all_model_capabilities(self) -> dict[str, ModelCapabilities]:
    method get_provider_type (line 81) | def get_provider_type(self) -> ProviderType:
    method get_capabilities (line 84) | def get_capabilities(self, model_name: str) -> ModelCapabilities:  # t...
    method validate_model_name (line 94) | def validate_model_name(self, model_name: str) -> bool:  # type: ignor...
    method _build_capabilities_map (line 100) | def _build_capabilities_map(self) -> dict[str, ModelCapabilities]:
    method _load_registry_entries (line 161) | def _load_registry_entries(self) -> dict[str, dict]:
    method _merge_specs (line 179) | def _merge_specs(
    method _normalise_deployments (line 206) | def _normalise_deployments(mapping: dict[str, object]) -> dict[str, di...
    method client (line 233) | def client(self):  # type: ignore[override]
    method generate_content (line 280) | def generate_content(
    method _resolve_canonical_and_deployment (line 318) | def _resolve_canonical_and_deployment(self, model_name: str) -> tuple[...
    method _parse_allowed_models (line 331) | def _parse_allowed_models(self) -> set[str] | None:  # type: ignore[ov...

FILE: providers/base.py
  class ModelProvider (line 16) | class ModelProvider(ABC):
    method __init__ (line 42) | def __init__(self, api_key: str, **kwargs):
    method get_provider_type (line 52) | def get_provider_type(self) -> ProviderType:
    method get_capabilities (line 55) | def get_capabilities(self, model_name: str) -> ModelCapabilities:
    method get_all_model_capabilities (line 77) | def get_all_model_capabilities(self) -> dict[str, ModelCapabilities]:
    method get_capabilities_by_rank (line 85) | def get_capabilities_by_rank(self) -> list[tuple[str, ModelCapabilitie...
    method _invalidate_capability_cache (line 101) | def _invalidate_capability_cache(self) -> None:
    method list_models (line 106) | def list_models(
    method generate_content (line 147) | def generate_content(
    method count_tokens (line 189) | def count_tokens(self, text: str, model_name: str) -> int:
    method close (line 201) | def close(self) -> None:
    method _is_error_retryable (line 209) | def _is_error_retryable(self, error: Exception) -> bool:
    method _run_with_retries (line 242) | def _run_with_retries(
    method validate_model_name (line 312) | def validate_model_name(self, model_name: str) -> bool:
    method validate_parameters (line 326) | def validate_parameters(self, model_name: str, temperature: float, **k...
    method get_preferred_model (line 343) | def get_preferred_model(self, category: "ToolModelCategory", allowed_m...
    method get_model_registry (line 348) | def get_model_registry(self) -> Optional[dict[str, Any]]:
    method _lookup_capabilities (line 356) | def _lookup_capabilities(
    method _ensure_model_allowed (line 365) | def _ensure_model_allowed(
    method _finalise_capabilities (line 389) | def _finalise_capabilities(
    method _raise_unsupported_model (line 399) | def _raise_unsupported_model(self, model_name: str) -> None:
    method _resolve_model_name (line 404) | def _resolve_model_name(self, model_name: str) -> str:

FILE: providers/custom.py
  class CustomProvider (line 13) | class CustomProvider(OpenAICompatibleProvider):
    method __init__ (line 37) | def __init__(self, api_key: str = "", base_url: str = "", **kwargs):
    method _lookup_capabilities (line 92) | def _lookup_capabilities(
    method get_provider_type (line 114) | def get_provider_type(self) -> ProviderType:
    method _resolve_model_name (line 123) | def _resolve_model_name(self, model_name: str) -> str:
    method get_all_model_capabilities (line 166) | def get_all_model_capabilities(self) -> dict[str, ModelCapabilities]:

FILE: providers/dial.py
  class DIALModelProvider (line 17) | class DIALModelProvider(RegistryBackedProviderMixin, OpenAICompatiblePro...
    method __init__ (line 35) | def __init__(self, api_key: str, **kwargs):
    method get_provider_type (line 104) | def get_provider_type(self) -> ProviderType:
    method _get_deployment_client (line 108) | def _get_deployment_client(self, deployment: str):
    method generate_content (line 152) | def generate_content(
    method close (line 279) | def close(self) -> None:

FILE: providers/gemini.py
  class GeminiModelProvider (line 24) | class GeminiModelProvider(RegistryBackedProviderMixin, ModelProvider):
    method __init__ (line 45) | def __init__(self, api_key: str, **kwargs):
    method client (line 64) | def client(self):
    method _resolve_http_timeout (line 85) | def _resolve_http_timeout(self) -> Optional[float]:
    method generate_content (line 114) | def generate_content(
    method get_provider_type (line 313) | def get_provider_type(self) -> ProviderType:
    method _extract_usage (line 317) | def _extract_usage(self, response) -> dict[str, int]:
    method _is_error_retryable (line 355) | def _is_error_retryable(self, error: Exception) -> bool:
    method _process_image (line 433) | def _process_image(self, image_path: str) -> Optional[dict]:
    method get_preferred_model (line 456) | def get_preferred_model(self, category: "ToolModelCategory", allowed_m...

FILE: providers/openai.py
  class OpenAIModelProvider (line 17) | class OpenAIModelProvider(RegistryBackedProviderMixin, OpenAICompatibleP...
    method __init__ (line 28) | def __init__(self, api_key: str, **kwargs):
    method _lookup_capabilities (line 40) | def _lookup_capabilities(
    method _finalise_capabilities (line 66) | def _finalise_capabilities(
    method _raise_unsupported_model (line 78) | def _raise_unsupported_model(self, model_name: str) -> None:
    method get_provider_type (line 85) | def get_provider_type(self) -> ProviderType:
    method get_preferred_model (line 93) | def get_preferred_model(self, category: "ToolModelCategory", allowed_m...

FILE: providers/openai_compatible.py
  class OpenAICompatibleProvider (line 22) | class OpenAICompatibleProvider(ModelProvider):
    method __init__ (line 34) | def __init__(self, api_key: str, base_url: str = None, **kwargs):
    method _ensure_model_allowed (line 63) | def _ensure_model_allowed(
    method _parse_allowed_models (line 106) | def _parse_allowed_models(self) -> Optional[set[str]]:
    method _configure_timeouts (line 134) | def _configure_timeouts(self, **kwargs):
    method _is_localhost_url (line 197) | def _is_localhost_url(self) -> bool:
    method _validate_base_url (line 227) | def _validate_base_url(self) -> None:
    method client (line 257) | def client(self):
    method _sanitize_for_logging (line 331) | def _sanitize_for_logging(self, params: dict) -> dict:
    method _safe_extract_output_text (line 359) | def _safe_extract_output_text(self, response) -> str:
    method _generate_with_responses_endpoint (line 388) | def _generate_with_responses_endpoint(
    method generate_content (line 499) | def generate_content(
    method validate_parameters (line 676) | def validate_parameters(self, model_name: str, temperature: float, **k...
    method _extract_usage (line 703) | def _extract_usage(self, response) -> dict[str, int]:
    method count_tokens (line 722) | def count_tokens(self, text: str, model_name: str) -> int:
    method _is_error_retryable (line 742) | def _is_error_retryable(self, error: Exception) -> bool:
    method _process_image (line 833) | def _process_image(self, image_path: str) -> Optional[dict]:

FILE: providers/openrouter.py
  class OpenRouterProvider (line 16) | class OpenRouterProvider(OpenAICompatibleProvider):
    method __init__ (line 46) | def __init__(self, api_key: str, **kwargs):
    method _lookup_capabilities (line 69) | def _lookup_capabilities(
    method get_provider_type (line 111) | def get_provider_type(self) -> ProviderType:
    method list_models (line 119) | def list_models(
    method _resolve_model_name (line 180) | def _resolve_model_name(self, model_name: str) -> str:
    method get_all_model_capabilities (line 200) | def get_all_model_capabilities(self) -> dict[str, ModelCapabilities]:

FILE: providers/registries/azure.py
  class AzureModelRegistry (line 13) | class AzureModelRegistry(CustomModelRegistryBase):
    method __init__ (line 16) | def __init__(self, config_path: str | None = None) -> None:
    method _extra_keys (line 24) | def _extra_keys(self) -> set[str]:
    method _provider_default (line 27) | def _provider_default(self) -> ProviderType:
    method _default_friendly_name (line 30) | def _default_friendly_name(self, model_name: str) -> str:
    method _finalise_entry (line 33) | def _finalise_entry(self, entry: dict) -> tuple[ModelCapabilities, dict]:

FILE: providers/registries/base.py
  class CustomModelRegistryBase (line 23) | class CustomModelRegistryBase:
    method __init__ (line 26) | def __init__(
    method reload (line 60) | def reload(self) -> None:
    method list_models (line 65) | def list_models(self) -> list[str]:
    method list_aliases (line 68) | def list_aliases(self) -> list[str]:
    method resolve (line 71) | def resolve(self, name_or_alias: str) -> ModelCapabilities | None:
    method get_capabilities (line 82) | def get_capabilities(self, name_or_alias: str) -> ModelCapabilities | ...
    method get_entry (line 85) | def get_entry(self, model_name: str) -> dict | None:
    method get_model_config (line 88) | def get_model_config(self, model_name: str) -> ModelCapabilities | None:
    method iter_entries (line 93) | def iter_entries(self) -> Iterable[tuple[str, ModelCapabilities, dict]]:
    method _load_config_data (line 100) | def _load_config_data(self) -> dict:
    method use_resources (line 137) | def use_resources(self) -> bool:
    method _parse_models (line 140) | def _parse_models(self, data: dict) -> Iterable[ModelCapabilities | No...
    method _convert_entry (line 146) | def _convert_entry(self, raw: dict) -> ModelCapabilities | None:
    method _default_friendly_name (line 178) | def _default_friendly_name(self, model_name: str) -> str:
    method _extra_keys (line 181) | def _extra_keys(self) -> set[str]:
    method _provider_default (line 184) | def _provider_default(self) -> ProviderType:
    method _finalise_entry (line 187) | def _finalise_entry(self, entry: dict) -> tuple[ModelCapabilities, dict]:
    method _build_maps (line 190) | def _build_maps(self, configs: Iterable[ModelCapabilities]) -> None:
  class CapabilityModelRegistry (line 215) | class CapabilityModelRegistry(CustomModelRegistryBase):
    method __init__ (line 218) | def __init__(
    method _provider_default (line 236) | def _provider_default(self) -> ProviderType:
    method _default_friendly_name (line 239) | def _default_friendly_name(self, model_name: str) -> str:
    method _finalise_entry (line 242) | def _finalise_entry(self, entry: dict) -> tuple[ModelCapabilities, dict]:

FILE: providers/registries/custom.py
  class CustomEndpointModelRegistry (line 9) | class CustomEndpointModelRegistry(CapabilityModelRegistry):
    method __init__ (line 12) | def __init__(self, config_path: str | None = None) -> None:
    method _finalise_entry (line 21) | def _finalise_entry(self, entry: dict) -> tuple[ModelCapabilities, dict]:

FILE: providers/registries/dial.py
  class DialModelRegistry (line 9) | class DialModelRegistry(CapabilityModelRegistry):
    method __init__ (line 12) | def __init__(self, config_path: str | None = None) -> None:

FILE: providers/registries/gemini.py
  class GeminiModelRegistry (line 9) | class GeminiModelRegistry(CapabilityModelRegistry):
    method __init__ (line 12) | def __init__(self, config_path: str | None = None) -> None:

FILE: providers/registries/openai.py
  class OpenAIModelRegistry (line 9) | class OpenAIModelRegistry(CapabilityModelRegistry):
    method __init__ (line 12) | def __init__(self, config_path: str | None = None) -> None:

FILE: providers/registries/openrouter.py
  class OpenRouterModelRegistry (line 9) | class OpenRouterModelRegistry(CapabilityModelRegistry):
    method __init__ (line 12) | def __init__(self, config_path: str | None = None) -> None:
    method _finalise_entry (line 21) | def _finalise_entry(self, entry: dict) -> tuple[ModelCapabilities, dict]:

FILE: providers/registries/xai.py
  class XAIModelRegistry (line 9) | class XAIModelRegistry(CapabilityModelRegistry):
    method __init__ (line 12) | def __init__(self, config_path: str | None = None) -> None:

FILE: providers/registry.py
  class ModelProviderRegistry (line 15) | class ModelProviderRegistry:
    method __new__ (line 48) | def __new__(cls):
    method register_provider (line 60) | def register_provider(cls, provider_type: ProviderType, provider_class...
    method get_provider (line 73) | def get_provider(cls, provider_type: ProviderType, force_new: bool = F...
    method get_provider_for_model (line 154) | def get_provider_for_model(cls, model_name: str) -> Optional[ModelProv...
    method get_available_providers (line 192) | def get_available_providers(cls) -> list[ProviderType]:
    method get_available_models (line 198) | def get_available_models(cls, respect_restrictions: bool = True) -> di...
    method _collect_restricted_display_names (line 260) | def _collect_restricted_display_names(
    method get_available_model_names (line 304) | def get_available_model_names(cls, provider_type: Optional[ProviderTyp...
    method _get_api_key_for_provider (line 325) | def _get_api_key_for_provider(cls, provider_type: ProviderType) -> Opt...
    method _get_allowed_models_for_provider (line 351) | def _get_allowed_models_for_provider(cls, provider: ModelProvider, pro...
    method get_preferred_fallback_model (line 384) | def get_preferred_fallback_model(cls, tool_category: Optional["ToolMod...
    method get_available_providers_with_keys (line 436) | def get_available_providers_with_keys(cls) -> list[ProviderType]:
    method clear_cache (line 450) | def clear_cache(cls) -> None:
    method reset_for_testing (line 456) | def reset_for_testing(cls) -> None:
    method unregister_provider (line 467) | def unregister_provider(cls, provider_type: ProviderType) -> None:

FILE: providers/registry_provider_mixin.py
  class RegistryBackedProviderMixin (line 29) | class RegistryBackedProviderMixin:
    method _registry_logger (line 37) | def _registry_logger(cls) -> logging.Logger:
    method _ensure_registry (line 42) | def _ensure_registry(cls, *, force_reload: bool = False) -> None:
    method reload_registry (line 68) | def reload_registry(cls) -> None:
    method get_all_model_capabilities (line 73) | def get_all_model_capabilities(self) -> dict[str, ModelCapabilities]:
    method get_model_registry (line 79) | def get_model_registry(self) -> dict[str, ModelCapabilities] | None:

FILE: providers/shared/model_capabilities.py
  class ModelCapabilities (line 14) | class ModelCapabilities:
    method get_effective_temperature (line 67) | def get_effective_temperature(self, requested_temperature: float) -> O...
    method get_effective_capability_rank (line 80) | def get_effective_capability_rank(self) -> int:
    method collect_aliases (line 114) | def collect_aliases(model_configs: dict[str, "ModelCapabilities"]) -> ...
    method collect_model_names (line 124) | def collect_model_names(

FILE: providers/shared/model_response.py
  class ModelResponse (line 12) | class ModelResponse:
    method total_tokens (line 23) | def total_tokens(self) -> int:

FILE: providers/shared/provider_type.py
  class ProviderType (line 8) | class ProviderType(Enum):

FILE: providers/shared/temperature.py
  class TemperatureConstraint (line 29) | class TemperatureConstraint(ABC):
    method validate (line 43) | def validate(self, temperature: float) -> bool:
    method get_corrected_value (line 47) | def get_corrected_value(self, temperature: float) -> float:
    method get_description (line 51) | def get_description(self) -> str:
    method get_default (line 55) | def get_default(self) -> float:
    method infer_support (line 59) | def infer_support(model_name: str) -> tuple[bool, str]:
    method resolve_settings (line 84) | def resolve_settings(
    method create (line 118) | def create(constraint_type: str) -> "TemperatureConstraint":
  class FixedTemperatureConstraint (line 131) | class FixedTemperatureConstraint(TemperatureConstraint):
    method __init__ (line 134) | def __init__(self, value: float):
    method validate (line 137) | def validate(self, temperature: float) -> bool:
    method get_corrected_value (line 140) | def get_corrected_value(self, temperature: float) -> float:
    method get_description (line 143) | def get_description(self) -> str:
    method get_default (line 146) | def get_default(self) -> float:
  class RangeTemperatureConstraint (line 150) | class RangeTemperatureConstraint(TemperatureConstraint):
    method __init__ (line 153) | def __init__(self, min_temp: float, max_temp: float, default: Optional...
    method validate (line 158) | def validate(self, temperature: float) -> bool:
    method get_corrected_value (line 161) | def get_corrected_value(self, temperature: float) -> float:
    method get_description (line 164) | def get_description(self) -> str:
    method get_default (line 167) | def get_default(self) -> float:
  class DiscreteTemperatureConstraint (line 171) | class DiscreteTemperatureConstraint(TemperatureConstraint):
    method __init__ (line 174) | def __init__(self, allowed_values: list[float], default: Optional[floa...
    method validate (line 178) | def validate(self, temperature: float) -> bool:
    method get_corrected_value (line 181) | def get_corrected_value(self, temperature: float) -> float:
    method get_description (line 184) | def get_description(self) -> str:
    method get_default (line 187) | def get_default(self) -> float:

FILE: providers/xai.py
  class XAIModelProvider (line 17) | class XAIModelProvider(RegistryBackedProviderMixin, OpenAICompatibleProv...
    method __init__ (line 33) | def __init__(self, api_key: str, **kwargs):
    method get_provider_type (line 41) | def get_provider_type(self) -> ProviderType:
    method get_preferred_model (line 45) | def get_preferred_model(self, category: "ToolModelCategory", allowed_m...

FILE: scripts/sync_version.py
  function update_config_version (line 13) | def update_config_version():

FILE: server.py
  class LocalTimeFormatter (line 81) | class LocalTimeFormatter(logging.Formatter):
    method formatTime (line 82) | def formatTime(self, record, datefmt=None):
  function parse_disabled_tools_env (line 172) | def parse_disabled_tools_env() -> set[str]:
  function validate_disabled_tools (line 185) | def validate_disabled_tools(disabled_tools: set[str], all_tools: dict[st...
  function apply_tool_filter (line 201) | def apply_tool_filter(all_tools: dict[str, Any], disabled_tools: set[str...
  function log_tool_configuration (line 221) | def log_tool_configuration(disabled_tools: set[str], enabled_tools: dict...
  function filter_disabled_tools (line 238) | def filter_disabled_tools(all_tools: dict[str, Any]) -> dict[str, Any]:
  function configure_providers (line 378) | def configure_providers():
  function handle_list_tools (line 631) | async def handle_list_tools() -> list[Tool]:
  function handle_call_tool (line 693) | async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list...
  function parse_model_option (line 881) | def parse_model_option(model_string: str) -> tuple[str, Optional[str]]:
  function get_follow_up_instructions (line 915) | def get_follow_up_instructions(current_turn_count: int, max_turns: int =...
  function reconstruct_thread_context (line 968) | async def reconstruct_thread_context(arguments: dict[str, Any]) -> dict[...
  function handle_list_prompts (line 1292) | async def handle_list_prompts() -> list[Prompt]:
  function handle_get_prompt (line 1342) | async def handle_get_prompt(name: str, arguments: dict[str, Any] = None)...
  function main (line 1449) | async def main():
  function run (line 1516) | def run():

FILE: simulator_tests/base_test.py
  class BaseSimulatorTest (line 17) | class BaseSimulatorTest:
    method __init__ (line 20) | def __init__(self, verbose: bool = False):
    method _get_python_path (line 32) | def _get_python_path(self) -> str:
    method setup_test_files (line 55) | def setup_test_files(self):
    method call_mcp_tool (line 127) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _parse_mcp_response (line 201) | def _parse_mcp_response(self, stdout: str, expected_id: int = 2) -> Op...
    method _extract_continuation_id (line 237) | def _extract_continuation_id(self, response_text: str) -> Optional[str]:
    method run_command (line 271) | def run_command(self, cmd: list[str], check: bool = True, capture_outp...
    method create_additional_test_file (line 278) | def create_additional_test_file(self, filename: str, content: str) -> ...
    method cleanup_test_files (line 289) | def cleanup_test_files(self):
    method get_server_logs_since (line 301) | def get_server_logs_since(self, since_time: Optional[str] = None) -> str:
    method get_recent_server_logs (line 305) | def get_recent_server_logs(self, lines: int = 500) -> str:
    method get_server_logs_subprocess (line 309) | def get_server_logs_subprocess(self, lines: int = 500) -> str:
    method check_server_logs_for_errors (line 313) | def check_server_logs_for_errors(self, lines: int = 500) -> list[str]:
    method extract_conversation_usage_logs (line 317) | def extract_conversation_usage_logs(self, logs: str) -> list[dict[str,...
    method extract_conversation_token_usage (line 321) | def extract_conversation_token_usage(self, logs: str) -> list[int]:
    method extract_thread_creation_logs (line 325) | def extract_thread_creation_logs(self, logs: str) -> list[dict[str, st...
    method extract_history_traversal_logs (line 329) | def extract_history_traversal_logs(self, logs: str) -> list[dict[str, ...
    method validate_file_deduplication_in_logs (line 333) | def validate_file_deduplication_in_logs(self, logs: str, tool_name: st...
    method search_logs_for_pattern (line 337) | def search_logs_for_pattern(
    method get_log_file_info (line 343) | def get_log_file_info(self) -> dict[str, dict[str, any]]:
    method run_test (line 347) | def run_test(self) -> bool:
    method test_name (line 352) | def test_name(self) -> str:
    method test_description (line 357) | def test_description(self) -> str:

FILE: simulator_tests/conversation_base_test.py
  class ConversationBaseTest (line 46) | class ConversationBaseTest(BaseSimulatorTest):
    method __init__ (line 49) | def __init__(self, verbose: bool = False):
    method setUp (line 54) | def setUp(self):
    method _clear_conversation_memory (line 65) | def _clear_conversation_memory(self):
    method _import_tools (line 78) | def _import_tools(self):
    method _get_event_loop (line 99) | def _get_event_loop(self):
    method call_mcp_tool_direct (line 109) | def call_mcp_tool_direct(self, tool_name: str, params: dict) -> tuple[...
    method _extract_continuation_id_from_response (line 191) | def _extract_continuation_id_from_response(self, response_text: str) -...
    method tearDown (line 239) | def tearDown(self):
    method test_name (line 246) | def test_name(self) -> str:
    method test_description (line 251) | def test_description(self) -> str:

FILE: simulator_tests/log_utils.py
  class LogUtils (line 14) | class LogUtils:
    method get_server_logs_since (line 22) | def get_server_logs_since(cls, since_time: Optional[str] = None) -> str:
    method get_recent_server_logs (line 57) | def get_recent_server_logs(cls, lines: int = 500) -> str:
    method get_server_logs_subprocess (line 80) | def get_server_logs_subprocess(cls, lines: int = 500) -> str:
    method check_server_logs_for_errors (line 100) | def check_server_logs_for_errors(cls, lines: int = 500) -> list[str]:
    method extract_conversation_usage_logs (line 123) | def extract_conversation_usage_logs(cls, logs: str) -> list[dict[str, ...
    method extract_conversation_token_usage (line 167) | def extract_conversation_token_usage(cls, logs: str) -> list[int]:
    method extract_thread_creation_logs (line 187) | def extract_thread_creation_logs(cls, logs: str) -> list[dict[str, str]]:
    method extract_history_traversal_logs (line 207) | def extract_history_traversal_logs(cls, logs: str) -> list[dict[str, U...
    method validate_file_deduplication_in_logs (line 227) | def validate_file_deduplication_in_logs(cls, logs: str, tool_name: str...
    method search_logs_for_pattern (line 259) | def search_logs_for_pattern(
    method get_log_file_info (line 286) | def get_log_file_info(cls) -> dict[str, dict[str, Union[str, int, bool...

FILE: simulator_tests/test_analyze_validation.py
  class AnalyzeValidationTest (line 16) | class AnalyzeValidationTest(ConversationBaseTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method _create_analysis_codebase (line 69) | def _create_analysis_codebase(self):
    method _test_single_analysis_session (line 439) | def _test_single_analysis_session(self) -> bool:
    method _test_analysis_refocus_flow (line 533) | def _test_analysis_refocus_flow(self) -> bool:
    method _test_complete_analysis_with_expert (line 619) | def _test_complete_analysis_with_expert(self) -> bool:
    method _test_certain_confidence (line 732) | def _test_certain_confidence(self) -> bool:
    method _test_context_aware_file_embedding (line 794) | def _test_context_aware_file_embedding(self) -> bool:
    method _test_analysis_types (line 899) | def _test_analysis_types(self) -> bool:
    method call_mcp_tool (line 991) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_analyze_continuation_id (line 1004) | def _extract_analyze_continuation_id(self, response_text: str) -> Opti...
    method _parse_analyze_response (line 1015) | def _parse_analyze_response(self, response_text: str) -> dict:
    method _validate_step_response (line 1026) | def _validate_step_response(

FILE: simulator_tests/test_basic_conversation.py
  class BasicConversationTest (line 14) | class BasicConversationTest(BaseSimulatorTest):
    method test_name (line 18) | def test_name(self) -> str:
    method test_description (line 22) | def test_description(self) -> str:
    method run_test (line 25) | def run_test(self) -> bool:

FILE: simulator_tests/test_chat_simple_validation.py
  class ChatSimpleValidationTest (line 20) | class ChatSimpleValidationTest(ConversationBaseTest):
    method test_name (line 24) | def test_name(self) -> str:
    method test_description (line 28) | def test_description(self) -> str:
    method run_test (line 31) | def run_test(self) -> bool:
    method test_new_conversation_no_continuation (line 71) | def test_new_conversation_no_continuation(self) -> bool:
    method test_continue_existing_conversation (line 108) | def test_continue_existing_conversation(self) -> bool:
    method test_file_handling_with_conversation (line 149) | def test_file_handling_with_conversation(self) -> bool:
    method test_temperature_validation_edge_cases (line 202) | def test_temperature_validation_edge_cases(self) -> bool:
    method test_image_limits_per_model (line 266) | def test_image_limits_per_model(self) -> bool:
    method test_conversation_context_preservation (line 328) | def test_conversation_context_preservation(self) -> bool:
    method test_chat_with_images (line 375) | def test_chat_with_images(self) -> bool:
    method test_continued_chat_with_previous_files (line 440) | def test_continued_chat_with_previous_files(self) -> bool:

FILE: simulator_tests/test_codereview_validation.py
  class CodeReviewValidationTest (line 16) | class CodeReviewValidationTest(ConversationBaseTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method _create_test_code_for_review (line 69) | def _create_test_code_for_review(self):
    method _test_single_review_session (line 239) | def _test_single_review_session(self) -> bool:
    method _test_review_refocus_flow (line 339) | def _test_review_refocus_flow(self) -> bool:
    method _test_complete_review_with_analysis (line 427) | def _test_complete_review_with_analysis(self) -> bool:
    method _test_certain_confidence (line 546) | def _test_certain_confidence(self) -> bool:
    method _test_context_aware_file_embedding (line 608) | def _test_context_aware_file_embedding(self) -> bool:
    method _test_multi_step_file_context (line 752) | def _test_multi_step_file_context(self) -> bool:
    method call_mcp_tool (line 944) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_review_continuation_id (line 957) | def _extract_review_continuation_id(self, response_text: str) -> Optio...
    method _parse_review_response (line 968) | def _parse_review_response(self, response_text: str) -> dict:
    method _validate_step_response (line 979) | def _validate_step_response(

FILE: simulator_tests/test_consensus_conversation.py
  class TestConsensusConversation (line 14) | class TestConsensusConversation(ConversationBaseTest):
    method call_mcp_tool (line 17) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple:
    method test_name (line 23) | def test_name(self) -> str:
    method test_description (line 27) | def test_description(self) -> str:
    method get_server_logs (line 30) | def get_server_logs(self):
    method run_test (line 42) | def run_test(self) -> bool:

FILE: simulator_tests/test_consensus_three_models.py
  class TestConsensusThreeModels (line 10) | class TestConsensusThreeModels(BaseSimulatorTest):
    method test_name (line 14) | def test_name(self) -> str:
    method test_description (line 18) | def test_description(self) -> str:
    method run_test (line 21) | def run_test(self) -> bool:

FILE: simulator_tests/test_consensus_workflow_accurate.py
  class TestConsensusWorkflowAccurate (line 18) | class TestConsensusWorkflowAccurate(ConversationBaseTest):
    method test_name (line 22) | def test_name(self) -> str:
    method test_description (line 26) | def test_description(self) -> str:
    method run_test (line 29) | def run_test(self) -> bool:

FILE: simulator_tests/test_content_validation.py
  class ContentValidationTest (line 14) | class ContentValidationTest(BaseSimulatorTest):
    method test_name (line 18) | def test_name(self) -> str:
    method test_description (line 22) | def test_description(self) -> str:
    method run_test (line 25) | def run_test(self) -> bool:

FILE: simulator_tests/test_conversation_chain_validation.py
  class ConversationChainValidationTest (line 28) | class ConversationChainValidationTest(ConversationBaseTest):
    method test_name (line 32) | def test_name(self) -> str:
    method test_description (line 36) | def test_description(self) -> str:
    method run_test (line 39) | def run_test(self) -> bool:
    method call_mcp_tool (line 361) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple:
  function main (line 368) | def main():

FILE: simulator_tests/test_cross_tool_comprehensive.py
  class CrossToolComprehensiveTest (line 19) | class CrossToolComprehensiveTest(ConversationBaseTest):
    method call_mcp_tool (line 22) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple:
    method test_name (line 33) | def test_name(self) -> str:
    method test_description (line 37) | def test_description(self) -> str:
    method run_test (line 40) | def run_test(self) -> bool:

FILE: simulator_tests/test_cross_tool_continuation.py
  class CrossToolContinuationTest (line 12) | class CrossToolContinuationTest(ConversationBaseTest):
    method test_name (line 16) | def test_name(self) -> str:
    method test_description (line 20) | def test_description(self) -> str:
    method run_test (line 23) | def run_test(self) -> bool:
    method _test_chat_thinkdeep_codereview (line 59) | def _test_chat_thinkdeep_codereview(self) -> bool:
    method _test_analyze_debug_thinkdeep (line 123) | def _test_analyze_debug_thinkdeep(self) -> bool:
    method _test_multi_file_continuation (line 191) | def _test_multi_file_continuation(self) -> bool:

FILE: simulator_tests/test_debug_certain_confidence.py
  class DebugCertainConfidenceTest (line 20) | class DebugCertainConfidenceTest(ConversationBaseTest):
    method test_name (line 24) | def test_name(self) -> str:
    method test_description (line 28) | def test_description(self) -> str:
    method run_test (line 31) | def run_test(self) -> bool:
    method _create_obvious_bug_scenarios (line 65) | def _create_obvious_bug_scenarios(self):
    method _test_obvious_import_error_certain (line 145) | def _test_obvious_import_error_certain(self) -> bool:
    method _test_certain_always_trusted (line 252) | def _test_certain_always_trusted(self) -> bool:
    method _test_regular_high_confidence_expert_analysis (line 306) | def _test_regular_high_confidence_expert_analysis(self) -> bool:
    method _test_multi_step_investigation_certain (line 367) | def _test_multi_step_investigation_certain(self) -> bool:
    method call_mcp_tool_direct (line 473) | def call_mcp_tool_direct(self, tool_name: str, params: dict) -> tuple[...
    method _extract_debug_continuation_id (line 510) | def _extract_debug_continuation_id(self, response_text: str) -> Option...
    method _parse_debug_response (line 519) | def _parse_debug_response(self, response_text: str) -> dict:
    method _validate_investigation_response (line 528) | def _validate_investigation_response(

FILE: simulator_tests/test_debug_validation.py
  class DebugValidationTest (line 16) | class DebugValidationTest(ConversationBaseTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method _create_buggy_code (line 69) | def _create_buggy_code(self):
    method _test_single_investigation_session (line 147) | def _test_single_investigation_session(self) -> bool:
    method _test_investigation_refine_flow (line 233) | def _test_investigation_refine_flow(self) -> bool:
    method _test_complete_investigation_with_analysis (line 313) | def _test_complete_investigation_with_analysis(self) -> bool:
    method _test_certain_confidence (line 421) | def _test_certain_confidence(self) -> bool:
    method call_mcp_tool (line 476) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_debug_continuation_id (line 489) | def _extract_debug_continuation_id(self, response_text: str) -> Option...
    method _parse_debug_response (line 500) | def _parse_debug_response(self, response_text: str) -> dict:
    method _validate_step_response (line 511) | def _validate_step_response(
    method _test_context_aware_file_embedding (line 559) | def _test_context_aware_file_embedding(self) -> bool:
    method _test_multi_step_file_context (line 747) | def _test_multi_step_file_context(self) -> bool:

FILE: simulator_tests/test_line_number_validation.py
  class LineNumberValidationTest (line 11) | class LineNumberValidationTest(BaseSimulatorTest):
    method test_name (line 15) | def test_name(self) -> str:
    method test_description (line 19) | def test_description(self) -> str:
    method run_test (line 22) | def run_test(self) -> bool:

FILE: simulator_tests/test_logs_validation.py
  class LogsValidationTest (line 12) | class LogsValidationTest(BaseSimulatorTest):
    method test_name (line 16) | def test_name(self) -> str:
    method test_description (line 20) | def test_description(self) -> str:
    method run_test (line 23) | def run_test(self) -> bool:

FILE: simulator_tests/test_model_thinking_config.py
  class TestModelThinkingConfig (line 12) | class TestModelThinkingConfig(BaseSimulatorTest):
    method test_name (line 16) | def test_name(self) -> str:
    method test_description (line 20) | def test_description(self) -> str:
    method test_pro_model_with_thinking_config (line 23) | def test_pro_model_with_thinking_config(self):
    method test_flash_model_without_thinking_config (line 48) | def test_flash_model_without_thinking_config(self):
    method test_model_resolution_logic (line 75) | def test_model_resolution_logic(self):
    method test_default_model_behavior (line 111) | def test_default_model_behavior(self):
    method run_test (line 136) | def run_test(self) -> bool:
  function main (line 165) | def main():

FILE: simulator_tests/test_o3_model_selection.py
  class O3ModelSelectionTest (line 15) | class O3ModelSelectionTest(BaseSimulatorTest):
    method test_name (line 19) | def test_name(self) -> str:
    method test_description (line 23) | def test_description(self) -> str:
    method run_test (line 26) | def run_test(self) -> bool:
    method _run_openrouter_o3_test (line 211) | def _run_openrouter_o3_test(self) -> bool:
  function main (line 339) | def main():

FILE: simulator_tests/test_o3_pro_expensive.py
  class O3ProExpensiveTest (line 20) | class O3ProExpensiveTest(BaseSimulatorTest):
    method test_name (line 24) | def test_name(self) -> str:
    method test_description (line 28) | def test_description(self) -> str:
    method run_test (line 31) | def run_test(self) -> bool:
  function main (line 91) | def main():

FILE: simulator_tests/test_ollama_custom_url.py
  class OllamaCustomUrlTest (line 16) | class OllamaCustomUrlTest(BaseSimulatorTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method validate_successful_response (line 175) | def validate_successful_response(self, response: str, test_name: str, ...
    method _validate_ai_response_content (line 258) | def _validate_ai_response_content(self, response: str) -> bool:
    method _check_server_logs_for_errors (line 313) | def _check_server_logs_for_errors(self):
    method validate_local_model_response (line 331) | def validate_local_model_response(self, response: str) -> bool:

FILE: simulator_tests/test_openrouter_fallback.py
  class OpenRouterFallbackTest (line 15) | class OpenRouterFallbackTest(BaseSimulatorTest):
    method test_name (line 19) | def test_name(self) -> str:
    method test_description (line 23) | def test_description(self) -> str:
    method run_test (line 26) | def run_test(self) -> bool:
  function main (line 227) | def main():

FILE: simulator_tests/test_openrouter_models.py
  class OpenRouterModelsTest (line 16) | class OpenRouterModelsTest(BaseSimulatorTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
  function main (line 240) | def main():

FILE: simulator_tests/test_per_tool_deduplication.py
  class PerToolDeduplicationTest (line 19) | class PerToolDeduplicationTest(ConversationBaseTest):
    method test_name (line 23) | def test_name(self) -> str:
    method test_description (line 27) | def test_description(self) -> str:
    method run_test (line 32) | def run_test(self) -> bool:

FILE: simulator_tests/test_planner_continuation_history.py
  class PlannerContinuationHistoryTest (line 18) | class PlannerContinuationHistoryTest(ConversationBaseTest):
    method test_name (line 22) | def test_name(self) -> str:
    method test_description (line 26) | def test_description(self) -> str:
    method run_test (line 29) | def run_test(self) -> bool:
    method _test_first_planning_session (line 60) | def _test_first_planning_session(self) -> bool:
    method _test_second_planning_session (line 135) | def _test_second_planning_session(self) -> bool:
    method _test_third_planning_session (line 204) | def _test_third_planning_session(self) -> bool:
    method _test_context_accumulation (line 278) | def _test_context_accumulation(self) -> bool:
    method call_mcp_tool (line 331) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_planner_continuation_id (line 344) | def _extract_planner_continuation_id(self, response_text: str) -> Opti...
    method _parse_planner_response (line 355) | def _parse_planner_response(self, response_text: str) -> dict:

FILE: simulator_tests/test_planner_validation.py
  class PlannerValidationTest (line 17) | class PlannerValidationTest(ConversationBaseTest):
    method test_name (line 21) | def test_name(self) -> str:
    method test_description (line 25) | def test_description(self) -> str:
    method run_test (line 28) | def run_test(self) -> bool:
    method _test_single_planning_session (line 67) | def _test_single_planning_session(self) -> bool:
    method _test_planning_with_continuation (line 213) | def _test_planning_with_continuation(self) -> bool:
    method _test_complex_plan_deep_thinking (line 275) | def _test_complex_plan_deep_thinking(self) -> bool:
    method _test_self_contained_completion (line 391) | def _test_self_contained_completion(self) -> bool:
    method _test_branching_and_revision (line 468) | def _test_branching_and_revision(self) -> bool:
    method _test_workflow_file_context (line 572) | def _test_workflow_file_context(self) -> bool:
    method call_mcp_tool (line 636) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_planner_continuation_id (line 649) | def _extract_planner_continuation_id(self, response_text: str) -> Opti...
    method _parse_planner_response (line 660) | def _parse_planner_response(self, response_text: str) -> dict:
    method _validate_step_response (line 671) | def _validate_step_response(

FILE: simulator_tests/test_planner_validation_old.py
  class PlannerValidationTest (line 19) | class PlannerValidationTest(ConversationBaseTest):
    method test_name (line 23) | def test_name(self) -> str:
    method test_description (line 27) | def test_description(self) -> str:
    method run_test (line 30) | def run_test(self) -> bool:
    method _test_single_planning_session (line 57) | def _test_single_planning_session(self) -> bool:
    method _test_plan_continuation (line 143) | def _test_plan_continuation(self) -> bool:
    method _test_branching_and_revision (line 215) | def _test_branching_and_revision(self) -> bool:
    method call_mcp_tool (line 316) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_planner_continuation_id (line 329) | def _extract_planner_continuation_id(self, response_text: str) -> Opti...
    method _parse_planner_response (line 340) | def _parse_planner_response(self, response_text: str) -> dict:
    method _validate_step_response (line 351) | def _validate_step_response(
    method _validate_final_step_response (line 404) | def _validate_final_step_response(self, response_data: dict, expected_...

FILE: simulator_tests/test_precommitworkflow_validation.py
  class PrecommitWorkflowValidationTest (line 16) | class PrecommitWorkflowValidationTest(ConversationBaseTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method _create_test_git_changes (line 69) | def _create_test_git_changes(self):
    method _test_single_validation_session (line 171) | def _test_single_validation_session(self) -> bool:
    method _test_validation_refocus_flow (line 266) | def _test_validation_refocus_flow(self) -> bool:
    method _test_complete_validation_with_analysis (line 348) | def _test_complete_validation_with_analysis(self) -> bool:
    method _test_certain_confidence (line 464) | def _test_certain_confidence(self) -> bool:
    method call_mcp_tool (line 527) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_precommit_continuation_id (line 540) | def _extract_precommit_continuation_id(self, response_text: str) -> Op...
    method _parse_precommit_response (line 551) | def _parse_precommit_response(self, response_text: str) -> dict:
    method _validate_step_response (line 562) | def _validate_step_response(
    method _test_context_aware_file_embedding (line 610) | def _test_context_aware_file_embedding(self) -> bool:
    method _test_multi_step_file_context (line 826) | def _test_multi_step_file_context(self) -> bool:

FILE: simulator_tests/test_prompt_size_limit_bug.py
  class PromptSizeLimitBugTest (line 25) | class PromptSizeLimitBugTest(ConversationBaseTest):
    method test_name (line 29) | def test_name(self) -> str:
    method test_description (line 33) | def test_description(self) -> str:
    method run_test (line 36) | def run_test(self) -> bool:
  function main (line 190) | def main():

FILE: simulator_tests/test_refactor_validation.py
  class RefactorValidationTest (line 15) | class RefactorValidationTest(ConversationBaseTest):
    method test_name (line 19) | def test_name(self) -> str:
    method test_description (line 23) | def test_description(self) -> str:
    method run_test (line 26) | def run_test(self) -> bool:
    method _create_refactoring_test_code (line 68) | def _create_refactoring_test_code(self):
    method _test_single_refactoring_session (line 279) | def _test_single_refactoring_session(self) -> bool:
    method _test_refactoring_refocus_flow (line 392) | def _test_refactoring_refocus_flow(self) -> bool:
    method _test_complete_refactoring_with_analysis (line 486) | def _test_complete_refactoring_with_analysis(self) -> bool:
    method _test_certain_confidence_complete_refactoring (line 629) | def _test_certain_confidence_complete_refactoring(self) -> bool:
    method _test_context_aware_refactoring_file_embedding (line 689) | def _test_context_aware_refactoring_file_embedding(self) -> bool:
    method _test_different_refactor_types (line 843) | def _test_different_refactor_types(self) -> bool:
    method call_mcp_tool (line 948) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_refactor_continuation_id (line 961) | def _extract_refactor_continuation_id(self, response_text: str) -> Opt...
    method _parse_refactor_response (line 972) | def _parse_refactor_response(self, response_text: str) -> dict:
    method _validate_refactoring_step_response (line 983) | def _validate_refactoring_step_response(

FILE: simulator_tests/test_secaudit_validation.py
  class SecauditValidationTest (line 15) | class SecauditValidationTest(ConversationBaseTest):
    method test_name (line 19) | def test_name(self) -> str:
    method test_description (line 23) | def test_description(self) -> str:
    method run_test (line 26) | def run_test(self) -> bool:
    method _create_test_code_for_audit (line 68) | def _create_test_code_for_audit(self):
    method _test_single_audit_session (line 214) | def _test_single_audit_session(self) -> bool:
    method _test_focused_security_audit (line 290) | def _test_focused_security_audit(self) -> bool:
    method _test_complete_audit_with_analysis (line 333) | def _test_complete_audit_with_analysis(self) -> bool:
    method _test_certain_confidence (line 438) | def _test_certain_confidence(self) -> bool:
    method _test_continuation_with_chat (line 488) | def _test_continuation_with_chat(self) -> bool:
    method _test_model_selection (line 549) | def _test_model_selection(self) -> bool:

FILE: simulator_tests/test_testgen_validation.py
  class TestGenValidationTest (line 16) | class TestGenValidationTest(ConversationBaseTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method _create_test_code_files (line 69) | def _create_test_code_files(self):
    method _test_single_test_generation_session (line 141) | def _test_single_test_generation_session(self) -> bool:
    method _test_generation_with_pattern_following (line 223) | def _test_generation_with_pattern_following(self) -> bool:
    method _test_complete_generation_with_analysis (line 276) | def _test_complete_generation_with_analysis(self) -> bool:
    method _test_certain_confidence (line 379) | def _test_certain_confidence(self) -> bool:
    method call_mcp_tool (line 433) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_testgen_continuation_id (line 446) | def _extract_testgen_continuation_id(self, response_text: str) -> Opti...
    method _parse_testgen_response (line 457) | def _parse_testgen_response(self, response_text: str) -> dict:
    method _validate_step_response (line 468) | def _validate_step_response(
    method _test_context_aware_file_embedding (line 516) | def _test_context_aware_file_embedding(self) -> bool:
    method _test_multi_step_test_planning (line 638) | def _test_multi_step_test_planning(self) -> bool:

FILE: simulator_tests/test_thinkdeep_validation.py
  class ThinkDeepWorkflowValidationTest (line 16) | class ThinkDeepWorkflowValidationTest(ConversationBaseTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
    method _create_thinking_context (line 69) | def _create_thinking_context(self):
    method _test_single_thinking_session (line 157) | def _test_single_thinking_session(self) -> bool:
    method _test_thinking_refocus_flow (line 246) | def _test_thinking_refocus_flow(self) -> bool:
    method _test_complete_thinking_with_analysis (line 331) | def _test_complete_thinking_with_analysis(self) -> bool:
    method _test_certain_confidence (line 451) | def _test_certain_confidence(self) -> bool:
    method call_mcp_tool (line 505) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optiona...
    method _extract_thinkdeep_continuation_id (line 518) | def _extract_thinkdeep_continuation_id(self, response_text: str) -> Op...
    method _parse_thinkdeep_response (line 529) | def _parse_thinkdeep_response(self, response_text: str) -> dict:
    method _validate_step_response (line 540) | def _validate_step_response(
    method _test_context_aware_file_embedding (line 588) | def _test_context_aware_file_embedding(self) -> bool:
    method _test_multi_step_file_context (line 734) | def _test_multi_step_file_context(self) -> bool:

FILE: simulator_tests/test_token_allocation_validation.py
  class TokenAllocationValidationTest (line 17) | class TokenAllocationValidationTest(ConversationBaseTest):
    method call_mcp_tool (line 20) | def call_mcp_tool(self, tool_name: str, params: dict) -> tuple:
    method test_name (line 26) | def test_name(self) -> str:
    method test_description (line 30) | def test_description(self) -> str:
    method run_test (line 33) | def run_test(self) -> bool:
  function main (line 338) | def main():

FILE: simulator_tests/test_vision_capability.py
  class VisionCapabilityTest (line 18) | class VisionCapabilityTest(BaseSimulatorTest):
    method test_name (line 22) | def test_name(self) -> str:
    method test_description (line 26) | def test_description(self) -> str:
    method get_triangle_png_path (line 29) | def get_triangle_png_path(self) -> str:
    method create_base64_triangle_data_url (line 42) | def create_base64_triangle_data_url(self) -> str:
    method run_test (line 53) | def run_test(self) -> bool:

FILE: simulator_tests/test_xai_models.py
  class XAIModelsTest (line 16) | class XAIModelsTest(BaseSimulatorTest):
    method test_name (line 20) | def test_name(self) -> str:
    method test_description (line 24) | def test_description(self) -> str:
    method run_test (line 27) | def run_test(self) -> bool:
  function main (line 210) | def main():

FILE: tests/conftest.py
  function custom_provider_factory (line 64) | def custom_provider_factory(api_key=None):
  function project_path (line 73) | def project_path(tmp_path):
  function _set_dummy_keys_if_missing (line 85) | def _set_dummy_keys_if_missing():
  function pytest_configure (line 93) | def pytest_configure(config):
  function pytest_collection_modifyitems (line 101) | def pytest_collection_modifyitems(session, config, items):
  function mock_provider_availability (line 109) | def mock_provider_availability(request, monkeypatch):
  function clear_model_restriction_env (line 179) | def clear_model_restriction_env(monkeypatch):
  function disable_force_env_override (line 195) | def disable_force_env_override(monkeypatch):

FILE: tests/http_transport_recorder.py
  class RecordingTransport (line 30) | class RecordingTransport(httpx.HTTPTransport):
    method __init__ (line 33) | def __init__(self, cassette_path: str, capture_content: bool = True, s...
    method handle_request (line 40) | def handle_request(self, request: httpx.Request) -> httpx.Response:
    method _record_interaction (line 98) | def _record_interaction(self, request_data: dict[str, Any], response_d...
    method _serialize_request (line 105) | def _serialize_request(self, request: httpx.Request) -> dict[str, Any]:
    method _serialize_response (line 135) | def _serialize_response(self, response: httpx.Response) -> dict[str, A...
    method _serialize_response_with_content (line 145) | def _serialize_response_with_content(self, response: httpx.Response, c...
    method _sanitize_request_content (line 184) | def _sanitize_request_content(self, content: str) -> Any:
    method _save_cassette (line 195) | def _save_cassette(self):
  class ReplayTransport (line 206) | class ReplayTransport(httpx.MockTransport):
    method __init__ (line 209) | def __init__(self, cassette_path: str):
    method _load_cassette (line 214) | def _load_cassette(self) -> list:
    method _handle_request (line 225) | def _handle_request(self, request: httpx.Request) -> httpx.Response:
    method _find_matching_interaction (line 281) | def _find_matching_interaction(self, request: httpx.Request) -> Option...
    method _get_request_signature (line 292) | def _get_request_signature(self, request: httpx.Request) -> str:
    method _is_o3_model_request (line 330) | def _is_o3_model_request(self, content_dict: dict) -> bool:
    method _extract_semantic_fields (line 335) | def _extract_semantic_fields(self, content_dict: dict) -> dict:
    method _get_saved_request_signature (line 374) | def _get_saved_request_signature(self, saved_request: dict[str, Any]) ...
  class TransportFactory (line 394) | class TransportFactory:
    method create_transport (line 398) | def create_transport(cassette_path: str) -> httpx.HTTPTransport:
    method should_record (line 412) | def should_record(cassette_path: str, api_key: Optional[str] = None) -...
    method should_replay (line 420) | def should_replay(cassette_path: str) -> bool:

FILE: tests/mock_helpers.py
  function create_mock_provider (line 8) | def create_mock_provider(model_name="gemini-2.5-flash", context_window=1...

FILE: tests/pii_sanitizer.py
  class PIIPattern (line 21) | class PIIPattern:
    method create (line 30) | def create(cls, name: str, pattern: str, replacement: str, description...
  class PIISanitizer (line 35) | class PIISanitizer:
    method __init__ (line 38) | def __init__(self, patterns: Optional[list[PIIPattern]] = None):
    method _add_default_patterns (line 47) | def _add_default_patterns(self):
    method add_pattern (line 143) | def add_pattern(self, pattern: PIIPattern):
    method sanitize_string (line 148) | def sanitize_string(self, text: str) -> str:
    method sanitize_headers (line 161) | def sanitize_headers(self, headers: dict[str, str]) -> dict[str, str]:
    method sanitize_value (line 182) | def sanitize_value(self, value: Any) -> Any:
    method sanitize_url (line 199) | def sanitize_url(self, url: str) -> str:
    method sanitize_request (line 229) | def sanitize_request(self, request_data: dict[str, Any]) -> dict[str, ...
    method sanitize_response (line 247) | def sanitize_response(self, response_data: dict[str, Any]) -> dict[str...

FILE: tests/sanitize_cassettes.py
  function sanitize_cassette (line 24) | def sanitize_cassette(cassette_path: Path, backup: bool = True) -> bool:
  function main (line 80) | def main():

FILE: tests/test_alias_target_restrictions.py
  class TestAliasTargetRestrictions (line 17) | class TestAliasTargetRestrictions:
    method test_openai_alias_target_validation_comprehensive (line 20) | def test_openai_alias_target_validation_comprehensive(self):
    method test_gemini_alias_target_validation_comprehensive (line 33) | def test_gemini_alias_target_validation_comprehensive(self):
    method test_restriction_policy_allows_alias_when_target_allowed (line 47) | def test_restriction_policy_allows_alias_when_target_allowed(self):
    method test_restriction_policy_alias_allows_canonical (line 66) | def test_restriction_policy_alias_allows_canonical(self):
    method test_restriction_policy_alias_allows_short_name (line 79) | def test_restriction_policy_alias_allows_short_name(self):
    method test_gemini_restriction_policy_allows_alias_when_target_allowed (line 91) | def test_gemini_restriction_policy_allows_alias_when_target_allowed(se...
    method test_gemini_restriction_policy_alias_allows_canonical (line 105) | def test_gemini_restriction_policy_alias_allows_canonical(self):
    method test_restriction_service_validation_includes_all_targets (line 116) | def test_restriction_service_validation_includes_all_targets(self):
    method test_both_alias_and_target_allowed_when_both_specified (line 137) | def test_both_alias_and_target_allowed_when_both_specified(self):
    method test_service_alias_allows_canonical_openai (line 157) | def test_service_alias_allows_canonical_openai(self):
    method test_service_alias_allows_canonical_gemini (line 169) | def test_service_alias_allows_canonical_gemini(self):
    method test_alias_target_policy_regression_prevention (line 180) | def test_alias_target_policy_regression_prevention(self):
    method test_no_duplicate_models_in_alias_aware_listing (line 217) | def test_no_duplicate_models_in_alias_aware_listing(self):
    method test_restriction_validation_uses_polymorphic_interface (line 232) | def test_restriction_validation_uses_polymorphic_interface(self):
    method test_complex_alias_chains_handled_correctly (line 259) | def test_complex_alias_chains_handled_correctly(self):
    method test_critical_regression_validation_sees_alias_targets (line 275) | def test_critical_regression_validation_sees_alias_targets(self):
    method test_critical_regression_prevents_policy_bypass (line 341) | def test_critical_regression_prevents_policy_bypass(self):

FILE: tests/test_auto_mode.py
  class TestAutoMode (line 13) | class TestAutoMode:
    method test_auto_mode_detection (line 16) | def test_auto_mode_detection(self):
    method test_model_capabilities_descriptions (line 46) | def test_model_capabilities_descriptions(self):
    method test_tool_schema_in_auto_mode (line 76) | def test_tool_schema_in_auto_mode(self):
    method test_tool_schema_in_normal_mode (line 108) | def test_tool_schema_in_normal_mode(self):
    method test_auto_mode_requires_model_parameter (line 141) | async def test_auto_mode_requires_model_parameter(self, tmp_path):
    method test_unavailable_model_error_message (line 175) | async def test_unavailable_model_error_message(self):
    method test_model_field_schema_generation (line 270) | def test_model_field_schema_generation(self):

FILE: tests/test_auto_mode_comprehensive.py
  class TestAutoModeComprehensive (line 23) | class TestAutoModeComprehensive:
    method setup_method (line 26) | def setup_method(self):
    method teardown_method (line 41) | def teardown_method(self):
    method test_auto_mode_model_selection_by_provider (line 146) | def test_auto_mode_model_selection_by_provider(self, provider_config, ...
    method test_tool_model_categories (line 198) | def test_tool_model_categories(self, tool_class, expected_category):
    method test_auto_mode_with_gemini_only_uses_correct_models (line 204) | async def test_auto_mode_with_gemini_only_uses_correct_models(self, tm...
    method test_auto_mode_schema_includes_all_available_models (line 241) | def test_auto_mode_schema_includes_all_available_models(self):
    method test_auto_mode_schema_with_all_providers (line 299) | def test_auto_mode_schema_with_all_providers(self):
    method test_auto_mode_model_parameter_required_error (line 348) | async def test_auto_mode_model_parameter_required_error(self, tmp_path):
    method test_model_availability_with_restrictions (line 399) | def test_model_availability_with_restrictions(self):
    method test_openrouter_fallback_when_no_native_apis (line 447) | def test_openrouter_fallback_when_no_native_apis(self):
    method test_actual_model_name_resolution_in_auto_mode (line 499) | async def test_actual_model_name_resolution_in_auto_mode(self, tmp_path):

FILE: tests/test_auto_mode_custom_provider_only.py
  class TestAutoModeCustomProviderOnly (line 14) | class TestAutoModeCustomProviderOnly:
    method setup_method (line 17) | def setup_method(self):
    method teardown_method (line 40) | def teardown_method(self):
    method test_reproduce_auto_mode_custom_provider_only_issue (line 62) | def test_reproduce_auto_mode_custom_provider_only_issue(self):
    method test_custom_provider_models_available_via_registry (line 101) | def test_custom_provider_models_available_via_registry(self):
    method test_custom_provider_validate_model_name (line 140) | def test_custom_provider_validate_model_name(self):
    method test_auto_mode_fallback_with_custom_only_should_work (line 168) | def test_auto_mode_fallback_with_custom_only_should_work(self):

FILE: tests/test_auto_mode_model_listing.py
  function _extract_available_models (line 20) | def _extract_available_models(message: str) -> list[str]:
  function reset_registry (line 40) | def reset_registry():
  function _register_core_providers (line 51) | def _register_core_providers(*, include_xai: bool = False):
  function test_error_listing_respects_env_restrictions (line 60) | def test_error_listing_respects_env_restrictions(monkeypatch, reset_regi...
  function test_error_listing_without_restrictions_shows_full_catalog (line 146) | def test_error_listing_without_restrictions_shows_full_catalog(monkeypat...

FILE: tests/test_auto_mode_provider_selection.py
  class TestAutoModeProviderSelection (line 13) | class TestAutoModeProviderSelection:
    method setup_method (line 16) | def setup_method(self):
    method teardown_method (line 28) | def teardown_method(self):
    method test_gemini_only_fallback_selection (line 35) | def test_gemini_only_fallback_selection(self):
    method test_openai_only_fallback_selection (line 74) | def test_openai_only_fallback_selection(self):
    method test_both_gemini_and_openai_priority (line 113) | def test_both_gemini_and_openai_priority(self):
    method test_xai_only_fallback_selection (line 155) | def test_xai_only_fallback_selection(self):
    method test_available_models_respects_restrictions (line 194) | def test_available_models_respects_restrictions(self):
    method test_model_validation_across_providers (line 243) | def test_model_validation_across_providers(self):
    method test_alias_resolution_before_api_calls (line 294) | def test_alias_resolution_before_api_calls(self):

FILE: tests/test_auto_model_planner_fix.py
  class TestAutoModelPlannerFix (line 18) | class TestAutoModelPlannerFix:
    method test_planner_requires_model_false (line 21) | def test_planner_requires_model_false(self):
    method test_chat_requires_model_true (line 26) | def test_chat_requires_model_true(self):
    method test_base_tool_requires_model_default (line 31) | def test_base_tool_requires_model_default(self):
    method test_auto_model_error_before_fix_simulation (line 61) | def test_auto_model_error_before_fix_simulation(self, mock_get_provider):
    method test_planner_execution_bypasses_model_resolution (line 79) | async def test_planner_execution_bypasses_model_resolution(self):
    method test_server_model_resolution_logic (line 107) | def test_server_model_resolution_logic(self):
    method test_provider_registry_auto_handling (line 135) | def test_provider_registry_auto_handling(self):
    method test_end_to_end_planner_with_auto_mode (line 148) | async def test_end_to_end_planner_with_auto_mode(self):
    method test_other_tools_still_require_models (line 196) | def test_other_tools_still_require_models(self):

FILE: tests/test_azure_openai_provider.py
  class _DummyResponse (line 15) | class _DummyResponse:
    method __init__ (line 16) | def __init__(self):
  function dummy_azure_client (line 34) | def dummy_azure_client(monkeypatch):
  function test_generate_content_uses_deployment_mapping (line 56) | def test_generate_content_uses_deployment_mapping(dummy_azure_client):
  function test_generate_content_accepts_deployment_alias (line 71) | def test_generate_content_accepts_deployment_alias(dummy_azure_client):
  function test_client_initialization_uses_endpoint_and_version (line 85) | def test_client_initialization_uses_endpoint_and_version(dummy_azure_cli...
  function test_deployment_overrides_capabilities (line 99) | def test_deployment_overrides_capabilities(dummy_azure_client):
  function test_registry_configuration_merges_capabilities (line 120) | def test_registry_configuration_merges_capabilities(dummy_azure_client, ...

FILE: tests/test_buggy_behavior_prevention.py
  class TestBuggyBehaviorPrevention (line 20) | class TestBuggyBehaviorPrevention:
    method test_alias_listing_includes_targets_for_restriction_validation (line 23) | def test_alias_listing_includes_targets_for_restriction_validation(self):
    method test_target_models_are_recognized_during_validation (line 64) | def test_target_models_are_recognized_during_validation(self):
    method test_policy_enforcement_remains_comprehensive (line 95) | def test_policy_enforcement_remains_comprehensive(self):
    method test_alias_aware_listing_extends_canonical_view (line 132) | def test_alias_aware_listing_extends_canonical_view(self):
    method test_restriction_validation_uses_alias_aware_variant (line 155) | def test_restriction_validation_uses_alias_aware_variant(self):
    method test_alias_listing_covers_targets_for_all_providers (line 208) | def test_alias_listing_covers_targets_for_all_providers(self):
    method test_validation_correctly_identifies_invalid_models (line 228) | def test_validation_correctly_identifies_invalid_models(self):
    method test_custom_provider_alias_listing (line 258) | def test_custom_provider_alias_listing(self):
    method test_openrouter_provider_alias_listing (line 274) | def test_openrouter_provider_alias_listing(self):

FILE: tests/test_cassette_semantic_matching.py
  class TestCassetteSemanticMatching (line 16) | class TestCassetteSemanticMatching:
    method dummy_cassette (line 20) | def dummy_cassette(self, tmp_path):
    method test_o3_model_semantic_matching (line 26) | def test_o3_model_semantic_matching(self, dummy_cassette):
    method test_non_o3_model_exact_matching (line 80) | def test_non_o3_model_exact_matching(self, dummy_cassette):
    method test_o3_mini_semantic_matching (line 92) | def test_o3_mini_semantic_matching(self, dummy_cassette):
    method test_o3_without_request_markers (line 114) | def test_o3_without_request_markers(self, dummy_cassette):

FILE: tests/test_challenge.py
  class TestChallengeTool (line 18) | class TestChallengeTool:
    method setup_method (line 21) | def setup_method(self):
    method test_tool_metadata (line 25) | def test_tool_metadata(self):
    method test_requires_model (line 33) | def test_requires_model(self):
    method test_schema_structure (line 37) | def test_schema_structure(self):
    method test_request_model_validation (line 60) | def test_request_model_validation(self):
    method test_required_fields (line 73) | def test_required_fields(self):
    method test_execute_success (line 82) | async def test_execute_success(self):
    method test_execute_error_handling (line 110) | async def test_execute_error_handling(self):
    method test_wrap_prompt_for_challenge (line 121) | def test_wrap_prompt_for_challenge(self):
    method test_multiple_prompts (line 133) | def test_multiple_prompts(self):
    method test_tool_fields (line 151) | def test_tool_fields(self):
    method test_required_fields_list (line 160) | def test_required_fields_list(self):
    method test_not_used_methods (line 166) | async def test_not_used_methods(self):
    method test_special_characters_in_prompt (line 177) | def test_special_characters_in_prompt(self):
    method test_unicode_support (line 187) | async def test_unicode_support(self):

FILE: tests/test_chat_codegen_integration.py
  function test_chat_codegen_saves_file (line 35) | async def test_chat_codegen_saves_file(monkeypatch, tmp_path):

FILE: tests/test_chat_cross_model_continuation.py
  function _extract_number (line 44) | def _extract_number(text: str) -> str:
  function test_chat_cross_model_continuation (line 58) | async def test_chat_cross_model_continuation(monkeypatch, tmp_path):

FILE: tests/test_chat_openai_integration.py
  function test_chat_auto_mode_with_openai (line 26) | async def test_chat_auto_mode_with_openai(monkeypatch, tmp_path):
  function test_chat_openai_continuation (line 92) | async def test_chat_openai_continuation(monkeypatch, tmp_path):

FILE: tests/test_chat_simple.py
  class TestChatTool (line 18) | class TestChatTool:
    method setup_method (line 21) | def setup_method(self):
    method test_tool_metadata (line 25) | def test_tool_metadata(self):
    method test_schema_structure (line 33) | def test_schema_structure(self):
    method test_request_model_validation (line 53) | def test_request_model_validation(self):
    method test_required_fields (line 73) | def test_required_fields(self):
    method test_model_availability (line 81) | def test_model_availability(self):
    method test_model_field_schema (line 87) | def test_model_field_schema(self):
    method test_prompt_preparation (line 104) | async def test_prompt_preparation(self):
    method test_response_formatting (line 124) | def test_response_formatting(self):
    method test_format_response_multiple_generated_code_blocks (line 135) | def test_format_response_multiple_generated_code_blocks(self, tmp_path):
    method test_format_response_single_generated_code_block (line 160) | def test_format_response_single_generated_code_block(self, tmp_path):
    method test_format_response_ignores_unclosed_generated_code (line 183) | def test_format_response_ignores_unclosed_generated_code(self, tmp_path):
    method test_format_response_ignores_orphaned_closing_tag (line 198) | def test_format_response_ignores_orphaned_closing_tag(self, tmp_path):
    method test_format_response_preserves_narrative_after_generated_code (line 213) | def test_format_response_preserves_narrative_after_generated_code(self...
    method test_tool_name (line 234) | def test_tool_name(self):
    method test_websearch_guidance (line 238) | def test_websearch_guidance(self):
    method test_convenience_methods (line 247) | def test_convenience_methods(self):
  class TestChatRequestModel (line 262) | class TestChatRequestModel:
    method test_field_descriptions (line 265) | def test_field_descriptions(self):
    method test_working_directory_absolute_path_description_matches_behavior (line 277) | def test_working_directory_absolute_path_description_matches_behavior(...
    method test_working_directory_absolute_path_must_exist (line 285) | async def test_working_directory_absolute_path_must_exist(self, tmp_pa...
    method test_default_values (line 304) | def test_default_values(self):
    method test_inheritance (line 312) | def test_inheritance(self):

FILE: tests/test_clink_claude_agent.py
  class DummyProcess (line 13) | class DummyProcess:
    method __init__ (line 14) | def __init__(self, *, stdout: bytes = b"", stderr: bytes = b"", return...
    method communicate (line 20) | async def communicate(self, input_data):
  function claude_agent (line 26) | def claude_agent():
  function _run_agent_with_process (line 45) | async def _run_agent_with_process(monkeypatch, agent, role, process, *, ...
  function test_claude_agent_injects_system_prompt (line 65) | async def test_claude_agent_injects_system_prompt(monkeypatch, claude_ag...
  function test_claude_agent_recovers_error_payload (line 86) | async def test_claude_agent_recovers_error_payload(monkeypatch, claude_a...
  function test_claude_agent_propagates_unparseable_output (line 106) | async def test_claude_agent_propagates_unparseable_output(monkeypatch, c...

FILE: tests/test_clink_claude_parser.py
  function _build_success_payload (line 11) | def _build_success_payload() -> str:
  function test_claude_parser_extracts_result_and_metadata (line 20) | def test_claude_parser_extracts_result_and_metadata():
  function test_claude_parser_falls_back_to_message (line 32) | def test_claude_parser_falls_back_to_message():
  function test_claude_parser_requires_output (line 43) | def test_claude_parser_requires_output():
  function test_claude_parser_handles_array_payload_with_result_event (line 50) | def test_claude_parser_handles_array_payload_with_result_event():

FILE: tests/test_clink_codex_agent.py
  class DummyProcess (line 12) | class DummyProcess:
    method __init__ (line 13) | def __init__(self, *, stdout: bytes = b"", stderr: bytes = b"", return...
    method communicate (line 18) | async def communicate(self, _input):
  function codex_agent (line 23) | def codex_agent():
  function _run_agent_with_process (line 41) | async def _run_agent_with_process(monkeypatch, agent, role, process):
  function test_codex_agent_recovers_jsonl (line 54) | async def test_codex_agent_recovers_jsonl(monkeypatch, codex_agent):
  function test_codex_agent_propagates_invalid_json (line 69) | async def test_codex_agent_propagates_invalid_json(monkeypatch, codex_ag...

FILE: tests/test_clink_gemini_agent.py
  class DummyProcess (line 12) | class DummyProcess:
    method __init__ (line 13) | def __init__(self, *, stdout: bytes = b"", stderr: bytes = b"", return...
    method communicate (line 18) | async def communicate(self, _input):
  function gemini_agent (line 23) | def gemini_agent():
  function _run_agent_with_process (line 41) | async def _run_agent_with_process(monkeypatch, agent, role, process):
  function test_gemini_agent_recovers_tool_error (line 54) | async def test_gemini_agent_recovers_tool_error(monkeypatch, gemini_agent):
  function test_gemini_agent_propagates_unrecoverable_error (line 75) | async def test_gemini_agent_propagates_unrecoverable_error(monkeypatch, ...

FILE: tests/test_clink_gemini_parser.py
  function _build_rate_limit_stdout (line 8) | def _build_rate_limit_stdout() -> str:
  function test_gemini_parser_handles_rate_limit_empty_response (line 30) | def test_gemini_parser_handles_rate_limit_empty_response():
  function test_gemini_parser_still_errors_when_no_fallback_available (line 43) | def test_gemini_parser_still_errors_when_no_fallback_available():

FILE: tests/test_clink_integration.py
  function test_clink_gemini_single_digit_sum (line 12) | async def test_clink_gemini_single_digit_sum():
  function test_clink_claude_single_digit_sum (line 49) | async def test_clink_claude_single_digit_sum():

FILE: tests/test_clink_parsers.py
  function test_codex_parser_success (line 7) | def test_codex_parser_success():
  function test_codex_parser_requires_agent_message (line 18) | def test_codex_parser_requires_agent_message():

FILE: tests/test_clink_tool.py
  function test_clink_tool_execute (line 12) | async def test_clink_tool_execute(monkeypatch):
  function test_registry_lists_roles (line 55) | def test_registry_lists_roles():
  function test_clink_tool_defaults_to_first_cli (line 73) | async def test_clink_tool_defaults_to_first_cli(monkeypatch):
  function test_clink_tool_truncates_large_output (line 108) | async def test_clink_tool_truncates_large_output(monkeypatch):
  function test_clink_tool_truncates_without_summary (line 150) | async def test_clink_tool_truncates_without_summary(monkeypatch):

FILE: tests/test_collaboration.py
  class TestDynamicContextRequests (line 17) | class TestDynamicContextRequests:
    method analyze_tool (line 21) | def analyze_tool(self):
    method debug_tool (line 25) | def debug_tool(self):
    method test_clarification_request_parsing (line 30) | async def test_clarification_request_parsing(self, mock_get_provider, ...
    method test_normal_response_not_parsed_as_clarification (line 86) | async def test_normal_response_not_parsed_as_clarification(
    method test_malformed_clarification_request_treated_as_normal (line 119) | async def test_malformed_clarification_request_treated_as_normal(self,...
    method test_clarification_with_suggested_action (line 159) | async def test_clarification_with_suggested_action(self, mock_get_prov...
    method test_tool_output_model_serialization (line 276) | def test_tool_output_model_serialization(self):
    method test_clarification_request_model (line 293) | def test_clarification_request_model(self):
    method test_error_response_format (line 307) | async def test_error_response_format(self, mock_get_provider, analyze_...
  class TestCollaborationWorkflow (line 344) | class TestCollaborationWorkflow:
    method teardown_method (line 347) | def teardown_method(self):
    method test_dependency_analysis_triggers_clarification (line 357) | async def test_dependency_analysis_triggers_clarification(self, mock_e...
    method test_multi_step_collaboration (line 423) | async def test_multi_step_collaboration(self, mock_expert_analysis, mo...

FILE: tests/test_config.py
  class TestConfig (line 16) | class TestConfig:
    method test_version_info (line 19) | def test_version_info(self):
    method test_model_config (line 31) | def test_model_config(self):
    method test_temperature_defaults (line 36) | def test_temperature_defaults(self):

FILE: tests/test_consensus.py
  class TestConsensusTool (line 13) | class TestConsensusTool:
    method test_tool_metadata (line 16) | def test_tool_metadata(self):
    method test_request_validation_step1 (line 26) | def test_request_validation_step1(self):
    method test_request_validation_missing_models_step1 (line 45) | def test_request_validation_missing_models_step1(self):
    method test_request_validation_later_steps (line 57) | def test_request_validation_later_steps(self):
    method test_request_validation_duplicate_model_stance (line 74) | def test_request_validation_duplicate_model_stance(self):
    method test_input_schema_generation (line 108) | def test_input_schema_generation(self):
    method test_get_required_actions (line 152) | def test_get_required_actions(self):
    method test_prepare_step_data (line 170) | def test_prepare_step_data(self):
    method test_stance_enhanced_prompt_generation (line 197) | def test_stance_enhanced_prompt_generation(self):
    method test_should_call_expert_analysis (line 217) | def test_should_call_expert_analysis(self):
    method test_execute_workflow_step1_basic (line 223) | def test_execute_workflow_step1_basic(self):
    method test_execute_workflow_total_steps_calculation (line 243) | def test_execute_workflow_total_steps_calculation(self):
    method test_consult_model_basic_structure (line 261) | def test_consult_model_basic_structure(self):
    method test_model_configuration_validation (line 274) | def test_model_configuration_validation(self):
    method test_handle_work_continuation (line 294) | def test_handle_work_continuation(self):
    method test_customize_workflow_response (line 317) | def test_customize_workflow_response(self):
    method test_consensus_with_relevant_files_model_context_fix (line 334) | async def test_consensus_with_relevant_files_model_context_fix(self):

FILE: tests/test_consensus_integration.py
  function test_consensus_multi_model_consultations (line 36) | async def test_consensus_multi_model_consultations(monkeypatch, openai_m...
  function test_consensus_auto_mode_with_openrouter_and_gemini (line 201) | async def test_consensus_auto_mode_with_openrouter_and_gemini(monkeypatch):

FILE: tests/test_consensus_schema.py
  function test_consensus_models_field_includes_available_models (line 8) | def test_consensus_models_field_includes_available_models(monkeypatch):

FILE: tests/test_conversation_continuation_integration.py
  function test_first_response_persisted_in_conversation_history (line 8) | def test_first_response_persisted_in_conversation_history(tmp_path):

FILE: tests/test_conversation_field_mapping.py
  function test_conversation_history_field_mapping (line 16) | async def test_conversation_history_field_mapping():
  function test_unknown_tool_defaults_to_prompt (line 100) | async def test_unknown_tool_defaults_to_prompt():
  function test_tool_parameter_standardization (line 142) | async def test_tool_parameter_standardization():

FILE: tests/test_conversation_file_features.py
  class TestConversationFileList (line 24) | class TestConversationFileList:
    method test_get_conversation_file_list_basic (line 27) | def test_get_conversation_file_list_basic(self):
    method test_get_conversation_file_list_deduplication (line 61) | def test_get_conversation_file_list_deduplication(self):
  class TestFileInclusionPlanning (line 98) | class TestFileInclusionPlanning:
    method test_plan_file_inclusion_within_budget (line 101) | def test_plan_file_inclusion_within_budget(self, project_path):
    method test_plan_file_inclusion_exceeds_budget (line 121) | def test_plan_file_inclusion_exceeds_budget(self, project_path):
    method test_plan_file_inclusion_empty_list (line 141) | def test_plan_file_inclusion_empty_list(self):
    method test_plan_file_inclusion_nonexistent_files (line 149) | def test_plan_file_inclusion_nonexistent_files(self):
  class TestConversationHistoryBuilding (line 160) | class TestConversationHistoryBuilding:
    method test_build_conversation_history_with_file_content (line 164) | def test_build_conversation_history_with_file_content(self, project_pa...
    method test_build_conversation_history_file_deduplication (line 213) | def test_build_conversation_history_file_deduplication(self, project_p...
    method test_build_conversation_history_empty_turns (line 260) | def test_build_conversation_history_empty_turns(self):
  class TestCrossToolFileContext (line 277) | class TestCrossToolFileContext:
    method test_cross_tool_file_context_preservation (line 281) | def test_cross_tool_file_context_preservation(self, project_path):
  class TestLargeConversations (line 352) | class TestLargeConversations:
    method test_large_conversation_with_many_files (line 356) | def test_large_conversation_with_many_files(self, project_path):
  class TestSmallAndNewConversations (line 418) | class TestSmallAndNewConversations:
    method test_empty_conversation (line 421) | def test_empty_conversation(self):
    method test_single_turn_conversation (line 438) | def test_single_turn_conversation(self, project_path):
  class TestFailureScenarios (line 477) | class TestFailureScenarios:
    method test_file_list_with_missing_files (line 480) | def test_file_list_with_missing_files(self):
    method test_conversation_with_unreadable_files (line 507) | def test_conversation_with_unreadable_files(self, project_path):

FILE: tests/test_conversation_memory.py
  class TestConversationMemory (line 26) | class TestConversationMemory:
    method test_create_thread (line 30) | def test_create_thread(self, mock_storage):
    method test_get_thread_valid (line 47) | def test_get_thread_valid(self, mock_storage):
    method test_get_thread_invalid_uuid (line 73) | def test_get_thread_invalid_uuid(self, mock_storage):
    method test_get_thread_not_found (line 79) | def test_get_thread_not_found(self, mock_storage):
    method test_add_turn_success (line 89) | def test_add_turn_success(self, mock_storage):
    method test_add_turn_max_limit (line 115) | def test_add_turn_max_limit(self, mock_storage):
    method test_build_conversation_history (line 142) | def test_build_conversation_history(self, project_path):
    method test_build_conversation_history_empty (line 221) | def test_build_conversation_history_empty(self):
  class TestConversationFlow (line 239) | class TestConversationFlow:
    method test_complete_conversation_cycle (line 243) | def test_complete_conversation_cycle(self, mock_storage):
    method test_invalid_continuation_id_error (line 347) | def test_invalid_continuation_id_error(self, mock_storage):
    method test_dynamic_max_turns_configuration (line 371) | def test_dynamic_max_turns_configuration(self):
    method test_follow_up_instructions_dynamic_behavior (line 404) | def test_follow_up_instructions_dynamic_behavior(self):
    method test_follow_up_instructions_defaults_to_config (line 438) | def test_follow_up_instructions_defaults_to_config(self):
    method test_complete_conversation_with_dynamic_turns (line 445) | def test_complete_conversation_with_dynamic_turns(self, mock_storage):
    method test_conversation_with_files_and_context_preservation (line 502) | def test_conversation_with_files_and_context_preservation(self, mock_s...
    method test_stateless_request_isolation (line 667) | def test_stateless_request_isolation(self, mock_storage):
    method test_token_limit_optimization_in_conversation_history (line 712) | def test_token_limit_optimization_in_conversation_history(self):

FILE: tests/test_conversation_missing_files.py
  class TestConversationMissingFiles (line 17) | class TestConversationMissingFiles:
    method test_build_conversation_history_handles_missing_files (line 20) | def test_build_conversation_history_handles_missing_files(self):

FILE: tests/test_custom_openai_temperature_fix.py
  class TestCustomOpenAITemperatureParameterFix (line 17) | class TestCustomOpenAITemperatureParameterFix:
    method _create_test_config (line 20) | def _create_test_config(self, models_config: list[dict]) -> str:
    method test_custom_openai_models_exclude_temperature_from_api_call (line 31) | def test_custom_openai_models_exclude_temperature_from_api_call(self, ...
    method test_custom_openai_models_include_temperature_when_supported (line 139) | def test_custom_openai_models_include_temperature_when_supported(self,...
    method test_custom_openai_model_validation (line 217) | def test_custom_openai_model_validation(self, mock_restriction_service):
    method test_fallback_to_builtin_models_when_registry_fails (line 263) | def test_fallback_to_builtin_models_when_registry_fails(self, mock_res...

FILE: tests/test_custom_provider.py
  class TestCustomProvider (line 13) | class TestCustomProvider:
    method test_provider_initialization_with_params (line 16) | def test_provider_initialization_with_params(self):
    method test_provider_initialization_with_env_vars (line 24) | def test_provider_initialization_with_env_vars(self):
    method test_provider_initialization_missing_url (line 32) | def test_provider_initialization_missing_url(self):
    method test_validate_model_names_always_true (line 38) | def test_validate_model_names_always_true(self):
    method test_get_capabilities_from_registry (line 50) | def test_get_capabilities_from_registry(self):
    method test_get_capabilities_generic_fallback (line 77) | def test_get_capabilities_generic_fallback(self):
    method test_model_alias_resolution (line 85) | def test_model_alias_resolution(self):
    method test_no_thinking_mode_support (line 98) | def test_no_thinking_mode_support(self):
    method test_generate_content_with_alias_resolution (line 110) | def test_generate_content_with_alias_resolution(self, mock_generate):
  class TestCustomProviderRegistration (line 132) | class TestCustomProviderRegistration:
    method setup_method (line 135) | def setup_method(self):
    method teardown_method (line 140) | def teardown_method(self):
    method test_custom_provider_factory_registration (line 145) | def test_custom_provider_factory_registration(self):
    method test_dual_provider_setup (line 163) | def test_dual_provider_setup(self):
    method test_provider_priority_selection (line 197) | def test_provider_priority_selection(self):
  class TestConfigureProvidersFunction (line 223) | class TestConfigureProvidersFunction:
    method setup_method (line 226) | def setup_method(self):
    method teardown_method (line 235) | def teardown_method(self):
    method test_configure_providers_custom_only (line 243) | def test_configure_providers_custom_only(self):
    method test_configure_providers_openrouter_only (line 266) | def test_configure_providers_openrouter_only(self):
    method test_configure_providers_dual_setup (line 288) | def test_configure_providers_dual_setup(self):
    method test_configure_providers_no_valid_keys (line 311) | def test_configure_providers_no_valid_keys(self):

FILE: tests/test_debug.py
  class TestDebugTool (line 9) | class TestDebugTool:
    method test_tool_metadata (line 12) | def test_tool_metadata(self):
    method test_request_validation (line 22) | def test_request_validation(self):
    method test_input_schema_generation (line 42) | def test_input_schema_generation(self):
    method test_model_category_for_debugging (line 61) | def test_model_category_for_debugging(self):
    method test_relevant_context_handling (line 66) | def test_relevant_context_handling(self):

FILE: tests/test_deploy_scripts.py
  class TestDeploymentScripts (line 12) | class TestDeploymentScripts:
    method setup (line 16) | def setup(self):
    method test_deployment_scripts_exist (line 21) | def test_deployment_scripts_exist(self):
    method test_bash_scripts_executable (line 29) | def test_bash_scripts_executable(self):
    method test_powershell_scripts_format (line 40) | def test_powershell_scripts_format(self):
    method test_deploy_script_docker_commands (line 62) | def test_deploy_script_docker_commands(self, mock_run):
    method test_build_script_functionality (line 75) | def test_build_script_functionality(self):
    method test_deploy_script_health_check_integration (line 87) | def test_deploy_script_health_check_integration(self):
    method test_script_error_handling (line 104) | def test_script_error_handling(self):
    method test_docker_compose_commands (line 127) | def test_docker_compose_commands(self, mock_run):
    method test_script_parameter_handling (line 143) | def test_script_parameter_handling(self):
    method test_environment_preparation (line 157) | def test_environment_preparation(self):
  class TestHealthCheckScript (line 175) | class TestHealthCheckScript:
    method setup (line 179) | def setup(self):
    method test_healthcheck_script_syntax (line 184) | def test_healthcheck_script_syntax(self):
    method test_healthcheck_functions_exist (line 197) | def test_healthcheck_functions_exist(self):
    method test_healthcheck_process_check (line 211) | def test_healthcheck_process_check(self, mock_run):
    method test_healthcheck_import_validation (line 222) | def test_healthcheck_import_validation(self):
    method test_healthcheck_exit_codes (line 233) | def test_healthcheck_exit_codes(self):
  class TestScriptIntegration (line 253) | class TestScriptIntegration:
    method test_scripts_work_with_compose_file (line 256) | def test_scripts_work_with_compose_file(self):
    method test_cross_platform_compatibility (line 277) | def test_cross_platform_compatibility(self):
    method test_script_logging_integration (line 295) | def test_script_logging_integration(self):

FILE: tests/test_dial_provider.py
  class TestDIALProvider (line 12) | class TestDIALProvider:
    method test_initialization_with_host (line 16) | def test_initialization_with_host(self):
    method test_initialization_default_host (line 25) | def test_initialization_default_host(self):
    method test_initialization_host_normalization (line 32) | def test_initialization_host_normalization(self):
    method test_model_validation (line 44) | def test_model_validation(self):
    method test_resolve_model_name (line 59) | def test_resolve_model_name(self):
    method test_get_capabilities (line 80) | def test_get_capabilities(self):
    method test_get_capabilities_invalid_model (line 120) | def test_get_capabilities_invalid_model(self):
    method test_get_capabilities_restricted_model (line 128) | def test_get_capabilities_restricted_model(self, mock_get_restriction):
    method test_supports_vision (line 142) | def test_supports_vision(self):
    method test_generate_content_with_alias (line 155) | def test_generate_content_with_alias(self, mock_openai_class):
    method test_provider_type (line 190) | def test_provider_type(self):
    method test_friendly_name (line 195) | def test_friendly_name(self):
    method test_configurable_api_version (line 201) | def test_configurable_api_version(self):
    method test_default_api_version (line 207) | def test_default_api_version(self):
    method test_allowed_models_restriction (line 223) | def test_allowed_models_restriction(self):
    method test_close_method (line 240) | def test_close_method(self, mock_openai_class, mock_httpx_client_class):

FILE: tests/test_directory_expansion_tracking.py
  class TestDirectoryExpansionTracking (line 21) | class TestDirectoryExpansionTracking:
    method tool (line 25) | def tool(self):
    method temp_directory_with_files (line 29) | def temp_directory_with_files(self, project_path):
    method test_directory_expansion_tracked_in_conversation_memory (line 78) | async def test_directory_expansion_tracked_in_conversation_memory(
    method test_conversation_continuation_with_directory_files (line 127) | async def test_conversation_continuation_with_directory_files(
    method test_get_conversation_embedded_files_with_expanded_files (line 202) | def test_get_conversation_embedded_files_with_expanded_files(self, moc...
    method test_file_filtering_with_mixed_files_and_directories (line 243) | def test_file_filtering_with_mixed_files_and_directories(self, mock_st...
    method test_actually_processed_files_stored_correctly (line 290) | async def test_actually_processed_files_stored_correctly(self, mock_ge...

FILE: tests/test_disabled_tools.py
  class MockTool (line 17) | class MockTool:
    method __init__ (line 18) | def __init__(self, name):
  class TestDisabledTools (line 22) | class TestDisabledTools:
    method test_parse_disabled_tools_empty (line 25) | def test_parse_disabled_tools_empty(self):
    method test_parse_disabled_tools_not_set (line 30) | def test_parse_disabled_tools_not_set(self):
    method test_parse_disabled_tools_single (line 38) | def test_parse_disabled_tools_single(self):
    method test_parse_disabled_tools_multiple (line 43) | def test_parse_disabled_tools_multiple(self):
    method test_parse_disabled_tools_extra_spaces (line 48) | def test_parse_disabled_tools_extra_spaces(self):
    method test_parse_disabled_tools_duplicates (line 53) | def test_parse_disabled_tools_duplicates(self):
    method test_tool_filtering_logic (line 58) | def test_tool_filtering_logic(self):
    method test_unknown_tools_warning (line 95) | def test_unknown_tools_warning(self, caplog):
    method test_essential_tools_warning (line 110) | def test_essential_tools_warning(self, caplog):
    method test_parse_disabled_tools_parametrized (line 137) | def test_parse_disabled_tools_parametrized(self, env_value, expected):

FILE: tests/test_docker_claude_desktop_integration.py
  class TestDockerClaudeDesktopIntegration (line 13) | class TestDockerClaudeDesktopIntegration:
    method setup (line 17) | def setup(self):
    method test_mcp_config_docker_run_format (line 21) | def test_mcp_config_docker_run_format(self):
    method test_mcp_config_docker_compose_format (line 52) | def test_mcp_config_docker_compose_format(self):
    method test_mcp_config_environment_variables (line 72) | def test_mcp_config_environment_variables(self):
    method test_windows_path_format (line 102) | def test_windows_path_format(self):
    method test_mcp_config_validation (line 131) | def test_mcp_config_validation(self):
    method test_mcp_stdio_communication (line 143) | def test_mcp_stdio_communication(self):
    method test_docker_image_reference (line 168) | def test_docker_image_reference(self):
    method temp_mcp_config (line 184) | def temp_mcp_config(self):
    method test_mcp_config_file_parsing (line 202) | def test_mcp_config_file_parsing(self, temp_mcp_config):
    method test_environment_file_integration (line 211) | def test_environment_file_integration(self):
    method test_docker_volume_mount_paths (line 231) | def test_docker_volume_mount_paths(self):
  class TestDockerMCPErrorHandling (line 249) | class TestDockerMCPErrorHandling:
    method test_missing_docker_image_handling (line 252) | def test_missing_docker_image_handling(self):
    method test_invalid_env_file_path (line 263) | def test_invalid_env_file_path(self):
    method test_docker_permission_issues (line 278) | def test_docker_permission_issues(self):
    method test_resource_limit_configurations (line 293) | def test_resource_limit_configurations(self):

FILE: tests/test_docker_config_complete.py
  class TestDockerMCPConfiguration (line 12) | class TestDockerMCPConfiguration:
    method test_dockerfile_configuration (line 15) | def test_dockerfile_configuration(self):
    method test_environment_file_template (line 40) | def test_environment_file_template(self):
    method test_logs_directory_setup (line 59) | def test_logs_directory_setup(self):
  class TestDockerCommandValidation (line 77) | class TestDockerCommandValidation:
    method test_docker_build_command (line 81) | def test_docker_build_command(self, mock_run):
    method test_docker_run_mcp_command (line 94) | def test_docker_run_mcp_command(self, mock_run):
    method test_docker_command_structure (line 118) | def test_docker_command_structure(self):
  class TestIntegrationChecks (line 145) | class TestIntegrationChecks:
    method test_complete_setup_checklist (line 148) | def test_complete_setup_checklist(self):
    method test_mcp_integration_readiness (line 171) | def test_mcp_integration_readiness(self):
  class TestErrorHandling (line 193) | class TestErrorHandling:
    method test_missing_api_key_handling (line 196) | def test_missing_api_key_handling(self):
    method test_docker_not_available_handling (line 212) | def test_docker_not_available_handling(self):

FILE: tests/test_docker_healthcheck.py
  class TestDockerHealthCheck (line 13) | class TestDockerHealthCheck:
    method setup (line 17) | def setup(self):
    method test_healthcheck_script_exists (line 22) | def test_healthcheck_script_exists(self):
    method test_healthcheck_script_executable (line 26) | def test_healthcheck_script_executable(self):
    method test_process_check_success (line 36) | def test_process_check_success(self, mock_run):
    method test_process_check_failure (line 49) | def test_process_check_failure(self, mock_run):
    method test_critical_modules_import (line 59) | def test_critical_modules_import(self):
    method test_optional_modules_graceful_failure (line 69) | def test_optional_modules_graceful_failure(self):
    method test_log_directory_check (line 80) | def test_log_directory_check(self):
    method test_health_check_timeout_handling (line 88) | def test_health_check_timeout_handling(self):
    method test_health_check_docker_configuration (line 99) | def test_health_check_docker_configuration(self):
  class TestDockerHealthCheckIntegration (line 113) | class TestDockerHealthCheckIntegration:
    method test_dockerfile_health_check_setup (line 116) | def test_dockerfile_health_check_setup(self):
    method test_health_check_failure_scenarios (line 129) | def test_health_check_failure_scenarios(self):
    method test_health_check_recovery (line 142) | def test_health_check_recovery(self):
    method test_health_check_with_missing_env_vars (line 154) | def test_health_check_with_missing_env_vars(self):
    method test_health_check_performance (line 165) | def test_health_check_performance(self):

FILE: tests/test_docker_implementation.py
  class TestDockerConfiguration (line 26) | class TestDockerConfiguration:
    method setup_method (line 29) | def setup_method(self):
    method test_dockerfile_exists (line 35) | def test_dockerfile_exists(self):
    method test_docker_compose_configuration (line 47) | def test_docker_compose_configuration(self):
    method test_environment_file_template (line 57) | def test_environment_file_template(self):
  class TestDockerCommands (line 68) | class TestDockerCommands:
    method setup_method (line 71) | def setup_method(self):
    method test_docker_build_command (line 76) | def test_docker_build_command(self, mock_run):
    method test_docker_run_command_structure (line 89) | def test_docker_run_command_structure(self, mock_run):
    method test_docker_health_check (line 117) | def test_docker_health_check(self, mock_run):
  class TestEnvironmentValidation (line 132) | class TestEnvironmentValidation:
    method test_required_api_keys_validation (line 135) | def test_required_api_keys_validation(self):
    method test_environment_file_parsing (line 149) | def test_environment_file_parsing(self):
  class TestMCPIntegration (line 182) | class TestMCPIntegration:
    method test_mcp_configuration_generation (line 185) | def test_mcp_configuration_generation(self):
    method test_stdio_communication_structure (line 217) | def test_stdio_communication_structure(self):
  class TestDockerSecurity (line 231) | class TestDockerSecurity:
    method test_non_root_user_configuration (line 234) | def test_non_root_user_configuration(self):
    method test_readonly_filesystem_configuration (line 243) | def test_readonly_filesystem_configuration(self):
    method test_environment_variable_security (line 258) | def test_environment_variable_security(self):
  class TestDockerPerformance (line 272) | class TestDockerPerformance:
    method test_image_size_optimization (line 275) | def test_image_size_optimization(self):
    method test_startup_time_expectations (line 289) | def test_startup_time_expectations(self):
  function temp_project_dir (line 303) | def temp_project_dir():
  class TestIntegration (line 324) | class TestIntegration:
    method test_complete_docker_setup_validation (line 327) | def test_complete_docker_setup_validation(self, temp_project_dir):

FILE: tests/test_docker_mcp_validation.py
  class TestDockerMCPValidation (line 19) | class TestDockerMCPValidation:
    method setup (line 23) | def setup(self):
    method test_dockerfile_exists_and_valid (line 28) | def test_dockerfile_exists_and_valid(self):
    method test_docker_command_validation (line 37) | def test_docker_command_validation(self, mock_run):
    method test_environment_variables_validation (line 47) | def test_environment_variables_validation(self):
    method test_docker_security_configuration (line 61) | def test_docker_security_configuration(self):
  class TestDockerIntegration (line 79) | class TestDockerIntegration:
    method temp_env_file (line 83) | def temp_env_file(self):
    method test_env_file_parsing (line 97) | def test_env_file_parsing(self, temp_env_file):
    method test_mcp_message_structure (line 112) | def test_mcp_message_structure(self):
  class TestDockerPerformance (line 125) | class TestDockerPerformance:
    method test_image_size_expectation (line 128) | def test_image_size_expectation(self):
    method test_startup_performance (line 138) | def test_startup_performance(self):
  class TestFullIntegration (line 147) | class TestFullIntegration:
    method test_complete_setup_simulation (line 150) | def test_complete_setup_simulation(self):
    method test_docker_mcp_workflow (line 164) | def test_docker_mcp_workflow(self):

FILE: tests/test_docker_security.py
  class TestDockerSecurity (line 12) | class TestDockerSecurity:
    method setup (line 16) | def setup(self):
    method test_non_root_user_configuration (line 22) | def test_non_root_user_configuration(self):
    method test_no_unnecessary_privileges (line 34) | def test_no_unnecessary_privileges(self):
    method test_read_only_filesystem (line 47) | def test_read_only_filesystem(self):
    method test_environment_variable_security (line 58) | def test_environment_variable_security(self):
    method test_network_security (line 81) | def test_network_security(self):
    method test_volume_security (line 94) | def test_volume_security(self):
    method test_secret_management (line 107) | def test_secret_management(self):
    method test_container_capabilities (line 117) | def test_container_capabilities(self):
  class TestDockerSecretsHandling (line 135) | class TestDockerSecretsHandling:
    method test_env_file_not_in_image (line 138) | def test_env_file_not_in_image(self):
    method test_dockerignore_for_sensitive_files (line 149) | def test_dockerignore_for_sensitive_files(self):
    method test_no_default_api_keys (line 167) | def test_no_default_api_keys(self):
    method test_api_key_format_validation (line 175) | def test_api_key_format_validation(self):
  class TestDockerComplianceChecks (line 192) | class TestDockerComplianceChecks:
    method test_dockerfile_best_practices (line 195) | def test_dockerfile_best_practices(self):
    method test_container_security_context (line 218) | def test_container_security_context(self):

FILE: tests/test_docker_volume_persistence.py
  class TestDockerVolumePersistence (line 14) | class TestDockerVolumePersistence:
    method setup (line 18) | def setup(self):
    method test_docker_compose_volumes_configuration (line 23) | def test_docker_compose_volumes_configuration(self):
    method test_persistent_volume_creation (line 38) | def test_persistent_volume_creation(self):
    method test_configuration_persistence_between_runs (line 54) | def test_configuration_persistence_between_runs(self):
    method test_log_persistence_configuration (line 71) | def test_log_persistence_configuration(self):
    method test_volume_backup_restore_capability (line 79) | def test_volume_backup_restore_capability(self):
    method test_volume_permissions (line 104) | def test_volume_permissions(self):
  class TestDockerVolumeIntegration (line 123) | class TestDockerVolumeIntegration:
    method test_mcp_config_persistence (line 126) | def test_mcp_config_persistence(self):
    method test_docker_compose_run_volume_usage (line 137) | def test_docker_compose_run_volume_usage(self):
    method test_volume_data_isolation (line 149) | def test_volume_data_isolation(self):

FILE: tests/test_file_protection.py
  class TestMCPDirectoryDetection (line 19) | class TestMCPDirectoryDetection:
    method test_detect_mcp_directory_dynamically (line 22) | def test_detect_mcp_directory_dynamically(self, tmp_path):
    method test_no_detection_on_non_mcp_directory (line 40) | def test_no_detection_on_non_mcp_directory(self, tmp_path):
    method test_no_detection_on_regular_directory (line 48) | def test_no_detection_on_regular_directory(self, tmp_path):
    method test_no_detection_on_file (line 57) | def test_no_detection_on_file(self, tmp_path):
    method test_mcp_directory_excluded_from_scan (line 64) | def test_mcp_directory_excluded_from_scan(self, tmp_path):
  class TestHomeDirectoryProtection (line 100) | class TestHomeDirectoryProtection:
    method test_detect_exact_home_directory (line 103) | def test_detect_exact_home_directory(self):
    method test_allow_home_subdirectories (line 111) | def test_allow_home_subdirectories(self):
    method test_detect_home_patterns_macos (line 119) | def test_detect_home_patterns_macos(self):
    method test_detect_home_patterns_linux (line 127) | def test_detect_home_patterns_linux(self):
    method test_detect_home_patterns_windows (line 134) | def test_detect_home_patterns_windows(self):
    method test_home_directory_excluded_from_scan (line 141) | def test_home_directory_excluded_from_scan(self, tmp_path):
  class TestUserHomeEnvironmentVariable (line 151) | class TestUserHomeEnvironmentVariable:
    method test_user_home_from_pathlib (line 154) | def test_user_home_from_pathlib(self):
    method test_get_home_directory_uses_pathlib (line 161) | def test_get_home_directory_uses_pathlib(self):
    method test_home_directory_on_different_platforms (line 170) | def test_home_directory_on_different_platforms(self):
  class TestExcludedDirectories (line 186) | class TestExcludedDirectories:
    method test_excluded_dirs_not_scanned (line 189) | def test_excluded_dirs_not_scanned(self, tmp_path):
    method test_new_excluded_directories (line 224) | def test_new_excluded_directories(self, tmp_path):
  class TestIntegrationScenarios (line 246) | class TestIntegrationScenarios:
    method test_project_with_mcp_clone_inside (line 249) | def test_project_with_mcp_clone_inside(self, tmp_path):
    method test_security_without_workspace_root (line 300) | def test_security_without_workspace_root(self, tmp_path):

FILE: tests/test_gemini_token_usage.py
  class TestGeminiTokenUsage (line 9) | class TestGeminiTokenUsage(unittest.TestCase):
    method setUp (line 12) | def setUp(self):
    method test_extract_usage_with_valid_tokens (line 16) | def test_extract_usage_with_valid_tokens(self):
    method test_extract_usage_with_none_input_tokens (line 29) | def test_extract_usage_with_none_input_tokens(self):
    method test_extract_usage_with_none_output_tokens (line 44) | def test_extract_usage_with_none_output_tokens(self):
    method test_extract_usage_with_both_none_tokens (line 59) | def test_extract_usage_with_both_none_tokens(self):
    method test_extract_usage_without_usage_metadata (line 71) | def test_extract_usage_without_usage_metadata(self):
    method test_extract_usage_with_zero_tokens (line 80) | def test_extract_usage_with_zero_tokens(self):
    method test_extract_usage_missing_attributes (line 93) | def test_extract_usage_missing_attributes(self):

FILE: tests/test_image_support_integration.py
  class TestImageSupportIntegration (line 33) | class TestImageSupportIntegration:
    method test_conversation_turn_includes_images (line 36) | def test_conversation_turn_includes_images(self):
    method test_get_conversation_image_list_newest_first (line 51) | def test_get_conversation_image_list_newest_first(self):
    method test_add_turn_with_images (line 86) | def test_add_turn_with_images(self, mock_storage):
    method test_chat_tool_schema_includes_images (line 148) | def test_chat_tool_schema_includes_images(self):
    method test_debug_tool_schema_includes_images (line 159) | def test_debug_tool_schema_includes_images(self):
    method test_tool_image_validation_limits (line 170) | def test_tool_image_validation_limits(self):
    method test_image_validation_model_specific_limits (line 204) | def test_image_validation_model_specific_limits(self):
    method test_chat_tool_execution_with_images (line 242) | async def test_chat_tool_execution_with_images(self):
    method test_cross_tool_image_context_preservation (line 318) | def test_cross_tool_image_context_preservation(self, mock_storage):
    method test_tool_request_base_class_has_images (line 410) | def test_tool_request_base_class_has_images(self):
    method test_data_url_image_format_support (line 422) | def test_data_url_image_format_support(self):
    method test_empty_images_handling (line 443) | def test_empty_images_handling(self):
    method test_conversation_memory_thread_chaining_with_images (line 456) | def test_conversation_memory_thread_chaining_with_images(self, mock_st...

FILE: tests/test_image_validation.py
  class TestImageValidation (line 13) | class TestImageValidation:
    method test_validate_data_url_valid (line 16) | def test_validate_data_url_valid(self) -> None:
    method test_validate_data_url_invalid_format (line 37) | def test_validate_data_url_invalid_format(self, invalid_url: str, expe...
    method test_non_data_url_treated_as_file_path (line 43) | def test_non_data_url_treated_as_file_path(self) -> None:
    method test_validate_data_url_unsupported_type (line 50) | def test_validate_data_url_unsupported_type(self) -> None:
    method test_validate_data_url_invalid_base64 (line 58) | def test_validate_data_url_invalid_base64(self) -> None:
    method test_validate_large_data_url (line 66) | def test_validate_large_data_url(self) -> None:
    method test_validate_file_path_valid (line 87) | def test_validate_file_path_valid(self) -> None:
    method test_validate_file_path_not_found (line 106) | def test_validate_file_path_not_found(self) -> None:
    method test_validate_file_path_unsupported_extension (line 112) | def test_validate_file_path_unsupported_extension(self) -> None:
    method test_validate_file_path_read_error (line 125) | def test_validate_file_path_read_error(self) -> None:
    method test_validate_image_size_limit (line 137) | def test_validate_image_size_limit(self) -> None:
    method test_validate_image_custom_size_limit (line 153) | def test_validate_image_custom_size_limit(self) -> None:
    method test_validate_image_default_size_limit (line 175) | def test_validate_image_default_size_limit(self) -> None:
    method test_validate_all_supported_formats (line 197) | def test_validate_all_supported_formats(self) -> None:
  class TestProviderIntegration (line 220) | class TestProviderIntegration:
    method test_gemini_provider_uses_validation (line 224) | def test_gemini_provider_uses_validation(self, mock_logger: Mock) -> N...
    method test_openai_compatible_provider_uses_validation (line 237) | def test_openai_compatible_provider_uses_validation(self, mock_logging...
    method test_data_url_preservation (line 249) | def test_data_url_preservation(self) -> None:

FILE: tests/test_integration_utf8.py
  function run_utf8_integration_tests (line 16) | def run_utf8_integration_tests():
  function test_utf8_json_encoding (line 50) | def test_utf8_json_encoding():
  function test_language_instruction_generation (line 124) | def test_language_instruction_generation():
  function test_file_utf8_handling (line 161) | def test_file_utf8_handling():
  function test_mcp_tools_integration (line 278) | def test_mcp_tools_integration():
  function run_unit_tests (line 409) | def run_unit_tests():
  function main (line 460) | def main():

FILE: tests/test_intelligent_fallback.py
  class TestIntelligentFallback (line 16) | class TestIntelligentFallback:
    method setup_method (line 19) | def setup_method(self):
    method teardown_method (line 29) | def teardown_method(self):
    method test_prefers_openai_o3_mini_when_available (line 39) | def test_prefers_openai_o3_mini_when_available(self):
    method test_prefers_gemini_flash_when_openai_unavailable (line 50) | def test_prefers_gemini_flash_when_openai_unavailable(self):
    method test_prefers_openai_when_both_available (line 61) | def test_prefers_openai_when_both_available(self):
    method test_fallback_when_no_keys_available (line 74) | def test_fallback_when_no_keys_available(self):
    method test_available_providers_with_keys (line 86) | def test_available_providers_with_keys(self):
    method test_auto_mode_conversation_memory_integration (line 111) | def test_auto_mode_conversation_memory_integration(self):
    method test_auto_mode_with_gemini_only (line 153) | def test_auto_mode_with_gemini_only(self):
    method test_non_auto_mode_unchanged (line 192) | def test_non_auto_mode_unchanged(self):

FILE: tests/test_issue_245_simple.py
  function test_issue_245_custom_openai_temperature_ignored (line 12) | def test_issue_245_custom_openai_temperature_ignored():

FILE: tests/test_large_prompt_handling.py
  class TestLargePromptHandling (line 25) | class TestLargePromptHandling:
    method teardown_method (line 28) | def teardown_method(self):
    method large_prompt (line 36) | def large_prompt(self):
    method normal_prompt (line 41) | def normal_prompt(self):
    method temp_prompt_file (line 46) | def temp_prompt_file(self, large_prompt):
    method test_chat_large_prompt_detection (line 56) | async def test_chat_large_prompt_detection(self, large_prompt):
    method test_chat_normal_prompt_works (line 75) | async def test_chat_normal_prompt_works(self, normal_prompt):
    method test_chat_prompt_file_handling (line 100) | async def test_chat_prompt_file_handling(self):
    method test_codereview_large_focus (line 136) | async def test_codereview_large_focus(self, large_prompt):
    method test_multiple_files_with_prompt_txt (line 256) | async def test_multiple_files_with_prompt_txt(self, temp_prompt_file):
    method test_boundary_case_exactly_at_limit (line 318) | async def test_boundary_case_exactly_at_limit(self):
    method test_boundary_case_just_over_limit (line 350) | async def test_boundary_case_just_over_limit(self):
    method test_empty_prompt_no_file (line 368) | async def test_empty_prompt_no_file(self):
    method test_prompt_file_read_error (line 397) | async def test_prompt_file_read_error(self):
    method test_large_file_context_does_not_trigger_mcp_prompt_limit (line 443) | async def test_large_file_context_does_not_trigger_mcp_prompt_limit(se...
    method test_mcp_boundary_with_large_internal_context (line 493) | async def test_mcp_boundary_with_large_internal_context(self):
    method test_mcp_boundary_vs_internal_processing_distinction (line 563) | async def test_mcp_boundary_vs_internal_processing_distinction(self):
    method test_continuation_with_huge_conversation_history (line 610) | async def test_continuation_with_huge_conversation_history(self):

FILE: tests/test_line_numbers_integration.py
  class TestLineNumbersIntegration (line 14) | class TestLineNumbersIntegration:
    method test_all_tools_want_line_numbers (line 17) | def test_all_tools_want_line_numbers(self):
    method test_no_tools_override_line_numbers (line 32) | def test_no_tools_override_line_numbers(self):

FILE: tests/test_listmodels.py
  class TestListModelsTool (line 13) | class TestListModelsTool:
    method tool (line 17) | def tool(self):
    method test_tool_metadata (line 21) | def test_tool_metadata(self, tool):
    method test_execute_with_no_providers (line 28) | async def test_execute_with_no_providers(self, tool):
    method test_execute_with_gemini_configured (line 56) | async def test_execute_with_gemini_configured(self, tool):
    method test_execute_with_multiple_providers (line 77) | async def test_execute_with_multiple_providers(self, tool):
    method test_execute_with_openrouter (line 105) | async def test_execute_with_openrouter(self, tool):
    method test_execute_with_custom_api (line 123) | async def test_execute_with_custom_api(self, tool):
    method test_output_includes_usage_tips (line 139) | async def test_output_includes_usage_tips(self, tool):
    method test_model_category (line 151) | def test_model_category(self, tool):

FILE: tests/test_listmodels_restrictions.py
  class TestListModelsRestrictions (line 14) | class TestListModelsRestrictions(unittest.TestCase):
    method setUp (line 17) | def setUp(self):
    method tearDown (line 84) | def tearDown(self):
    method test_listmodels_respects_openrouter_restrictions (line 103) | def test_listmodels_respects_openrouter_restrictions(
    method test_listmodels_shows_all_models_without_restrictions (line 244) | def test_listmodels_shows_all_models_without_restrictions(self, mock_g...

FILE: tests/test_mcp_error_handling.py
  function _install_dummy_provider (line 11) | def _install_dummy_provider(monkeypatch):
  function test_tool_execution_error_sets_is_error_flag_for_mcp_response (line 40) | async def test_tool_execution_error_sets_is_error_flag_for_mcp_response(...

FILE: tests/test_model_enumeration.py
  class TestModelEnumeration (line 19) | class TestModelEnumeration:
    method setup_method (line 22) | def setup_method(self):
    method teardown_method (line 37) | def teardown_method(self):
    method _setup_environment (line 54) | def _setup_environment(self, provider_config):
    method test_no_models_when_no_providers_configured (line 78) | def test_no_models_when_no_providers_configured(self):
    method test_openrouter_models_without_api_key (line 99) | def test_openrouter_models_without_api_key(self):
    method test_custom_models_without_custom_url (line 112) | def test_custom_models_without_custom_url(self):
    method test_custom_models_not_exposed_with_openrouter_only (line 125) | def test_custom_models_not_exposed_with_openrouter_only(self):
    method test_no_duplicates_with_overlapping_providers (line 135) | def test_no_duplicates_with_overlapping_providers(self):
    method test_specific_native_models_only_with_api_keys (line 167) | def test_specific_native_models_only_with_api_keys(self, model_name, s...
    method test_openrouter_free_model_aliases_available (line 179) | def test_openrouter_free_model_aliases_available(self, tmp_path, monke...

FILE: tests/test_model_metadata_continuation.py
  class TestModelMetadataContinuation (line 20) | class TestModelMetadataContinuation:
    method test_model_preserved_from_previous_turn (line 24) | async def test_model_preserved_from_previous_turn(self):
    method test_reconstruct_thread_context_preserves_model (line 62) | async def test_reconstruct_thread_context_preserves_model(self):
    method test_multiple_turns_uses_last_assistant_model (line 90) | async def test_multiple_turns_uses_last_assistant_model(self):
    method test_no_previous_assistant_turn_defaults (line 122) | async def test_no_previous_assistant_turn_defaults(self):
    method test_explicit_model_overrides_previous_turn (line 179) | async def test_explicit_model_overrides_previous_turn(self):
    method test_thread_chain_model_preservation (line 206) | async def test_thread_chain_model_preservation(self):

FILE: tests/test_model_resolution_bug.py
  class TestModelResolutionBug (line 17) | class TestModelResolutionBug:
    method setup_method (line 20) | def setup_method(self):
    method test_openrouter_registry_resolves_gemini_alias (line 24) | def test_openrouter_registry_resolves_gemini_alias(self):
    method test_consensus_tool_model_resolution_bug_reproduction (line 47) | def test_consensus_tool_model_resolution_bug_reproduction(self):
    method test_bug_reproduction_with_malformed_model_name (line 98) | def test_bug_reproduction_with_malformed_model_name(self):

FILE: tests/test_model_restrictions.py
  class TestModelRestrictionService (line 14) | class TestModelRestrictionService:
    method test_no_restrictions_by_default (line 17) | def test_no_restrictions_by_default(self):
    method test_load_single_model_restriction (line 35) | def test_load_single_model_restriction(self):
    method test_load_multiple_models_restriction (line 49) | def test_load_multiple_models_restriction(self):
    method test_case_insensitive_and_whitespace_handling (line 79) | def test_case_insensitive_and_whitespace_handling(self):
    method test_empty_string_allows_all (line 90) | def test_empty_string_allows_all(self):
    method test_filter_models (line 106) | def test_filter_models(self):
    method test_get_allowed_models (line 116) | def test_get_allowed_models(self):
    method test_shorthand_names_in_restrictions (line 127) | def test_shorthand_names_in_restrictions(self):
    method test_validation_against_known_models (line 152) | def test_validation_against_known_models(self, caplog):
    method test_openrouter_model_restrictions (line 173) | def test_openrouter_model_restrictions(self):
    method test_openrouter_filter_models (line 195) | def test_openrouter_filter_models(self):
    method test_combined_provider_restrictions (line 205) | def test_combined_provider_restrictions(self):
  class TestProviderIntegration (line 236) | class TestProviderIntegration:
    method test_openai_provider_respects_restrictions (line 240) | def test_openai_provider_respects_restrictions(self):
    method test_gemini_provider_respects_restrictions (line 261) | def test_gemini_provider_respects_restrictions(self):
    method test_gemini_parameter_order_regression_protection (line 284) | def test_gemini_parameter_order_regression_protection(self):
    method test_gemini_parameter_order_edge_case_full_name_only (line 323) | def test_gemini_parameter_order_edge_case_full_name_only(self):
  class TestCustomProviderOpenRouterRestrictions (line 349) | class TestCustomProviderOpenRouterRestrictions:
    method test_custom_provider_respects_openrouter_restrictions (line 353) | def test_custom_provider_respects_openrouter_restrictions(self):
    method test_custom_provider_openrouter_capabilities_restrictions (line 373) | def test_custom_provider_openrouter_capabilities_restrictions(self):
    method test_custom_provider_no_openrouter_key_ignores_restrictions (line 397) | def test_custom_provider_no_openrouter_key_ignores_restrictions(self):
    method test_custom_provider_empty_restrictions_allows_all_openrouter (line 419) | def test_custom_provider_empty_restrictions_allows_all_openrouter(self):
  class TestRegistryIntegration (line 436) | class TestRegistryIntegration:
    method test_registry_with_shorthand_restrictions (line 440) | def test_registry_with_shorthand_restrictions(self):
    method test_get_available_models_respects_restrictions (line 461) | def test_get_available_models_respects_restrictions(self, mock_get_pro...
  class TestShorthandRestrictions (line 585) | class TestShorthandRestrictions:
    method test_providers_validate_shorthands_correctly (line 589) | def test_providers_validate_shorthands_correctly(self):
    method test_multiple_shorthands_for_same_model (line 621) | def test_multiple_shorthands_for_same_model(self):
    method test_both_shorthand_and_full_name_allowed (line 646) | def test_both_shorthand_and_full_name_allowed(self):
  class TestAutoModeWithRestrictions (line 664) | class TestAutoModeWithRestrictions:
    method test_fallback_model_respects_restrictions (line 668) | def test_fallback_model_respects_restrictions(self, mock_get_provider):
    method test_fallback_with_shorthand_restrictions (line 747) | def test_fallback_with_shorthand_restrictions(self, monkeypatch):

FILE: tests/test_o3_pro_output_text_fix.py
  class TestO3ProOutputTextFix (line 37) | class TestO3ProOutputTextFix:
    method setup_method (line 40) | def setup_method(self):
    method teardown_method (line 53) | def teardown_method(self):
    method test_o3_pro_uses_output_text_field (line 60) | async def test_o3_pro_uses_output_text_field(self, monkeypatch):
    method _execute_chat_tool_test (line 93) | async def _execute_chat_tool_test(self):
    method _verify_chat_tool_response (line 106) | def _verify_chat_tool_response(self, result):

FILE: tests/test_o3_temperature_fix_simple.py
  class TestO3TemperatureParameterFixSimple (line 13) | class TestO3TemperatureParameterFixSimple:
    method test_o3_models_exclude_temperature_from_api_call (line 18) | def test_o3_models_exclude_temperature_from_api_call(self, mock_openai...
    method test_regular_models_include_temperature_in_api_call (line 66) | def test_regular_models_include_temperature_in_api_call(self, mock_ope...
    method test_o3_models_filter_unsupported_parameters (line 115) | def test_o3_models_filter_unsupported_parameters(self, mock_openai_cla...
    method test_all_o3_models_have_correct_temperature_capability (line 176) | def test_all_o3_models_have_correct_temperature_capability(self, mock_...
    method test_openai_provider_temperature_constraints (line 212) | def test_openai_provider_temperature_constraints(self, mock_restrictio...

FILE: tests/test_openai_compatible_token_usage.py
  class TestOpenAICompatibleTokenUsage (line 9) | class TestOpenAICompatibleTokenUsage(unittest.TestCase):
    method setUp (line 12) | def setUp(self):
    method test_extract_usage_with_valid_tokens (line 34) | def test_extract_usage_with_valid_tokens(self):
    method test_extract_usage_with_none_prompt_tokens (line 48) | def test_extract_usage_with_none_prompt_tokens(self):
    method test_extract_usage_with_none_completion_tokens (line 63) | def test_extract_usage_with_none_completion_tokens(self):
    method test_extract_usage_with_all_none_tokens (line 78) | def test_extract_usage_with_all_none_tokens(self):
    method test_extract_usage_without_usage (line 93) | def test_extract_usage_without_usage(self):
    method test_extract_usage_with_zero_tokens (line 102) | def test_extract_usage_with_zero_tokens(self):
    method test_alternative_token_format_with_none (line 116) | def test_alternative_token_format_with_none(self):

FILE: tests/test_openai_provider.py
  class TestOpenAIProvider (line 10) | class TestOpenAIProvider:
    method setup_method (line 13) | def setup_method(self):
    method teardown_method (line 20) | def teardown_method(self):
    method test_initialization (line 28) | def test_initialization(self):
    method test_initialization_with_custom_url (line 35) | def test_initialization_with_custom_url(self):
    method test_model_validation (line 41) | def test_model_validation(self):
    method test_resolve_model_name (line 75) | def test_resolve_model_name(self):
    method test_get_capabilities_o3 (line 105) | def test_get_capabilities_o3(self):
    method test_get_capabilities_with_alias (line 122) | def test_get_capabilities_with_alias(self):
    method test_get_capabilities_gpt5 (line 132) | def test_get_capabilities_gpt5(self):
    method test_get_capabilities_gpt5_mini (line 148) | def test_get_capabilities_gpt5_mini(self):
    method test_get_capabilities_gpt52 (line 164) | def test_get_capabilities_gpt52(self):
    method test_get_capabilities_gpt51_codex (line 175) | def test_get_capabilities_gpt51_codex(self):
    method test_get_capabilities_gpt51_codex_mini (line 185) | def test_get_capabilities_gpt51_codex_mini(self):
    method test_generate_content_resolves_alias_before_api_call (line 195) | def test_generate_content_resolves_alias_before_api_call(self, mock_op...
    method test_generate_content_other_aliases (line 247) | def test_generate_content_other_aliases(self, mock_openai_class):
    method test_generate_content_no_alias_passthrough (line 277) | def test_generate_content_no_alias_passthrough(self, mock_openai_class):
    method test_extended_thinking_capabilities (line 300) | def test_extended_thinking_capabilities(self):
    method test_o3_pro_routes_to_responses_endpoint (line 327) | def test_o3_pro_routes_to_responses_endpoint(self, mock_openai_class):
    method test_non_o3_pro_uses_chat_completions (line 364) | def test_non_o3_pro_uses_chat_completions(self, mock_openai_class):

FILE: tests/test_openrouter_provider.py
  class TestOpenRouterProvider (line 13) | class TestOpenRouterProvider:
    method test_provider_initialization (line 16) | def test_provider_initialization(self):
    method test_custom_headers (line 23) | def test_custom_headers(self):
    method test_model_validation (line 41) | def test_model_validation(self):
    method test_get_capabilities (line 56) | def test_get_capabilities(self):
    method test_model_alias_resolution (line 77) | def test_model_alias_resolution(self):
    method test_openrouter_registration (line 114) | def test_openrouter_registration(self):
  class TestOpenRouterAutoMode (line 129) | class TestOpenRouterAutoMode:
    method setup_method (line 132) | def setup_method(self):
    method teardown_method (line 145) | def teardown_method(self):
    method test_openrouter_only_auto_mode (line 159) | def test_openrouter_only_auto_mode(self):
    method test_openrouter_with_restrictions (line 204) | def test_openrouter_with_restrictions(self):
    method test_no_providers_fails_auto_mode (line 249) | def test_no_providers_fails_auto_mode(self):
    method test_openrouter_without_registry (line 261) | def test_openrouter_without_registry(self):
  class TestOpenRouterRegistry (line 282) | class TestOpenRouterRegistry:
    method test_registry_loading (line 285) | def test_registry_loading(self):
    method test_registry_capabilities (line 304) | def test_registry_capabilities(self):
    method test_multiple_aliases_same_model (line 340) | def test_multiple_aliases_same_model(self):
  class TestOpenRouterFunctionality (line 359) | class TestOpenRouterFunctionality:
    method test_openrouter_always_uses_correct_url (line 362) | def test_openrouter_always_uses_correct_url(self):
    method test_openrouter_headers_set_correctly (line 374) | def test_openrouter_headers_set_correctly(self):
    method test_openrouter_model_registry_initialized (line 383) | def test_openrouter_model_registry_initialized(self):

FILE: tests/test_openrouter_registry.py
  class TestOpenRouterModelRegistry (line 14) | class TestOpenRouterModelRegistry:
    method test_registry_initialization (line 17) | def test_registry_initialization(self):
    method test_custom_config_path (line 25) | def test_custom_config_path(self):
    method test_environment_variable_override (line 52) | def test_environment_variable_override(self):
    method test_alias_resolution (line 85) | def test_alias_resolution(self):
    method test_direct_model_name_lookup (line 107) | def test_direct_model_name_lookup(self):
    method test_unknown_model_resolution (line 120) | def test_unknown_model_resolution(self):
    method test_model_capabilities_conversion (line 129) | def test_model_capabilities_conversion(self):
    method test_duplicate_alias_detection (line 144) | def test_duplicate_alias_detection(self):
    method test_backwards_compatibility_max_tokens (line 168) | def test_backwards_compatibility_max_tokens(self):
    method test_missing_config_file (line 192) | def test_missing_config_file(self):
    method test_invalid_json_config (line 203) | def test_invalid_json_config(self):
    method test_model_with_all_capabilities (line 217) | def test_model_with_all_capabilities(self):

FILE: tests/test_openrouter_store_parameter.py
  class MockOpenRouterProvider (line 19) | class MockOpenRouterProvider(OpenAICompatibleProvider):
    method get_provider_type (line 24) | def get_provider_type(self):
    method get_capabilities (line 27) | def get_capabilities(self, model_name):
    method validate_model_name (line 32) | def validate_model_name(self, model_name):
    method list_models (line 35) | def list_models(self, **kwargs):
  class MockOpenAIProvider (line 39) | class MockOpenAIProvider(OpenAICompatibleProvider):
    method get_provider_type (line 44) | def get_provider_type(self):
    method get_capabilities (line 47) | def get_capabilities(self, model_name):
    method validate_model_name (line 52) | def validate_model_name(self, model_name):
    method list_models (line 55) | def list_models(self, **kwargs):
  class TestStoreParameterHandling (line 59) | class TestStoreParameterHandling(unittest.TestCase):
    method test_openrouter_responses_omits_store_parameter (line 66) | def test_openrouter_responses_omits_store_parameter(self):
    method test_openai_responses_includes_store_parameter (line 104) | def test_openai_responses_includes_store_parameter(self):

FILE: tests/test_parse_model_option.py
  class TestParseModelOption (line 6) | class TestParseModelOption:
    method test_openrouter_free_suffix_preserved (line 9) | def test_openrouter_free_suffix_preserved(self):
    method test_openrouter_beta_suffix_preserved (line 15) | def test_openrouter_beta_suffix_preserved(self):
    method test_openrouter_preview_suffix_preserved (line 21) | def test_openrouter_preview_suffix_preserved(self):
    method test_ollama_tag_parsed_as_option (line 27) | def test_ollama_tag_parsed_as_option(self):
    method test_consensus_stance_parsed_as_option (line 33) | def test_consensus_stance_parsed_as_option(self):
    method test_openrouter_unknown_suffix_parsed_as_option (line 43) | def test_openrouter_unknown_suffix_parsed_as_option(self):
    method test_plain_model_name (line 49) | def test_plain_model_name(self):
    method test_url_not_parsed (line 55) | def test_url_not_parsed(self):
    method test_whitespace_handling (line 61) | def test_whitespace_handling(self):
    method test_case_insensitive_suffix_matching (line 71) | def test_case_insensitive_suffix_matching(self):

FILE: tests/test_path_traversal_security.py
  class TestPathTraversalFix (line 22) | class TestPathTraversalFix:
    method test_exact_match_still_works (line 25) | def test_exact_match_still_works(self):
    method test_subdirectory_now_blocked (line 31) | def test_subdirectory_now_blocked(self):
    method test_deeply_nested_blocked (line 39) | def test_deeply_nested_blocked(self):
    method test_root_blocked (line 44) | def test_root_blocked(self):
    method test_safe_paths_allowed (line 48) | def test_safe_paths_allowed(self):
    method test_similar_names_not_blocked (line 54) | def test_similar_names_not_blocked(self):
  class TestHomeDirectoryHandling (line 61) | class TestHomeDirectoryHandling:
    method test_home_container_blocked (line 69) | def test_home_container_blocked(self):
    method test_home_subdirectories_allowed (line 73) | def test_home_subdirectories_allowed(self):
    method test_home_deeply_nested_allowed (line 86) | def test_home_deeply_nested_allowed(self):
  class TestRegressionPrevention (line 91) | class TestRegressionPrevention:
    method test_etc_passwd_blocked (line 94) | def test_etc_passwd_blocked(self):
    method test_etc_shadow_blocked (line 98) | def test_etc_shadow_blocked(self):
  class TestWindowsPathHandling (line 103) | class TestWindowsPathHandling:
    method test_windows_root_drive_blocked (line 111) | def test_windows_root_drive_blocked(self):
    method test_windows_dangerous_subdirectory_detection (line 120) | def test_windows_dangerous_subdirectory_detection(self):
    method test_windows_path_not_relative_to_different_drive (line 141) | def test_windows_path_not_relative_to_different_drive(self):

FILE: tests/test_per_tool_model_defaults.py
  class TestToolModelCategories (line 25) | class TestToolModelCategories:
    method test_thinkdeep_category (line 28) | def test_thinkdeep_category(self):
    method test_debug_category (line 32) | def test_debug_category(self):
    method test_analyze_category (line 36) | def test_analyze_category(self):
    method test_precommit_category (line 40) | def test_precommit_category(self):
    method test_chat_category (line 44) | def test_chat_category(self):
    method test_codereview_category (line 48) | def test_codereview_category(self):
    method test_base_tool_default_category (line 52) | def test_base_tool_default_category(self):
  class TestModelSelection (line 77) | class TestModelSelection:
    method teardown_method (line 80) | def teardown_method(self):
    method test_extended_reasoning_with_openai (line 87) | def test_extended_reasoning_with_openai(self):
    method test_extended_reasoning_with_gemini_only (line 104) | def test_extended_reasoning_with_gemini_only(self):
    method test_fast_response_with_openai (line 122) | def test_fast_response_with_openai(self):
    method test_fast_response_with_gemini_only (line 139) | def test_fast_response_with_gemini_only(self):
    method test_balanced_category_fallback (line 156) | def test_balanced_category_fallback(self):
    method test_no_category_uses_balanced_logic (line 173) | def test_no_category_uses_balanced_logic(self):
  class TestFlexibleModelSelection (line 186) | class TestFlexibleModelSelection:
    method test_fallback_handles_mixed_model_names (line 189) | def test_fallback_handles_mixed_model_names(self):
  class TestCustomProviderFallback (line 238) | class TestCustomProviderFallback:
    method test_extended_reasoning_custom_fallback (line 241) | def test_extended_reasoning_custom_fallback(self):
    method test_extended_reasoning_final_fallback (line 256) | def test_extended_reasoning_final_fallback(self):
  class TestAutoModeErrorMessages (line 270) | class TestAutoModeErrorMessages:
    method teardown_method (line 273) | def teardown_method(self):
    method test_chat_auto_error_message (line 279) | async def test_chat_auto_error_message(self):
  class TestProviderHelperMethods (line 315) | class TestProviderHelperMethods:
    method test_extended_reasoning_with_custom_provider (line 318) | def test_extended_reasoning_with_custom_provider(self):
    method test_extended_reasoning_with_openrouter (line 332) | def test_extended_reasoning_with_openrouter(self):
    method test_fallback_when_no_providers_available (line 345) | def test_fallback_when_no_providers_available(self):
  class TestEffectiveAutoMode (line 359) | class TestEffectiveAutoMode:
    method test_explicit_auto_mode (line 362) | def test_explicit_auto_mode(self):
    method test_unavailable_model_triggers_auto_mode (line 369) | def test_unavailable_model_triggers_auto_mode(self):
    method test_available_model_no_auto_mode (line 379) | def test_available_model_no_auto_mode(self):
  class TestRuntimeModelSelection (line 390) | class TestRuntimeModelSelection:
    method teardown_method (line 393) | def teardown_method(self):
    method test_explicit_auto_in_request (line 399) | async def test_explicit_auto_in_request(self):
    method test_unavailable_model_in_request (line 419) | async def test_unavailable_model_in_request(self):
  class TestSchemaGeneration (line 444) | class TestSchemaGeneration:
    method test_schema_with_explicit_auto_mode (line 447) | def test_schema_with_explicit_auto_mode(self):
    method test_schema_with_unavailable_default_model (line 457) | def test_schema_with_unavailable_default_model(self):
    method test_schema_with_available_default_model (line 470) | def test_schema_with_available_default_model(self):
  class TestUnavailableModelFallback (line 484) | class TestUnavailableModelFallback:
    method test_unavailable_default_model_fallback (line 488) | async def test_unavailable_default_model_fallback(self):
    method test_available_default_model_no_fallback (line 515) | async def test_available_default_model_no_fallback(self):

FILE: tests/test_pii_sanitizer.py
  class TestPIISanitizer (line 9) | class TestPIISanitizer(unittest.TestCase):
    method setUp (line 12) | def setUp(self):
    method test_api_key_sanitization (line 16) | def test_api_key_sanitization(self):
    method test_personal_info_sanitization (line 36) | def test_personal_info_sanitization(self):
    method test_header_sanitization (line 58) | def test_header_sanitization(self):
    method test_nested_structure_sanitization (line 76) | def test_nested_structure_sanitization(self):
    method test_url_sanitization (line 99) | def test_url_sanitization(self):
    method test_disable_sanitization (line 117) | def test_disable_sanitization(self):
    method test_custom_pattern (line 127) | def test_custom_pattern(self):

FILE: tests/test_pip_detection_fix.py
  class TestPipDetectionFix (line 15) | class TestPipDetectionFix:
    method test_run_server_script_syntax_valid (line 18) | def test_run_server_script_syntax_valid(self):
    method test_run_server_has_proper_shebang (line 23) | def test_run_server_has_proper_shebang(self):
    method test_critical_functions_exist (line 28) | def test_critical_functions_exist(self):
    method test_pip_detection_consistency_issue (line 36) | def test_pip_detection_consistency_issue(self):
    method test_pip_detection_with_non_interactive_shell (line 52) | def test_pip_detection_with_non_interactive_shell(self):
    method test_enhanced_diagnostic_messages_included (line 81) | def test_enhanced_diagnostic_messages_included(self):
    method test_setup_env_file_does_not_create_bsd_backup (line 103) | def test_setup_env_file_does_not_create_bsd_backup(self, tmp_path):

FILE: tests/test_planner.py
  class TestPlannerTool (line 14) | class TestPlannerTool:
    method test_tool_metadata (line 17) | def test_tool_metadata(self):
    method test_request_validation (line 27) | def test_request_validation(self):
    method test_input_schema_generation (line 45) | def test_input_schema_generation(self):
    method test_model_category_for_planning (line 74) | def test_model_category_for_planning(self):
    method test_execute_first_step (line 83) | async def test_execute_first_step(self):
    method test_execute_subsequent_step (line 120) | async def test_execute_subsequent_step(self):
    method test_execute_with_continuation_context (line 155) | async def test_execute_with_continuation_context(self):
    method test_execute_final_step (line 204) | async def test_execute_final_step(self):
    method test_execute_with_branching (line 236) | async def test_execute_with_branching(self):
    method test_execute_with_revision (line 267) | async def test_execute_with_revision(self):
    method test_execute_adjusts_total_steps (line 305) | async def test_execute_adjusts_total_steps(self):
    method test_execute_error_handling (line 335) | async def test_execute_error_handling(self):
    method test_execute_step_history_tracking (line 355) | async def test_execute_step_history_tracking(self):
  class TestPlannerToolIntegration (line 383) | class TestPlannerToolIntegration:
    method setup_method (line 386) | def setup_method(self):
    method test_interactive_planning_flow (line 394) | async def test_interactive_planning_flow(self):
    method test_simple_planning_flow (line 427) | async def test_simple_planning_flow(self):

FILE: tests/test_precommit_workflow.py
  class TestPrecommitWorkflowTool (line 17) | class TestPrecommitWorkflowTool:
    method test_tool_metadata (line 20) | def test_tool_metadata(self):
    method test_tool_model_category (line 28) | def test_tool_model_category(self):
    method test_default_temperature (line 33) | def test_default_temperature(self):
    method test_request_model_basic_validation (line 40) | def test_request_model_basic_validation(self):
    method test_request_model_step_one_validation (line 59) | def test_request_model_step_one_validation(self):
    method test_request_model_later_steps_no_path_required (line 72) | def test_request_model_later_steps_no_path_required(self):
    method test_request_model_optional_fields (line 87) | def test_request_model_optional_fields(self):
    method test_precommit_specific_fields (line 111) | def test_precommit_specific_fields(self):
    method test_precommit_type_validation (line 133) | def test_precommit_type_validation(self):
    method test_severity_filter_options (line 160) | def test_severity_filter_options(self):
    method test_input_schema_generation (line 176) | def test_input_schema_generation(self):
    method test_workflow_request_model_method (line 194) | def test_workflow_request_model_method(self):
    method test_system_prompt_integration (line 200) | def test_system_prompt_integration(self):

FILE: tests/test_prompt_regression.py
  function skip_if_no_custom_api (line 46) | def skip_if_no_custom_api():
  class TestPromptIntegration (line 54) | class TestPromptIntegration:
    method test_chat_normal_prompt (line 59) | async def test_chat_normal_prompt(self):
    method test_chat_with_files (line 81) | async def test_chat_with_files(self):
    method test_thinkdeep_normal_analysis (line 123) | async def test_thinkdeep_normal_analysis(self):
    method test_codereview_normal_review (line 150) | async def test_codereview_normal_review(self):
    method test_analyze_normal_question (line 236) | async def test_analyze_normal_question(self):
    method test_empty_optional_fields (line 294) | async def test_empty_optional_fields(self):
    method test_thinking_modes_work (line 316) | async def test_thinking_modes_work(self):
    method test_special_characters_in_prompts (line 341) | async def test_special_characters_in_prompts(self):
    method test_mixed_file_paths (line 367) | async def test_mixed_file_paths(self):
    method test_unicode_content (line 415) | async def test_unicode_content(self):

FILE: tests/test_prompt_size_limit_bug_fix.py
  class TestPromptSizeLimitBugFix (line 12) | class TestPromptSizeLimitBugFix:
    method test_prompt_size_validation_with_conversation_history (line 15) | def test_prompt_size_validation_with_conversation_history(self):
    method test_prompt_size_validation_without_original_prompt (line 57) | def test_prompt_size_validation_without_original_prompt(self):
    method test_prompt_size_validation_with_missing_original_prompt (line 71) | def test_prompt_size_validation_with_missing_original_prompt(self):
    method test_base_tool_default_behavior (line 89) | def test_base_tool_default_behavior(self):

FILE: tests/test_provider_retry_logic.py
  function _mock_chat_response (line 10) | def _mock_chat_response(content: str = "retry success") -> SimpleNamespace:
  function test_openai_provider_retries_on_transient_error (line 19) | def test_openai_provider_retries_on_transient_error(monkeypatch):
  function test_openai_provider_bails_on_non_retryable_error (line 45) | def test_openai_provider_bails_on_non_retryable_error(monkeypatch):

FILE: tests/test_provider_routing_bugs.py
  class MockRequest (line 22) | class MockRequest(ToolRequest):
  class TestProviderRoutingBugs (line 28) | class TestProviderRoutingBugs:
    method setup_method (line 31) | def setup_method(self):
    method teardown_method (line 43) | def teardown_method(self):
    method test_fallback_routing_bug_reproduction (line 51) | def test_fallback_routing_bug_reproduction(self):
    method test_fallback_should_not_register_without_api_key (line 115) | def test_fallback_should_not_register_without_api_key(self):
    method test_mixed_api_keys_correct_routing (line 167) | def test_mixed_api_keys_correct_routing(self):
  class TestOpenRouterAliasRestrictions (line 223) | class TestOpenRouterAliasRestrictions:
    method setup_method (line 226) | def setup_method(self):
    method teardown_method (line 238) | def teardown_method(self):
    method test_openrouter_alias_restrictions_bug_reproduction (line 246) | def test_openrouter_alias_restrictions_bug_reproduction(self):
    method test_openrouter_mixed_alias_and_full_names (line 322) | def test_openrouter_mixed_alias_and_full_names(self):
  class TestProviderMetadataBug (line 374) | class TestProviderMetadataBug:
    method test_provider_used_metadata_included (line 377) | def test_provider_used_metadata_included(self):

FILE: tests/test_provider_utf8.py
  class TestProviderUTF8Encoding (line 18) | class TestProviderUTF8Encoding(unittest.TestCase):
    method setUp (line 21) | def setUp(self):
    method tearDown (line 25) | def tearDown(self):
    method test_base_provider_utf8_support (line 32) | def test_base_provider_utf8_support(self):
    method test_gemini_provider_utf8_request (line 46) | def test_gemini_provider_utf8_request(self, mock_model_class):
    method test_openai_provider_utf8_logging (line 86) | def test_openai_provider_utf8_logging(self, mock_openai_class):
    method test_openai_compatible_o3_pro_utf8 (line 118) | def test_openai_compatible_o3_pro_utf8(self, mock_openai_class):
    method test_provider_type_enum_utf8_safe (line 158) | def test_provider_type_enum_utf8_safe(self):
    method test_model_response_utf8_serialization (line 178) | def test_model_response_utf8_serialization(self):
    method test_error_handling_with_utf8 (line 217) | def test_error_handling_with_utf8(self):
    method test_temperature_handling_utf8_locale (line 233) | def test_temperature_handling_utf8_locale(self):
    method test_provider_registry_utf8 (line 252) | def test_provider_registry_utf8(self):
    method test_gemini_provider_handles_api_encoding_error (line 280) | def test_gemini_provider_handles_api_encoding_error(self, mock_model_c...
  class DummyToolForLocaleTest (line 302) | class DummyToolForLocaleTest:
    method get_language_instruction (line 305) | def get_language_instruction(self):
  class TestLocaleModelIntegration (line 312) | class TestLocaleModelIntegration(unittest.TestCase):
    method setUp (line 315) | def setUp(self):
    method tearDown (line 319) | def tearDown(self):
    method test_system_prompt_enhancement_french (line 326) | def test_system_prompt_enhancement_french(self):
    method test_system_prompt_enhancement_multiple_locales (line 336) | def test_system_prompt_enhancement_multiple_locales(self):
    method test_model_name_resolution_utf8 (line 351) | def test_model_name_resolution_utf8(self):
    method test_system_prompt_enhancement_with_unusual_locale_formats (line 370) | def test_system_prompt_enhancement_with_unusual_locale_formats(self):

FILE: tests/test_providers.py
  class TestModelProviderRegistry (line 14) | class TestModelProviderRegistry:
    method setup_method (line 17) | def setup_method(self):
    method teardown_method (line 25) | def teardown_method(self):
    method test_register_provider (line 33) | def test_register_provider(self):
    method test_get_provider (line 42) | def test_get_provider(self):
    method test_get_provider_no_api_key (line 53) | def test_get_provider_no_api_key(self):
    method test_get_provider_for_model (line 63) | def test_get_provider_for_model(self):
    method test_get_available_providers (line 72) | def test_get_available_providers(self):
  class TestGeminiProvider (line 84) | class TestGeminiProvider:
    method test_provider_initialization (line 87) | def test_provider_initialization(self):
    method test_get_capabilities (line 94) | def test_get_capabilities(self):
    method test_get_capabilities_pro_model (line 105) | def test_get_capabilities_pro_model(self):
    method test_model_shorthand_resolution (line 113) | def test_model_shorthand_resolution(self):
    method test_generate_content (line 124) | def test_generate_content(self, mock_client_class):
  class TestOpenAIProvider (line 155) | class TestOpenAIProvider:
    method setup_method (line 158) | def setup_method(self):
    method teardown_method (line 164) | def teardown_method(self):
    method test_provider_initialization (line 170) | def test_provider_initialization(self):
    method test_get_capabilities_o3 (line 178) | def test_get_capabilities_o3(self):
    method test_get_capabilities_o4_mini (line 189) | def test_get_capabilities_o4_mini(self):
    method test_validate_model_names (line 202) | def test_validate_model_names(self):
    method test_openai_models_do_not_support_extended_thinking (line 218) | def test_openai_models_do_not_support_extended_thinking(self):
    method test_gpt52_family_capabilities (line 226) | def test_gpt52_family_capabilities(self):

FILE: tests/test_rate_limit_patterns.py
  function test_openai_structured_error_retry_logic (line 9) | def test_openai_structured_error_retry_logic():
  function test_gemini_structured_error_retry_logic (line 45) | def test_gemini_structured_error_retry_logic():
  function test_actual_log_error_from_issue_with_structured_parsing (line 75) | def test_actual_log_error_from_issue_with_structured_parsing():
  function test_non_429_errors_still_work (line 93) | def test_non_429_errors_still_work():
  function test_edge_cases_and_fallbacks (line 121) | def test_edge_cases_and_fallbacks():

FILE: tests/test_refactor.py
  class TestRefactorTool (line 13) | class TestRefactorTool:
    method refactor_tool (line 17) | def refactor_tool(self):
    method mock_model_response (line 22) | def mock_model_response(self):
    method test_get_name (line 64) | def test_get_name(self, refactor_tool):
    method test_get_description (line 68) | def test_get_description(self, refactor_tool):
    method test_get_input_schema (line 77) | def test_get_input_schema(self, refactor_tool):
    method test_model_category (line 104) | def test_model_category(self, refactor_tool):
    method test_default_temperature (line 111) | def test_default_temperature(self, refactor_tool):
  class TestFileUtilsLineNumbers (line 121) | class TestFileUtilsLineNumbers:
    method test_read_file_content_with_line_numbers (line 124) | def test_read_file_content_with_line_numbers(self, project_path):
    method test_read_file_content_without_line_numbers (line 142) | def test_read_file_content_without_line_numbers(self, project_path):
    method test_read_file_content_auto_detect_programming (line 159) | def test_read_file_content_auto_detect_programming(self, project_path):
    method test_read_file_content_auto_detect_text (line 175) | def test_read_file_content_auto_detect_text(self, project_path):
    method test_line_ending_normalization (line 190) | def test_line_ending_normalization(self):
    method test_detect_file_type (line 213) | def test_detect_file_type(self):
    method test_should_add_line_numbers (line 230) | def test_should_add_line_numbers(self):
    method test_line_numbers_double_triple_digits (line 245) | def test_line_numbers_double_triple_digits(self, project_path):
    method test_line_numbers_with_file_reading (line 286) | def test_line_numbers_with_file_reading(self, project_path):
    method test_line_numbers_large_files_22k_lines (line 332) | def test_line_numbers_large_files_22k_lines(self, project_path):
    method test_line_numbers_boundary_conditions (line 367) | def test_line_numbers_boundary_conditions(self):

FILE: tests/test_secaudit.py
  class TestSecauditTool (line 11) | class TestSecauditTool:
    method test_tool_metadata (line 14) | def test_tool_metadata(self):
    method test_request_validation (line 24) | def test_request_validation(self):
    method test_request_validation_defaults (line 49) | def test_request_validation_defaults(self):
    method test_request_validation_invalid_threat_level (line 64) | def test_request_validation_invalid_threat_level(self):
    method test_request_validation_invalid_audit_focus (line 76) | def test_request_validation_invalid_audit_focus(self):
    method test_input_schema_generation (line 88) | def test_input_schema_generation(self):
    method test_step_guidance_step_1 (line 110) | def test_step_guidance_step_1(self):
    method test_step_guidance_step_2 (line 131) | def test_step_guidance_step_2(self):
    method test_step_guidance_step_4 (line 152) | def test_step_guidance_step_4(self):
    method test_expert_analysis_trigger (line 169) | def test_expert_analysis_trigger(self):
    method test_expert_analysis_context_preparation (line 200) | def test_expert_analysis_context_preparation(self):
    method test_security_issues_formatting_empty (line 255) | def test_security_issues_formatting_empty(self):
    method test_security_issues_formatting_with_issues (line 261) | def test_security_issues_formatting_with_issues(self):
    method test_tool_field_definitions (line 284) | def test_tool_field_definitions(self):
    method test_workflow_request_model (line 320) | def test_workflow_request_model(self):
    method test_workflow_system_prompt (line 326) | def test_workflow_system_prompt(self):
    method test_compliance_requirements_validation (line 337) | def test_compliance_requirements_validation(self):
    method test_comprehensive_workflow_scenario (line 362) | def test_comprehensive_workflow_scenario(self):

FILE: tests/test_server.py
  class TestServerTools (line 10) | class TestServerTools:
    method test_handle_call_tool_unknown (line 14) | async def test_handle_call_tool_unknown(self):
    method test_handle_chat (line 21) | async def test_handle_chat(self):
    method test_handle_version (line 90) | async def test_handle_version(self):

FILE: tests/test_supported_models_aliases.py
  class TestSupportedModelsAliases (line 9) | class TestSupportedModelsAliases:
    method test_gemini_provider_aliases (line 12) | def test_gemini_provider_aliases(self):
    method test_openai_provider_aliases (line 40) | def test_openai_provider_aliases(self):
    method test_xai_provider_aliases (line 77) | def test_xai_provider_aliases(self):
    method test_dial_provider_aliases (line 101) | def test_dial_provider_aliases(self):
    method test_list_models_includes_aliases (line 127) | def test_list_models_includes_aliases(self):
    method test_list_models_all_known_variant_includes_aliases (line 159) | def test_list_models_all_known_variant_includes_aliases(self):
    method test_no_string_shorthand_in_supported_models (line 191) | def test_no_string_shorthand_in_supported_models(self):
    method test_resolve_returns_original_if_not_found (line 210) | def test_resolve_returns_original_if_not_found(self):

FILE: tests/test_thinking_modes.py
  function setup_test_env (line 16) | def setup_test_env():
  class TestThinkingModes (line 22) | class TestThinkingModes:
    method test_default_thinking_modes (line 26) | def test_default_thinking_modes(self):
    method test_thinking_mode_minimal (line 41) | async def test_thinking_mode_minimal(self):
    method test_thinking_mode_low (line 124) | async def test_thinking_mode_low(self):
    method test_thinking_mode_medium (line 203) | async def test_thinking_mode_medium(self):
    method test_thinking_mode_high (line 283) | async def test_thinking_mode_high(self):
    method test_thinking_mode_max (line 362) | async def test_thinking_mode_max(self):

FILE: tests/test_tools.py
  class TestThinkDeepTool (line 15) | class TestThinkDeepTool:
    method tool (line 19) | def tool(self):
    method test_tool_metadata (line 22) | def test_tool_metadata(self, tool):
    method test_execute_success (line 41) | async def test_execute_success(self, tool):
  class TestCodeReviewTool (line 116) | class TestCodeReviewTool:
    method tool (line 120) | def tool(self):
    method test_tool_metadata (line 123) | def test_tool_metadata(self, tool):
    method test_execute_with_review_type (line 135) | async def test_execute_with_review_type(self, tool, tmp_path):
  class TestAnalyzeTool (line 209) | class TestAnalyzeTool:
    method tool (line 213) | def tool(self):
    method test_tool_metadata (line 216) | def test_tool_metadata(self, tool):
    method test_execute_with_analysis_type (line 237) | async def test_execute_with_analysis_type(self, tool, tmp_path):
  class TestAbsolutePathValidation (line 313) | class TestAbsolutePathValidation:
    method test_thinkdeep_tool_relative_path_rejected (line 325) | async def test_thinkdeep_tool_relative_path_rejected(self):
    method test_chat_tool_relative_path_rejected (line 346) | async def test_chat_tool_relative_path_rejected(self):
    method test_analyze_tool_accepts_absolute_paths (line 368) | async def test_analyze_tool_accepts_absolute_paths(self):
  class TestSpecialStatusModels (line 440) | class TestSpecialStatusModels:
    method test_trace_complete_status_in_registry (line 443) | def test_trace_complete_status_in_registry(self):
    method test_trace_complete_model_validation (line 450) | def test_trace_complete_model_validation(self):

FILE: tests/test_tracer.py
  class TestTracerTool (line 11) | class TestTracerTool:
    method tracer_tool (line 15) | def tracer_tool(self):
    method test_get_name (line 19) | def test_get_name(self, tracer_tool):
    method test_get_description (line 23) | def test_get_description(self, tracer_tool):
    method test_get_input_schema (line 31) | def test_get_input_schema(self, tracer_tool):
    method test_get_model_category (line 51) | def test_get_model_category(self, tracer_tool):
    method test_request_model_validation (line 56) | def test_request_model_validation(self, tracer_tool):
    method test_get_required_actions (line 83) | def test_get_required_actions(self, tracer_tool):
    method test_workflow_tool_characteristics (line 110) | def test_workflow_tool_characteristics(self, tracer_tool):
    method test_get_rendering_instructions_precision (line 121) | def test_get_rendering_instructions_precision(self, tracer_tool):
    method test_get_rendering_instructions_dependencies (line 131) | def test_get_rendering_instructions_dependencies(self, tracer_tool):
    method test_rendering_instructions_consistency (line 143) | def test_rendering_instructions_consistency(self, tracer_tool):
    method test_mode_selection_guidance (line 160) | def test_mode_selection_guidance(self, tracer_tool):

FILE: tests/test_utf8_localization.py
  class MockTestTool (line 22) | class MockTestTool(BaseTool):
    method __init__ (line 25) | def __init__(self):
    method get_name (line 28) | def get_name(self) -> str:
    method get_description (line 31) | def get_description(self) -> str:
    method get_input_schema (line 34) | def get_input_schema(self) -> dict:
    method get_system_prompt (line 37) | def get_system_prompt(self) -> str:
    method get_request_model (line 40) | def get_request_model(self):
    method prepare_prompt (line 45) | async def prepare_prompt(self, request) -> str:
    method execute (line 48) | async def execute(self, arguments: dict) -> list:
  class TestUTF8Localization (line 52) | class TestUTF8Localization(unittest.TestCase):
    method setUp (line 55) | def setUp(self):
    method tearDown (line 59) | def tearDown(self):
    method test_language_instruction_generation_french (line 66) | def test_language_instruction_generation_french(self):
    method test_language_instruction_generation_english (line 78) | def test_language_instruction_generation_english(self):
    method test_language_instruction_empty_locale (line 89) | def test_language_instruction_empty_locale(self):
    method test_language_instruction_no_locale (line 100) | def test_language_instruction_no_locale(self):
    method test_json_dumps_utf8_encoding (line 111) | def test_json_dumps_utf8_encoding(self):
    method test_json_dumps_ascii_encoding_comparison (line 144) | def test_json_dumps_ascii_encoding_comparison(self):
    method test_french_characters_in_file_content (line 160) | def test_french_characters_in_file_content(self):
    method test_unicode_normalization (line 218) | def test_unicode_normalization(self):
    method test_emoji_preservation (line 238) | def test_emoji_preservation(self):
  class TestLocalizationIntegration (line 271) | class TestLocalizationIntegration(unittest.TestCase):
    method setUp (line 274) | def setUp(self):
    method tearDown (line 278) | def tearDown(self):
    method test_codereview_tool_french_locale_simple (line 285) | def test_codereview_tool_french_locale_simple(self):
    method test_multiple_locales_switching (line 313) | def test_multiple_locales_switching(self):
  function run_async_test (line 351) | def run_async_test(test_func):

FILE: tests/test_utils.py
  class TestFileUtils (line 8) | class TestFileUtils:
    method test_read_file_content_success (line 11) | def test_read_file_content_success(self, project_path):
    method test_read_file_content_not_found (line 23) | def test_read_file_content_not_found(self, project_path):
    method test_read_file_content_dangerous_files_blocked (line 32) | def test_read_file_content_dangerous_files_blocked(self):
    method test_read_file_content_relative_path_rejected (line 40) | def test_read_file_content_relative_path_rejected(self):
    method test_read_file_content_directory (line 48) | def test_read_file_content_directory(self, project_path):
    method test_read_files_multiple (line 55) | def test_read_files_multiple(self, project_path):
    method test_read_files_with_code (line 73) | def test_read_files_with_code(self):
    method test_read_files_directory_support (line 85) | def test_read_files_directory_support(self, project_path):
    method test_read_files_mixed_paths (line 124) | def test_read_files_mixed_paths(self, project_path):
    method test_read_files_token_limit (line 149) | def test_read_files_token_limit(self, project_path):
    method test_read_files_large_file (line 170) | def test_read_files_large_file(self, project_path):
    method test_read_files_file_extensions (line 183) | def test_read_files_file_extensions(self, project_path):
  class TestTokenUtils (line 202) | class TestTokenUtils:
    method test_estimate_tokens (line 205) | def test_estimate_tokens(self):
    method test_check_token_limit_within (line 211) | def test_check_token_limit_within(self):
    method test_check_token_limit_exceeded (line 218) | def test_check_token_limit_exceeded(self):

FILE: tests/test_uvx_resource_packaging.py
  class TestUvxPathResolution (line 11) | class TestUvxPathResolution:
    method test_normal_operation (line 14) | def test_normal_operation(self):
    method test_config_path_resolution (line 20) | def test_config_path_resolution(self):
    method test_explicit_config_path_override (line 37) | def test_explicit_config_path_override(self):
    method test_environment_variable_override (line 47) | def test_environment_variable_override(self):
    method test_multiple_path_fallback (line 59) | def test_multiple_path_fallback(self, mock_files):
    method test_missing_config_handling (line 100) | def test_missing_config_handling(self):
    method test_resource_loading_success (line 110) | def test_resource_loading_success(self):
    method test_use_resources_attribute (line 120) | def test_use_resources_attribute(self):

FILE: tests/test_uvx_support.py
  class TestUvxEnvironmentHandling (line 14) | class TestUvxEnvironmentHandling:
    method test_dotenv_import_success (line 17) | def test_dotenv_import_success(self):
    method test_dotenv_import_failure_graceful_handling (line 50) | def test_dotenv_import_failure_graceful_handling(self):
    method test_env_file_path_resolution (line 70) | def test_env_file_path_resolution(self):
    method test_environment_variables_still_work_without_dotenv (line 82) | def test_environment_variables_still_work_without_dotenv(self):
    method test_dotenv_graceful_fallback_behavior (line 92) | def test_dotenv_graceful_fallback_behavior(self):
  class TestUvxProjectConfiguration (line 109) | class TestUvxProjectConfiguration:
    method test_pyproject_toml_has_required_uvx_fields (line 112) | def test_pyproject_toml_has_required_uvx_fields(self):
    method test_pyproject_dependencies_match_requirements (line 145) | def test_pyproject_dependencies_match_requirements(self):
    method test_uvx_entry_point_callable (line 178) | def test_uvx_entry_point_callable(self):

FILE: tests/test_workflow_file_embedding.py
  class TestWorkflowFileEmbedding (line 18) | class TestWorkflowFileEmbedding:
    method setup_method (line 21) | def setup_method(self):
    method teardown_method (line 43) | def teardown_method(self):
    method test_intermediate_step_no_embedding (line 51) | def test_intermediate_step_no_embedding(self):
    method test_intermediate_step_with_continuation_no_embedding (line 62) | def test_intermediate_step_with_continuation_no_embedding(self):
    method test_final_step_embeds_files (line 73) | def test_final_step_embeds_files(self):
    method test_final_step_new_conversation_embeds_files (line 84) | def test_final_step_new_conversation_embeds_files(self):
    method test_comprehensive_file_collection_for_expert_analysis (line 99) | def test_comprehensive_file_collection_for_expert_analysis(
    method test_force_embed_bypasses_conversation_history (line 170) | def test_force_embed_bypasses_conversation_history(self, mock_expand_p...
    method test_embedding_decision_logic_comprehensive (line 204) | def test_embedding_decision_logic_comprehensive(self):

FILE: tests/test_workflow_metadata.py
  class TestWorkflowMetadata (line 19) | class TestWorkflowMetadata:
    method setup_method (line 22) | def setup_method(self):
    method teardown_method (line 34) | def teardown_method(self):
    method test_workflow_metadata_in_response (line 42) | def test_workflow_metadata_in_response(self):
    method test_workflow_metadata_in_error_response (line 129) | def test_workflow_metadata_in_error_response(self):
    method test_workflow_metadata_fallback_handling (line 194) | def test_workflow_metadata_fallback_handling(self):
    method test_workflow_metadata_preserves_existing_response_fields (line 254) | def test_workflow_metadata_preserves_existing_response_fields(self):

FILE: tests/test_workflow_prompt_size_validation_simple.py
  function build_debug_arguments (line 18) | def build_debug_arguments(**overrides) -> dict[str, object]:
  function test_workflow_tool_accepts_normal_step_content (line 41) | async def test_workflow_tool_accepts_normal_step_content() -> None:
  function test_workflow_tool_rejects_oversized_step_with_guidance (line 57) | async def test_workflow_tool_rejects_oversized_step_with_guidance() -> N...

FILE: tests/test_workflow_utf8.py
  class TestWorkflowToolsUTF8 (line 16) | class TestWorkflowToolsUTF8(unittest.IsolatedAsyncioTestCase):
    method setUp (line 19) | def setUp(self):
    method tearDown (line 25) | def tearDown(self):
    method test_workflow_json_response_structure (line 32) | def test_workflow_json_response_structure(self):
    method test_analyze_tool_utf8_response (line 63) | async def test_analyze_tool_utf8_response(self, mock_model_context, mo...
    method test_codereview_tool_french_findings (line 130) | async def test_codereview_tool_french_findings(self, mock_get_provider):
    method test_debug_tool_french_error_analysis (line 203) | async def test_debug_tool_french_error_analysis(self, mock_get_provider):
    method test_utf8_emoji_preservation_in_workflow_responses (line 266) | def test_utf8_emoji_preservation_in_workflow_responses(self):

FILE: tests/test_xai_provider.py
  class TestXAIProvider (line 12) | class TestXAIProvider:
    method setup_method (line 15) | def setup_method(self):
    method teardown_method (line 22) | def teardown_method(self):
    method test_initialization (line 30) | def test_initialization(self):
    method test_initialization_with_custom_url (line 37) | def test_initialization_with_custom_url(self):
    method test_model_validation (line 43) | def test_model_validation(self):
    method test_resolve_model_name (line 66) | def test_resolve_model_name(self):
    method test_get_capabilities_grok4 (line 80) | def test_get_capabilities_grok4(self):
    method test_get_capabilities_grok4_1_fast (line 101) | def test_get_capabilities_grok4_1_fast(self):
    method test_get_capabilities_with_shorthand (line 115) | def test_get_capabilities_with_shorthand(self):
    method test_unsupported_model_capabilities (line 126) | def test_unsupported_model_capabilities(self):
    method test_extended_thinking_flags (line 133) | def test_extended_thinking_flags(self):
    method test_provider_type (line 148) | def test_provider_type(self):
    method test_model_restrictions (line 154) | def test_model_restrictions(self):
    method test_multiple_model_restrictions (line 174) | def test_multiple_model_restrictions(self):
    method test_both_shorthand_and_full_name_allowed (line 195) | def test_both_shorthand_and_full_name_allowed(self):
    method test_empty_restrictions_allows_all (line 211) | def test_empty_restrictions_allows_all(self):
    method test_friendly_name (line 226) | def test_friendly_name(self):
    method test_supported_models_structure (line 234) | def test_supported_models_structure(self):
    method test_generate_content_resolves_alias_before_api_call (line 265) | def test_generate_content_resolves_alias_before_api_call(self, mock_op...
    method test_generate_content_other_aliases (line 315) | def test_generate_content_other_aliases(self, mock_openai_class):

FILE: tests/transport_helpers.py
  function inject_transport (line 6) | def inject_transport(monkeypatch, cassette_path: str):

FILE: tools/analyze.py
  class AnalyzeWorkflowRequest (line 83) | class AnalyzeWorkflowRequest(WorkflowRequest):
    method validate_step_one_requirements (line 125) | def validate_step_one_requirements(self):
  class AnalyzeTool (line 133) | class AnalyzeTool(WorkflowTool):
    method __init__ (line 143) | def __init__(self):
    method get_name (line 148) | def get_name(self) -> str:
    method get_description (line 151) | def get_description(self) -> str:
    method get_system_prompt (line 158) | def get_system_prompt(self) -> str:
    method get_default_temperature (line 161) | def get_default_temperature(self) -> float:
    method get_model_category (line 164) | def get_model_category(self) -> "ToolModelCategory":
    method get_workflow_request_model (line 170) | def get_workflow_request_model(self):
    method get_input_schema (line 174) | def get_input_schema(self) -> dict[str, Any]:
    method get_required_actions (line 253) | def get_required_actions(
    method should_call_expert_analysis (line 288) | def should_call_expert_analysis(self, consolidated_findings, request=N...
    method prepare_expert_analysis_context (line 301) | def prepare_expert_analysis_context(self, consolidated_findings) -> str:
    method _build_analysis_summary (line 339) | def _build_analysis_summary(self, consolidated_findings) -> str:
    method should_include_files_in_expert_prompt (line 356) | def should_include_files_in_expert_prompt(self) -> bool:
    method should_embed_system_prompt (line 360) | def should_embed_system_prompt(self) -> bool:
    method get_expert_thinking_mode (line 364) | def get_expert_thinking_mode(self) -> str:
    method get_expert_analysis_instruction (line 368) | def get_expert_analysis_instruction(self) -> str:
    method prepare_step_data (line 378) | def prepare_step_data(self, request) -> dict:
    method should_skip_expert_analysis (line 396) | def should_skip_expert_analysis(self, request, consolidated_findings) ...
    method store_initial_issue (line 405) | def store_initial_issue(self, step_description: str):
    method get_completion_status (line 411) | def get_completion_status(self) -> str:
    method get_completion_data_key (line 415) | def get_completion_data_key(self) -> str:
    method get_final_analysis_from_request (line 419) | def get_final_analysis_from_request(self, request):
    method get_confidence_level (line 423) | def get_confidence_level(self, request) -> str:
    method get_completion_message (line 427) | def get_completion_message(self) -> str:
    method get_skip_reason (line 437) | def get_skip_reason(self) -> str:
    method get_skip_expert_analysis_status (line 441) | def get_skip_expert_analysis_status(self) -> str:
    method prepare_work_summary (line 445) | def prepare_work_summary(self) -> str:
    method get_completion_next_steps_message (line 449) | def get_completion_next_steps_message(self, expert_analysis_used: bool...
    method get_expert_analysis_guidance (line 469) | def get_expert_analysis_guidance(self) -> str:
    method get_step_guidance_message (line 483) | def get_step_guidance_message(self, request) -> str:
    method get_analyze_step_guidance (line 490) | def get_analyze_step_guidance(self, step_number: int, request) -> dict...
    method customize_workflow_response (line 527) | def customize_workflow_response(self, response_data: dict, request) ->...
    method get_request_model (line 577) | def get_request_model(self):
    method prepare_prompt (line 581) | async def prepare_prompt(self, request) -> str:

FILE: tools/apilookup.py
  class LookupRequest (line 23) | class LookupRequest(ToolRequest):
  class LookupTool (line 70) | class LookupTool(SimpleTool):
    method get_name (line 73) | def get_name(self) -> str:
    method get_description (line 76) | def get_description(self) -> str:
    method get_system_prompt (line 82) | def get_system_prompt(self) -> str:
    method get_default_temperature (line 85) | def get_default_temperature(self) -> float:
    method requires_model (line 88) | def requires_model(self) -> bool:
    method get_model_category (line 91) | def get_model_category(self) -> ToolModelCategory:
    method get_request_model (line 96) | def get_request_model(self):
    method get_tool_fields (line 99) | def get_tool_fields(self) -> dict[str, dict[str, Any]]:
    method prepare_prompt (line 107) | async def prepare_prompt(self, request) -> str:  # pragma: no cover - ...
    method get_input_schema (line 110) | def get_input_schema(self) -> dict[str, Any]:
    method execute (line 122) | async def execute(self, arguments: dict[str, Any]) -> list:

FILE: tools/challenge.py
  class ChallengeRequest (line 33) | class ChallengeRequest(ToolRequest):
  class ChallengeTool (line 39) | class ChallengeTool(SimpleTool):
    method get_name (line 52) | def get_name(self) -> str:
    method get_description (line 55) | def get_description(self) -> str:
    method get_system_prompt (line 61) | def get_system_prompt(self) -> str:
    method get_default_temperature (line 65) | def get_default_temperature(self) -> float:
    method get_model_category (line 68) | def get_model_category(self) -> "ToolModelCategory":
    method requires_model (line 74) | def requires_model(self) -> bool:
    method get_request_model (line 86) | def get_request_model(self):
    method get_input_schema (line 90) | def get_input_schema(self) -> dict[str, Any]:
    method execute (line 109) | async def execute(self, arguments: dict[str, Any]) -> list:
    method _wrap_prompt_for_challenge (line 158) | def _wrap_prompt_for_challenge(self, prompt: str) -> str:
    method prepare_prompt (line 179) | async def prepare_prompt(self, request: ChallengeRequest) -> str:
    method format_response (line 183) | def format_response(self, response: str, request: ChallengeRequest, mo...
    method get_tool_fields (line 187) | def get_tool_fields(self) -> dict[str, dict[str, Any]]:
    method get_required_fields (line 196) | def get_required_fields(self) -> list[str]:

FILE: tools/chat.py
  class ChatRequest (line 42) | class ChatRequest(ToolRequest):
  class ChatTool (line 57) | class ChatTool(SimpleTool):
    method __init__ (line 68) | def __init__(self) -> None:
    method get_name (line 72) | def get_name(self) -> str:
    method get_description (line 75) | def get_description(self) -> str:
    method get_annotations (line 81) | def get_annotations(self) -> Optional[dict[str, Any]]:
    method get_system_prompt (line 86) | def get_system_prompt(self) -> str:
    method get_capability_system_prompts (line 89) | def get_capability_system_prompts(self, capabilities: Optional["ModelC...
    method get_default_temperature (line 95) | def get_default_temperature(self) -> float:
    method get_model_category (line 98) | def get_model_category(self) -> "ToolModelCategory":
    method get_request_model (line 104) | def get_request_model(self):
    method get_input_schema (line 110) | def get_input_schema(self) -> dict[str, Any]:
    method get_tool_fields (line 161) | def get_tool_fields(self) -> dict[str, dict[str, Any]]:
    method get_required_fields (line 185) | def get_required_fields(self) -> list[str]:
    method prepare_prompt (line 191) | async def prepare_prompt(self, request: ChatRequest) -> str:
    method _validate_file_paths (line 201) | def _validate_file_paths(self, request) -> Optional[str]:
    method format_response (line 236) | def format_response(self, response: str, request: ChatRequest, model_i...
    method _record_assistant_turn (line 300) | def _record_assistant_turn(
    method _model_supports_code_generation (line 309) | def _model_supports_code_generation(self) -> bool:
    method _extract_generated_code_block (line 321) | def _extract_generated_code_block(self, text: str) -> tuple[Optional[s...
    method _persist_generated_code_block (line 336) | def _persist_generated_code_block(self, block: str, working_directory:...
    method _build_agent_instruction (line 355) | def _build_agent_instruction(artifact_path: Path) -> str:
    method _join_sections (line 373) | def _join_sections(*sections: str) -> str:
    method get_websearch_guidance (line 382) | def get_websearch_guidance(self) -> str:

FILE: tools/clink.py
  class CLinkRequest (line 29) | class CLinkRequest(BaseModel):
  class CLinkTool (line 55) | class CLinkTool(SimpleTool):
    method __init__ (line 63) | def __init__(self) -> None:
    method get_name (line 76) | def get_name(self) -> str:
    method get_description (line 79) | def get_description(self) -> str:
    method get_annotations (line 85) | def get_annotations(self) -> dict[str, Any]:
    method requires_model (line 88) | def requires_model(self) -> bool:
    method get_model_category (line 91) | def get_model_category(self) -> ToolModelCategory:
    method get_default_temperature (line 94) | def get_default_temperature(self) -> float:
    method get_system_prompt (line 97) | def get_system_prompt(self) -> str:
    method get_request_model (line 100) | def get_request_model(self):
    method get_input_schema (line 103) | def get_input_schema(self) -> dict[str, Any]:
    method get_tool_fields (line 160) | def get_tool_fields(self) -> dict[str, dict[str, Any]]:
    method execute (line 164) | async def execute(self, arguments: dict[str, Any]) -> list[TextContent]:
    method prepare_prompt (line 261) | async def prepare_prompt(self, request) -> str:
    method _prepare_prompt_for_role (line 273) | async def _prepare_prompt_for_role(
    method _use_external_system_prompt (line 301) | def _use_external_system_prompt(self, client: ResolvedCLIClient) -> bool:
    method _build_success_metadata (line 305) | def _build_success_metadata(
    method _merge_metadata (line 328) | def _merge_metadata(self, base: dict[str, Any] | None, extra: dict[str...
    method _apply_output_limit (line 333) | def _apply_output_limit(
    method _extract_summary (line 399) | def _extract_summary(self, content: str) -> str | None:
    method _prune_metadata (line 406) | def _prune_metadata(
    method _build_error_metadata (line 425) | def _build_error_metadata(self, client: ResolvedCLIClient, exc: CLIAge...
    method _raise_tool_error (line 437) | def _raise_tool_error(self, message: str, metadata: dict[str, Any] | N...
    method _agent_capabilities_guidance (line 441) | def _agent_capabilities_guidance(self) -> str:
    method _format_file_references (line 449) | def _format_file_references(self, files: list[str]) -> str:

FILE: tools/codereview.py
  class CodeReviewRequest (line 64) | class CodeReviewRequest(WorkflowRequest):
    method validate_step_one_requirements (line 111) | def validate_step_one_requirements(self):
  class CodeReviewTool (line 118) | class CodeReviewTool(WorkflowTool):
    method __init__ (line 128) | def __init__(self):
    method get_name (line 133) | def get_name(self) -> str:
    method get_description (line 136) | def get_description(self) -> str:
    method get_system_prompt (line 143) | def get_system_prompt(self) -> str:
    method get_default_temperature (line 146) | def get_default_temperature(self) -> float:
    method get_model_category (line 149) | def get_model_category(self) -> "ToolModelCategory":
    method get_workflow_request_model (line 155) | def get_workflow_request_model(self):
    method get_input_schema (line 159) | def get_input_schema(self) -> dict[str, Any]:
    method get_required_actions (line 244) | def get_required_actions(
    method should_call_expert_analysis (line 306) | def should_call_expert_analysis(self, consolidated_findings, request=N...
    method prepare_expert_analysis_context (line 329) | def prepare_expert_analysis_context(self, consolidated_findings) -> str:
    method _build_code_review_summary (line 376) | def _build_code_review_summary(self, consolidated_findings) -> str:
    method should_include_files_in_expert_prompt (line 394) | def should_include_files_in_expert_prompt(self) -> bool:
    method should_embed_system_prompt (line 398) | def should_embed_system_prompt(self) -> bool:
    method get_expert_thinking_mode (line 402) | def get_expert_thinking_mode(self) -> str:
    method get_expert_analysis_instruction (line 406) | def get_expert_analysis_instruction(self) -> str:
    method prepare_step_data (line 417) | def prepare_step_data(self, request) -> dict:
    method should_skip_expert_analysis (line 436) | def should_skip_expert_analysis(self, request, consolidated_findings) ...
    method store_initial_issue (line 451) | def store_initial_issue(self, step_description: str):
    method get_review_validation_type (line 457) | def get_review_validation_type(self, request) -> str:
    method get_completion_status (line 464) | def get_completion_status(self) -> str:
    method get_completion_data_key (line 468) | def get_completion_data_key(self) -> str:
    method get_final_analysis_from_request (line 472) | def get_final_analysis_from_request(self, request):
    method get_confidence_level (line 476) | def get_confidence_level(self, request) -> str:
    method get_completion_message (line 480) | def get_completion_message(self) -> str:
    method get_skip_reason (line 489) | def get_skip_reason(self) -> str:
    method get_skip_expert_analysis_status (line 493) | def get_skip_expert_analysis_status(self) -> str:
    method prepare_work_summary (line 497) | def prepare_work_summary(self) -> str:
    method get_completion_next_steps_message (line 501) | def get_completion_next_steps_message(self, expert_analysis_used: bool...
    method get_expert_analysis_guidance (line 521) | def get_expert_analysis_guidance(self) -> str:
    method get_step_guidance_message (line 534) | def get_step_guidance_message(self, request) -> str:
    method get_code_review_step_guidance (line 541) | def get_code_review_step_guidance(self, step_number: int, request) -> ...
    method customize_workflow_response (line 666) | def customize_workflow_response(self, response_data: dict, request) ->...
    method get_request_model (line 718) | def get_request_model(self):
    method prepare_prompt (line 722) | async def prepare_prompt(self, request) -> str:

FILE: tools/consensus.py
  class ConsensusRequest (line 62) | class ConsensusRequest(WorkflowRequest):
    method validate_step_one_requirements (line 106) | def validate_step_one_requirements(self):
  class ConsensusTool (line 129) | class ConsensusTool(WorkflowTool):
    method __init__ (line 138) | def __init__(self):
    method get_name (line 146) | def get_name(self) -> str:
    method get_description (line 149) | def get_description(self) -> str:
    method get_system_prompt (line 156) | def get_system_prompt(self) -> str:
    method get_default_temperature (line 178) | def get_default_temperature(self) -> float:
    method get_model_category (line 181) | def get_model_category(self) -> ToolModelCategory:
    method get_workflow_request_model (line 187) | def get_workflow_request_model(self):
    method get_input_schema (line 191) | def get_input_schema(self) -> dict[str, Any]:
    method get_required_actions (line 318) | def get_required_actions(
    method should_call_expert_analysis (line 348) | def should_call_expert_analysis(self, consolidated_findings, request=N...
    method prepare_expert_analysis_context (line 352) | def prepare_expert_analysis_context(self, consolidated_findings) -> str:
    method requires_expert_analysis (line 356) | def requires_expert_analysis(self) -> bool:
    method requires_model (line 360) | def requires_model(self) -> bool:
    method prepare_step_data (line 373) | def prepare_step_data(self, request) -> dict:
    method handle_work_completion (line 389) | async def handle_work_completion(self, response_data: dict, request, a...
    method handle_work_continuation (line 413) | def handle_work_continuation(self, response_data: dict, request) -> dict:
    method execute_workflow (line 436) | async def execute_workflow(self, arguments: dict[str, Any]) -> list:
    method _build_continuation_offer (line 547) | def _build_continuation_offer(self, continuation_id: str) -> dict[str,...
    method _consult_model (line 574) | async def _consult_model(self, model_config: dict, request) -> dict:
    method _get_stance_enhanced_prompt (line 647) | def _get_stance_enhanced_prompt(self, stance: str, custom_stance_promp...
   
Condensed preview — 360 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,100K chars).
[
  {
    "path": ".claude/commands/fix-github-issue.md",
    "chars": 487,
    "preview": "Please analyze and fix the GitHub issue: $ARGUMENTS.\n\nFollow these steps:\n\n1. Use `gh issue view` to get the issue detai"
  },
  {
    "path": ".claude/settings.json",
    "chars": 63,
    "preview": "{\n  \"permissions\": {\n    \"allow\": [\n    ],\n    \"deny\": []\n  }\n}"
  },
  {
    "path": ".coveragerc",
    "chars": 410,
    "preview": "[run]\nsource = gemini_server\nomit = \n    */tests/*\n    */venv/*\n    */__pycache__/*\n    */site-packages/*\n\n[report]\nexcl"
  },
  {
    "path": ".dockerignore",
    "chars": 549,
    "preview": "# Git\n.git\n.gitignore\n\n# Python\n__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nenv/\nvenv/\n.venv/\n.pal_venv/\nENV/\nenv.bak"
  },
  {
    "path": ".gitattributes",
    "chars": 438,
    "preview": "# Ensure shell scripts always have LF line endings on checkout\n*.sh text eol=lf\n*.bash text eol=lf\n\n# Python files\n*.py "
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 68,
    "preview": "# These are supported funding model platforms\n\ngithub: [guidedways]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1907,
    "preview": "name: 🐞 Bug Report\ndescription: Create a report to help us improve\nlabels: [\"bug\", \"needs-triage\"]\nbody:\n  - type: markd"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 580,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 💬 General Discussion\n    url: https://github.com/BeehiveInnovations"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.yml",
    "chars": 1844,
    "preview": "name: 📖 Documentation Improvement\ndescription: Report an issue or suggest an improvement for the documentation\nlabels: ["
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 2054,
    "preview": "name: ✨ Feature Request\ndescription: Suggest an idea for this project\nlabels: [\"enhancement\", \"needs-triage\"]\nbody:\n  - "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/tool_addition.yml",
    "chars": 2774,
    "preview": "name: 🛠️ New Gemini Tool Proposal\ndescription: Propose a new PAL MCP tool (e.g., `summarize`, `fixer`, `refactor`)\nlabel"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 2966,
    "preview": "## PR Title Format\n\n**Please ensure your PR title follows [Conventional Commits](https://www.conventionalcommits.org/) f"
  },
  {
    "path": ".github/workflows/docker-pr.yml",
    "chars": 4325,
    "preview": "name: PR Docker Build\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened, labeled, unlabeled]\n    paths:\n   "
  },
  {
    "path": ".github/workflows/docker-release.yml",
    "chars": 3788,
    "preview": "name: Docker Release Build\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n    inputs:\n      tag:\n        de"
  },
  {
    "path": ".github/workflows/semantic-pr.yml",
    "chars": 1467,
    "preview": "---\nname: Semantic PR\n\non:\n  pull_request:\n    types: [opened, edited, synchronize]\n\nconcurrency:\n  group: ${{ github.wo"
  },
  {
    "path": ".github/workflows/semantic-release.yml",
    "chars": 2047,
    "preview": "name: Semantic Release\n\non:\n  push:\n    branches:\n      - main\n\npermissions:\n  contents: write\n  issues: write\n  pull-re"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1517,
    "preview": "name: Tests\n\non:\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matr"
  },
  {
    "path": ".gitignore",
    "chars": 2095,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 657,
    "preview": "---\ndefault_stages: [pre-commit, pre-push]\nrepos:\n  - repo: https://github.com/psf/black\n    rev: 25.1.0\n    hooks:\n    "
  },
  {
    "path": "AGENTS.md",
    "chars": 4801,
    "preview": "# Repository Guidelines\n\nSee `requirements.txt` and `requirements-dev.txt`\n\nAlso read CLAUDE.md and CLAUDE.local.md if a"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 45809,
    "preview": "# CHANGELOG\n\n<!-- version list -->\n\n## v9.8.2 (2025-12-15)\n\n### Bug Fixes\n\n- Allow home subdirectories through is_danger"
  },
  {
    "path": "CLAUDE.md",
    "chars": 11150,
    "preview": "# Claude Development Guide for PAL MCP Server\n\nThis file contains essential commands and workflows for developing and ma"
  },
  {
    "path": "Dockerfile",
    "chars": 2626,
    "preview": "# ===========================================\n# STAGE 1: Build dependencies\n# =========================================="
  },
  {
    "path": "LICENSE",
    "chars": 11052,
    "preview": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licens"
  },
  {
    "path": "README.md",
    "chars": 21068,
    "preview": "# PAL MCP: Many Workflows. One Context.\n\n<div align=\"center\">\n\n  <em>Your AI's PAL – a Provider Abstraction Layer</em><b"
  },
  {
    "path": "SECURITY.md",
    "chars": 3366,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 9.x.x   | "
  },
  {
    "path": "claude_config_example.json",
    "chars": 399,
    "preview": "{\n  \"comment\": \"Example Claude Desktop configuration for PAL MCP Server\",\n  \"comment2\": \"Run './run-server.sh -c' to get"
  },
  {
    "path": "clink/__init__.py",
    "chars": 175,
    "preview": "\"\"\"Public helpers for clink components.\"\"\"\n\nfrom __future__ import annotations\n\nfrom .registry import ClinkRegistry, get"
  },
  {
    "path": "clink/agents/__init__.py",
    "chars": 703,
    "preview": "\"\"\"Agent factory for clink CLI integrations.\"\"\"\n\nfrom __future__ import annotations\n\nfrom clink.models import ResolvedCL"
  },
  {
    "path": "clink/agents/base.py",
    "chars": 8099,
    "preview": "\"\"\"Execute configured CLI agents for the clink tool and parse output.\"\"\"\n\nfrom __future__ import annotations\n\nimport asy"
  },
  {
    "path": "clink/agents/claude.py",
    "chars": 1498,
    "preview": "\"\"\"Claude-specific CLI agent hooks.\"\"\"\n\nfrom __future__ import annotations\n\nfrom clink.models import ResolvedCLIRole\nfro"
  },
  {
    "path": "clink/agents/codex.py",
    "chars": 1111,
    "preview": "\"\"\"Codex-specific CLI agent hooks.\"\"\"\n\nfrom __future__ import annotations\n\nfrom clink.models import ResolvedCLIClient\nfr"
  },
  {
    "path": "clink/agents/gemini.py",
    "chars": 2528,
    "preview": "\"\"\"Gemini-specific CLI agent hooks.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom typing import Any\n\nfrom cli"
  },
  {
    "path": "clink/constants.py",
    "chars": 1524,
    "preview": "\"\"\"Internal defaults and constants for clink.\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass,"
  },
  {
    "path": "clink/models.py",
    "chars": 3409,
    "preview": "\"\"\"Pydantic models for clink configuration and runtime structures.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib "
  },
  {
    "path": "clink/parsers/__init__.py",
    "chars": 780,
    "preview": "\"\"\"Parser registry for clink.\"\"\"\n\nfrom __future__ import annotations\n\nfrom .base import BaseParser, ParsedCLIResponse, P"
  },
  {
    "path": "clink/parsers/base.py",
    "chars": 619,
    "preview": "\"\"\"Parser interfaces for clink runner outputs.\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass"
  },
  {
    "path": "clink/parsers/claude.py",
    "chars": 5287,
    "preview": "\"\"\"Parser for Claude CLI JSON output.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom typing import Any\n\nfrom ."
  },
  {
    "path": "clink/parsers/codex.py",
    "chars": 2197,
    "preview": "\"\"\"Parser for Codex CLI JSONL output.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom typing import Any\n\nfrom ."
  },
  {
    "path": "clink/parsers/gemini.py",
    "chars": 4330,
    "preview": "\"\"\"Parser for Gemini CLI JSON output.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom typing import Any\n\nfrom ."
  },
  {
    "path": "clink/registry.py",
    "chars": 8934,
    "preview": "\"\"\"Configuration registry for clink CLI integrations.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport logging"
  },
  {
    "path": "code_quality_checks.ps1",
    "chars": 8329,
    "preview": "<#\n.SYNOPSIS\n    Code quality checks script for PAL MCP server on Windows.\n\n.DESCRIPTION\n    This PowerShell script perf"
  },
  {
    "path": "code_quality_checks.sh",
    "chars": 3042,
    "preview": "#!/bin/bash\n\n# PAL MCP Server - Code Quality Checks\n# This script runs all required linting and testing checks before co"
  },
  {
    "path": "communication_simulator_test.py",
    "chars": 21167,
    "preview": "\"\"\"\nCommunication Simulator Test for PAL MCP Server\n\nThis script provides comprehensive end-to-end testing of the PAL MC"
  },
  {
    "path": "conf/__init__.py",
    "chars": 45,
    "preview": "\"\"\"Configuration data for PAL MCP Server.\"\"\"\n"
  },
  {
    "path": "conf/azure_models.json",
    "chars": 2839,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for Azure OpenAI / Azure AI Foundry-backed provider. The `models` de"
  },
  {
    "path": "conf/cli_clients/claude.json",
    "chars": 508,
    "preview": "{\n  \"name\": \"claude\",\n  \"command\": \"claude\",\n  \"additional_args\": [\n    \"--permission-mode\",\n    \"acceptEdits\",\n    \"--m"
  },
  {
    "path": "conf/cli_clients/codex.json",
    "chars": 537,
    "preview": "{\n  \"name\": \"codex\",\n  \"command\": \"codex\",\n  \"additional_args\": [\n    \"--json\",\n    \"--dangerously-bypass-approvals-and-"
  },
  {
    "path": "conf/cli_clients/gemini.json",
    "chars": 449,
    "preview": "{\n  \"name\": \"gemini\",\n  \"command\": \"gemini\",\n  \"additional_args\": [\n    \"--yolo\"\n  ],\n  \"env\": {},\n  \"roles\": {\n    \"def"
  },
  {
    "path": "conf/custom_models.json",
    "chars": 2252,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for local/self-hosted OpenAI-compatible endpoints (Custom provider)."
  },
  {
    "path": "conf/dial_models.json",
    "chars": 6879,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for the DIAL (Data & AI Layer) aggregation provider.\",\n    \"document"
  },
  {
    "path": "conf/gemini_models.json",
    "chars": 6059,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for Google's Gemini API access.\",\n    \"documentation\": \"https://gith"
  },
  {
    "path": "conf/openai_models.json",
    "chars": 12123,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for native OpenAI API access.\",\n    \"documentation\": \"https://github"
  },
  {
    "path": "conf/openrouter_models.json",
    "chars": 18010,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for OpenRouter-backed providers.\",\n    \"documentation\": \"https://git"
  },
  {
    "path": "conf/xai_models.json",
    "chars": 3702,
    "preview": "{\n  \"_README\": {\n    \"description\": \"Model metadata for X.AI (GROK) API access.\",\n    \"documentation\": \"https://github.c"
  },
  {
    "path": "config.py",
    "chars": 6876,
    "preview": "\"\"\"\nConfiguration and constants for PAL MCP Server\n\nThis module centralizes all configuration settings for the PAL MCP S"
  },
  {
    "path": "docker/README.md",
    "chars": 8157,
    "preview": "# PAL MCP Server - Docker Setup\n\n## Quick Start\n\n### 1. Prerequisites\n\n- Docker installed (Docker Compose optional)\n- At"
  },
  {
    "path": "docker/scripts/build.ps1",
    "chars": 2135,
    "preview": "#!/usr/bin/env pwsh\n#Requires -Version 5.1\n[CmdletBinding()]\nparam()\n\n# Set error action preference\n$ErrorActionPreferen"
  },
  {
    "path": "docker/scripts/build.sh",
    "chars": 1163,
    "preview": "#!/bin/bash\nset -euo pipefail\n\n# Colors for output\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nRED='\\033[0;31m'\nNC='\\033[0m'\n"
  },
  {
    "path": "docker/scripts/deploy.ps1",
    "chars": 6539,
    "preview": "#!/usr/bin/env pwsh\n#Requires -Version 5.1\n[CmdletBinding()]\nparam(\n    [switch]$SkipHealthCheck,\n    [int]$HealthCheckT"
  },
  {
    "path": "docker/scripts/deploy.sh",
    "chars": 3047,
    "preview": "#!/bin/bash\nset -euo pipefail\n\n# Colors for output\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nRED='\\033[0;31m'\nNC='\\033[0m'\n"
  },
  {
    "path": "docker/scripts/healthcheck.py",
    "chars": 3264,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nHealth check script for PAL MCP Server Docker container\n\"\"\"\n\nimport os\nimport subprocess\nimpo"
  },
  {
    "path": "docker-compose.yml",
    "chars": 2601,
    "preview": "services:\n  pal-mcp:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: runtime\n    image: pal-mcp-s"
  },
  {
    "path": "docs/adding_providers.md",
    "chars": 11671,
    "preview": "# Adding a New Provider\n\nThis guide explains how to add support for a new AI model provider to the PAL MCP Server. The p"
  },
  {
    "path": "docs/adding_tools.md",
    "chars": 8280,
    "preview": "# Adding Tools to PAL MCP Server\n\nPAL MCP tools are Python classes that inherit from the shared infrastructure in `tools"
  },
  {
    "path": "docs/advanced-usage.md",
    "chars": 25755,
    "preview": "# Advanced Usage Guide\n\nThis guide covers advanced features, configuration options, and workflows for power users of the"
  },
  {
    "path": "docs/ai-collaboration.md",
    "chars": 6070,
    "preview": "# AI-to-AI Conversation Threading\n\nThis server enables **true AI collaboration** between Claude and multiple AI models ("
  },
  {
    "path": "docs/ai_banter.md",
    "chars": 12561,
    "preview": "# The Code Comedy Hour\n\nJust when I thought it was a routine test, Claude and it's _thought-partner_ would go off-script"
  },
  {
    "path": "docs/azure_openai.md",
    "chars": 3308,
    "preview": "# Azure OpenAI Configuration\n\nAzure OpenAI support lets PAL MCP talk to GPT-4o, GPT-4.1, GPT-5, and o-series deployments"
  },
  {
    "path": "docs/configuration.md",
    "chars": 11979,
    "preview": "# Configuration Guide\n\nThis guide covers all configuration options for the PAL MCP Server. The server is configured thro"
  },
  {
    "path": "docs/context-revival.md",
    "chars": 6170,
    "preview": "# Context Revival: AI Memory Beyond Context Limits\n\n## **The Most Profound Feature: Context Revival After Reset**\n\n**Thi"
  },
  {
    "path": "docs/contributions.md",
    "chars": 6906,
    "preview": "# Contributing to PAL MCP Server\n\nThank you for your interest in contributing to PAL MCP Server! This guide will help yo"
  },
  {
    "path": "docs/custom_models.md",
    "chars": 12683,
    "preview": "# Custom Models & API Setup\n\nThis guide covers setting up multiple AI model providers including OpenRouter, custom API e"
  },
  {
    "path": "docs/docker-deployment.md",
    "chars": 10442,
    "preview": "# Docker Deployment Guide\n\nThis guide covers deploying PAL MCP Server using Docker and Docker Compose for production env"
  },
  {
    "path": "docs/gemini-setup.md",
    "chars": 1097,
    "preview": "# Gemini CLI Setup\n\n> **Note**: While PAL MCP Server connects successfully to Gemini CLI, tool invocation is not working"
  },
  {
    "path": "docs/getting-started.md",
    "chars": 17402,
    "preview": "# Getting Started with PAL MCP Server\n\nThis guide walks you through setting up the PAL MCP Server from scratch, includin"
  },
  {
    "path": "docs/index.md",
    "chars": 1207,
    "preview": "# PAL MCP Server Documentation\n\n_Formerly known as PAL MCP. See the short [name change note](name-change.md) for context"
  },
  {
    "path": "docs/locale-configuration.md",
    "chars": 5297,
    "preview": "# Locale Configuration for PAL MCP Server\n\nThis guide explains how to configure and use the localization feature to cust"
  },
  {
    "path": "docs/logging.md",
    "chars": 1599,
    "preview": "# Logging\n\n## Quick Start - Follow Logs\n\nThe easiest way to monitor logs is to use the `-f` flag when starting the serve"
  },
  {
    "path": "docs/model_ranking.md",
    "chars": 3280,
    "preview": "# Model Capability Ranking\n\nAuto mode needs a short, trustworthy list of models to suggest. The server\ncomputes a capabi"
  },
  {
    "path": "docs/name-change.md",
    "chars": 404,
    "preview": "# PAL MCP Name Change\n\nPAL MCP was previously called Zen MCP. We renamed to avoid confusion with another similarly named"
  },
  {
    "path": "docs/testing.md",
    "chars": 5197,
    "preview": "# Testing Guide\n\nThis project includes comprehensive test coverage through unit tests and integration simulator tests.\n\n"
  },
  {
    "path": "docs/tools/analyze.md",
    "chars": 8504,
    "preview": "# Analyze Tool - Smart File Analysis\n\n**General-purpose code understanding and exploration through workflow-driven inves"
  },
  {
    "path": "docs/tools/apilookup.md",
    "chars": 4459,
    "preview": "# API Lookup Tool\n\nThe `apilookup` tool ensures you get **current, accurate API/SDK documentation** by forcing the AI to"
  },
  {
    "path": "docs/tools/challenge.md",
    "chars": 1547,
    "preview": "# challenge - Challenge an approach or validate ideas with confidence\n\nThe `challenge` tool encourages thoughtful critic"
  },
  {
    "path": "docs/tools/chat.md",
    "chars": 6996,
    "preview": "# Chat Tool - General Development Chat & Collaborative Thinking\n\n**Your thinking partner - bounce ideas, get second opin"
  },
  {
    "path": "docs/tools/clink.md",
    "chars": 9024,
    "preview": "# Clink Tool - CLI-to-CLI Bridge\n\n**Spawn AI subagents, connect external CLIs, orchestrate isolated contexts – all witho"
  },
  {
    "path": "docs/tools/codereview.md",
    "chars": 8851,
    "preview": "# CodeReview Tool - Professional Code Review\n\n**Comprehensive code analysis with prioritized feedback through workflow-d"
  },
  {
    "path": "docs/tools/consensus.md",
    "chars": 6929,
    "preview": "# Consensus Tool - Multi-Model Perspective Gathering\n\n**Get diverse expert opinions from multiple AI models on technical"
  },
  {
    "path": "docs/tools/debug.md",
    "chars": 10430,
    "preview": "# Debug Tool - Systematic Investigation & Expert Analysis\n\n**Step-by-step investigation followed by expert debugging ass"
  },
  {
    "path": "docs/tools/docgen.md",
    "chars": 8916,
    "preview": "# DocGen Tool - Comprehensive Documentation Generation\n\n**Generates comprehensive documentation with complexity analysis"
  },
  {
    "path": "docs/tools/listmodels.md",
    "chars": 3922,
    "preview": "# ListModels Tool - List Available Models\n\n**Display all available AI models organized by provider**\n\nThe `listmodels` t"
  },
  {
    "path": "docs/tools/planner.md",
    "chars": 3610,
    "preview": "# Planner Tool - Interactive Step-by-Step Planning\n\n**Break down complex projects into manageable, structured plans thro"
  },
  {
    "path": "docs/tools/precommit.md",
    "chars": 13067,
    "preview": "# PreCommit Tool - Pre-Commit Validation\n\n**Comprehensive review of staged/unstaged git changes across multiple reposito"
  },
  {
    "path": "docs/tools/refactor.md",
    "chars": 11088,
    "preview": "# Refactor Tool - Intelligent Code Refactoring\n\n**Comprehensive refactoring analysis with top-down decomposition strateg"
  },
  {
    "path": "docs/tools/secaudit.md",
    "chars": 11974,
    "preview": "# Secaudit Tool - Comprehensive Security Audit\n\n**Systematic OWASP-based security assessment with compliance evaluation "
  },
  {
    "path": "docs/tools/testgen.md",
    "chars": 9003,
    "preview": "# TestGen Tool - Comprehensive Test Generation\n\n**Generates thorough test suites with edge case coverage through workflo"
  },
  {
    "path": "docs/tools/thinkdeep.md",
    "chars": 4809,
    "preview": "# ThinkDeep Tool - Extended Reasoning Partner\n\n**Get a second opinion to augment Claude's own extended thinking**\n\nThe `"
  },
  {
    "path": "docs/tools/tracer.md",
    "chars": 6239,
    "preview": "# Tracer Tool - Static Code Analysis Prompt Generator\n\n**Creates detailed analysis prompts for call-flow mapping and dep"
  },
  {
    "path": "docs/tools/version.md",
    "chars": 3877,
    "preview": "# Version Tool - Server Information\n\n**Get server version, configuration details, and list of available tools**\n\nThe `ve"
  },
  {
    "path": "docs/troubleshooting.md",
    "chars": 2851,
    "preview": "# Troubleshooting Guide\n\n## Quick Debugging Steps\n\nIf you're experiencing issues with the PAL MCP Server, follow these s"
  },
  {
    "path": "docs/vcr-testing.md",
    "chars": 4249,
    "preview": "# HTTP Transport Recorder for Testing\n\nA custom HTTP recorder for testing expensive API calls (like o3-pro) with real re"
  },
  {
    "path": "docs/wsl-setup.md",
    "chars": 2233,
    "preview": "# WSL (Windows Subsystem for Linux) Setup Guide\n\nThis guide provides detailed instructions for setting up PAL MCP Server"
  },
  {
    "path": "examples/claude_config_macos.json",
    "chars": 385,
    "preview": "{\n  \"comment\": \"macOS configuration using standalone server\",\n  \"comment2\": \"Run './run-server.sh' to set up the environ"
  },
  {
    "path": "examples/claude_config_wsl.json",
    "chars": 438,
    "preview": "{\n  \"comment\": \"Windows configuration using WSL with standalone server\",\n  \"comment2\": \"Run './run-server.sh' in WSL to "
  },
  {
    "path": "pal-mcp-server",
    "chars": 286,
    "preview": "#!/bin/bash\n# Wrapper script for Gemini CLI compatibility\n\n# Get the directory of this script\nDIR=\"$(cd \"$(dirname \"$0\")"
  },
  {
    "path": "providers/__init__.py",
    "chars": 677,
    "preview": "\"\"\"Model provider abstractions for supporting multiple AI providers.\"\"\"\n\nfrom .azure_openai import AzureOpenAIProvider\nf"
  },
  {
    "path": "providers/azure_openai.py",
    "chars": 14457,
    "preview": "\"\"\"Azure OpenAI provider built on the OpenAI-compatible implementation.\"\"\"\n\nfrom __future__ import annotations\n\nimport l"
  },
  {
    "path": "providers/base.py",
    "chars": 16546,
    "preview": "\"\"\"Base interfaces and common behaviour for model providers.\"\"\"\n\nimport logging\nimport time\nfrom abc import ABC, abstrac"
  },
  {
    "path": "providers/custom.py",
    "chars": 7393,
    "preview": "\"\"\"Custom API provider implementation.\"\"\"\n\nimport logging\n\nfrom utils.env import get_env\n\nfrom .openai_compatible import"
  },
  {
    "path": "providers/dial.py",
    "chars": 12697,
    "preview": "\"\"\"DIAL (Data & AI Layer) model provider implementation.\"\"\"\n\nimport logging\nimport threading\nfrom typing import ClassVar"
  },
  {
    "path": "providers/gemini.py",
    "chars": 21613,
    "preview": "\"\"\"Gemini model provider implementation.\"\"\"\n\nimport base64\nimport logging\nfrom typing import TYPE_CHECKING, ClassVar, Op"
  },
  {
    "path": "providers/openai.py",
    "chars": 6082,
    "preview": "\"\"\"OpenAI model provider implementation.\"\"\"\n\nimport logging\nfrom typing import TYPE_CHECKING, ClassVar, Optional\n\nif TYP"
  },
  {
    "path": "providers/openai_compatible.py",
    "chars": 36271,
    "preview": "\"\"\"Base class for OpenAI-compatible API providers.\"\"\"\n\nimport copy\nimport ipaddress\nimport logging\nfrom typing import Op"
  },
  {
    "path": "providers/openrouter.py",
    "chars": 8178,
    "preview": "\"\"\"OpenRouter provider implementation.\"\"\"\n\nimport logging\n\nfrom utils.env import get_env\n\nfrom .openai_compatible import"
  },
  {
    "path": "providers/registries/__init__.py",
    "chars": 561,
    "preview": "\"\"\"Registry implementations for provider capability manifests.\"\"\"\n\nfrom .azure import AzureModelRegistry\nfrom .custom im"
  },
  {
    "path": "providers/registries/azure.py",
    "chars": 1701,
    "preview": "\"\"\"Registry loader for Azure OpenAI model configurations.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\n\nfrom ."
  },
  {
    "path": "providers/registries/base.py",
    "chars": 9436,
    "preview": "\"\"\"Shared infrastructure for JSON-backed model registries.\"\"\"\n\nfrom __future__ import annotations\n\nimport importlib.reso"
  },
  {
    "path": "providers/registries/custom.py",
    "chars": 986,
    "preview": "\"\"\"Registry loader for custom OpenAI-compatible endpoints.\"\"\"\n\nfrom __future__ import annotations\n\nfrom ..shared import "
  },
  {
    "path": "providers/registries/dial.py",
    "chars": 611,
    "preview": "\"\"\"Registry loader for DIAL provider capabilities.\"\"\"\n\nfrom __future__ import annotations\n\nfrom ..shared import Provider"
  },
  {
    "path": "providers/registries/gemini.py",
    "chars": 622,
    "preview": "\"\"\"Registry loader for Gemini model capabilities.\"\"\"\n\nfrom __future__ import annotations\n\nfrom ..shared import ProviderT"
  },
  {
    "path": "providers/registries/openai.py",
    "chars": 622,
    "preview": "\"\"\"Registry loader for OpenAI model capabilities.\"\"\"\n\nfrom __future__ import annotations\n\nfrom ..shared import ProviderT"
  },
  {
    "path": "providers/registries/openrouter.py",
    "chars": 1584,
    "preview": "\"\"\"OpenRouter model registry for managing model configurations and aliases.\"\"\"\n\nfrom __future__ import annotations\n\nfrom"
  },
  {
    "path": "providers/registries/xai.py",
    "chars": 603,
    "preview": "\"\"\"Registry loader for X.AI model capabilities.\"\"\"\n\nfrom __future__ import annotations\n\nfrom ..shared import ProviderTyp"
  },
  {
    "path": "providers/registry.py",
    "chars": 19441,
    "preview": "\"\"\"Model provider registry for managing available providers.\"\"\"\n\nimport logging\nfrom typing import TYPE_CHECKING, Option"
  },
  {
    "path": "providers/registry_provider_mixin.py",
    "chars": 3261,
    "preview": "\"\"\"Mixin for providers backed by capability registries.\n\nThis mixin centralises the boilerplate for providers that expos"
  },
  {
    "path": "providers/shared/__init__.py",
    "chars": 565,
    "preview": "\"\"\"Shared data structures and helpers for model providers.\"\"\"\n\nfrom .model_capabilities import ModelCapabilities\nfrom .m"
  },
  {
    "path": "providers/shared/model_capabilities.py",
    "chars": 6203,
    "preview": "\"\"\"Dataclass describing the feature set of a model exposed by a provider.\"\"\"\n\nimport math\nfrom dataclasses import datacl"
  },
  {
    "path": "providers/shared/model_response.py",
    "chars": 702,
    "preview": "\"\"\"Dataclass used to normalise provider SDK responses.\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom typing import A"
  },
  {
    "path": "providers/shared/provider_type.py",
    "chars": 363,
    "preview": "\"\"\"Enumeration describing which backend owns a given model.\"\"\"\n\nfrom enum import Enum\n\n__all__ = [\"ProviderType\"]\n\n\nclas"
  },
  {
    "path": "providers/shared/temperature.py",
    "chars": 7137,
    "preview": "\"\"\"Helper types for validating model temperature parameters.\"\"\"\n\nfrom abc import ABC, abstractmethod\nfrom typing import "
  },
  {
    "path": "providers/xai.py",
    "chars": 3105,
    "preview": "\"\"\"X.AI (GROK) model provider implementation.\"\"\"\n\nimport logging\nfrom typing import TYPE_CHECKING, ClassVar, Optional\n\ni"
  },
  {
    "path": "pyproject.toml",
    "chars": 3114,
    "preview": "[project]\nname = \"pal-mcp-server\"\nversion = \"9.8.2\"\ndescription = \"AI-powered MCP server with multiple model providers\"\n"
  },
  {
    "path": "pytest.ini",
    "chars": 290,
    "preview": "[pytest]\ntestpaths = tests\npython_files = test_*.py\npython_classes = Test*\npython_functions = test_*\nasyncio_mode = auto"
  },
  {
    "path": "requirements-dev.txt",
    "chars": 142,
    "preview": "pytest>=7.4.0\npytest-asyncio>=0.21.0\npytest-mock>=3.11.0\nblack>=23.0.0\nruff>=0.1.0\nisort>=5.12.0\npython-semantic-release"
  },
  {
    "path": "requirements.txt",
    "chars": 324,
    "preview": "mcp>=1.0.0\ngoogle-genai>=1.19.0\nopenai>=1.55.2  # Minimum version for httpx 0.28.0 compatibility\npydantic>=2.0.0\npython-"
  },
  {
    "path": "run-server.ps1",
    "chars": 76497,
    "preview": "<#\n.SYNOPSIS\n    Installation, configuration, and launch script for PAL MCP server on Windows.\n\n.DESCRIPTION\n    This P"
  },
  {
    "path": "run-server.sh",
    "chars": 91969,
    "preview": "#!/bin/bash\nset -euo pipefail\n\n# ============================================================================\n# PAL MCP "
  },
  {
    "path": "run_integration_tests.ps1",
    "chars": 7003,
    "preview": "<#\n.SYNOPSIS\n    Integration test runner script for the PAL MCP server on Windows.\n\n.DESCRIPTION\n    This PowerShell scr"
  },
  {
    "path": "run_integration_tests.sh",
    "chars": 2634,
    "preview": "#!/bin/bash\n\n# PAL MCP Server - Run Integration Tests\n# This script runs integration tests that require API keys\n# Run t"
  },
  {
    "path": "scripts/sync_version.py",
    "chars": 952,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nSync version from pyproject.toml to config.py\nThis script is called by GitHub Actions after s"
  },
  {
    "path": "server.py",
    "chars": 66201,
    "preview": "\"\"\"\nPAL MCP Server - Main server implementation\n\nThis module implements the core MCP (Model Context Protocol) server tha"
  },
  {
    "path": "simulator_tests/__init__.py",
    "chars": 5483,
    "preview": "\"\"\"\nCommunication Simulator Tests Package\n\nThis package contains individual test modules for the PAL MCP Communication S"
  },
  {
    "path": "simulator_tests/base_test.py",
    "chars": 14626,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nBase Test Class for Communication Simulator Tests\n\nProvides common functionality and utilitie"
  },
  {
    "path": "simulator_tests/conversation_base_test.py",
    "chars": 10569,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nConversation Base Test Class for In-Process MCP Tool Testing\n\nThis class enables testing MCP "
  },
  {
    "path": "simulator_tests/log_utils.py",
    "chars": 10353,
    "preview": "\"\"\"\nCentralized log utility for simulator tests.\n\nThis module provides common log reading and parsing functionality\nused"
  },
  {
    "path": "simulator_tests/test_analyze_validation.py",
    "chars": 47224,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nAnalyze Tool Validation Test\n\nTests the analyze tool's capabilities using the new workflow ar"
  },
  {
    "path": "simulator_tests/test_basic_conversation.py",
    "chars": 3340,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nBasic Conversation Flow Test\n\nTests basic conversation continuity with the chat tool, includi"
  },
  {
    "path": "simulator_tests/test_chat_simple_validation.py",
    "chars": 20810,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nChat Simple Tool Validation Test\n\nComprehensive test for the new ChatSimple tool implementati"
  },
  {
    "path": "simulator_tests/test_codereview_validation.py",
    "chars": 46892,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nCodeReview Tool Validation Test\n\nTests the codereview tool's capabilities using the new workf"
  },
  {
    "path": "simulator_tests/test_consensus_conversation.py",
    "chars": 10467,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nConsensus Conversation Continuation Test\n\nTests that the consensus tool properly handles conv"
  },
  {
    "path": "simulator_tests/test_consensus_three_models.py",
    "chars": 7216,
    "preview": "\"\"\"\nTest consensus tool with three models demonstrating sequential processing\n\"\"\"\n\nimport json\n\nfrom .base_test import B"
  },
  {
    "path": "simulator_tests/test_consensus_workflow_accurate.py",
    "chars": 10820,
    "preview": "\"\"\"\nAccurate Consensus Workflow Test\n\nThis test validates the complete consensus workflow step-by-step to ensure:\n1. Ste"
  },
  {
    "path": "simulator_tests/test_content_validation.py",
    "chars": 7013,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nContent Validation Test\n\nTests that tools don't duplicate file content in their responses.\nTh"
  },
  {
    "path": "simulator_tests/test_conversation_chain_validation.py",
    "chars": 17500,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nConversation Chain and Threading Validation Test\n\nThis test validates that:\n1. Multiple tool "
  },
  {
    "path": "simulator_tests/test_cross_tool_comprehensive.py",
    "chars": 14918,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nComprehensive Cross-Tool Test\n\nTests file deduplication, conversation continuation, and file "
  },
  {
    "path": "simulator_tests/test_cross_tool_continuation.py",
    "chars": 9379,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nCross-Tool Continuation Test\n\nTests comprehensive cross-tool continuation scenarios to ensure"
  },
  {
    "path": "simulator_tests/test_debug_certain_confidence.py",
    "chars": 25685,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nDebug Tool Certain Confidence Simulator Test\n\nTests the debug tool's 'certain' confidence fea"
  },
  {
    "path": "simulator_tests/test_debug_validation.py",
    "chars": 43038,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nDebugWorkflow Tool Validation Test\n\nTests the debug tool's capabilities using the new workflo"
  },
  {
    "path": "simulator_tests/test_line_number_validation.py",
    "chars": 7399,
    "preview": "\"\"\"\nTest to validate line number handling across different tools\n\"\"\"\n\nimport json\nimport os\n\nfrom .base_test import Base"
  },
  {
    "path": "simulator_tests/test_logs_validation.py",
    "chars": 4127,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nServer Logs Validation Test\n\nValidates server logs to confirm file deduplication behavior and"
  },
  {
    "path": "simulator_tests/test_model_thinking_config.py",
    "chars": 6253,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nModel Thinking Configuration Test\n\nTests that thinking configuration is properly applied only"
  },
  {
    "path": "simulator_tests/test_o3_model_selection.py",
    "chars": 14009,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nO3 Model Selection Test\n\nTests that O3 models are properly selected and used when explicitly "
  },
  {
    "path": "simulator_tests/test_o3_pro_expensive.py",
    "chars": 4474,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nO3-Pro Expensive Model Test\n\n⚠️  WARNING: This test uses o3-pro which is EXTREMELY EXPENSIVE!"
  },
  {
    "path": "simulator_tests/test_ollama_custom_url.py",
    "chars": 12797,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nOllama Custom URL Test\n\nTests custom API endpoint functionality with Ollama-style local model"
  },
  {
    "path": "simulator_tests/test_openrouter_fallback.py",
    "chars": 9281,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nOpenRouter Fallback Test\n\nTests that verify the system correctly falls back to OpenRouter whe"
  },
  {
    "path": "simulator_tests/test_openrouter_models.py",
    "chars": 9356,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nOpenRouter Model Tests\n\nTests that verify OpenRouter functionality including:\n- Model alias r"
  },
  {
    "path": "simulator_tests/test_per_tool_deduplication.py",
    "chars": 9485,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPer-Tool File Deduplication Test\n\nTests file deduplication for each individual MCP tool to en"
  },
  {
    "path": "simulator_tests/test_planner_continuation_history.py",
    "chars": 16254,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPlanner Continuation History Test\n\nTests the planner tool's continuation history building acr"
  },
  {
    "path": "simulator_tests/test_planner_validation.py",
    "chars": 30312,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPlannerWorkflow Tool Validation Test\n\nTests the planner tool's capabilities using the new wor"
  },
  {
    "path": "simulator_tests/test_planner_validation_old.py",
    "chars": 18155,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPlanner Tool Validation Test\n\nTests the planner tool's sequential planning capabilities inclu"
  },
  {
    "path": "simulator_tests/test_precommitworkflow_validation.py",
    "chars": 48308,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPrecommitWorkflow Tool Validation Test\n\nTests the precommit tool's capabilities using the new"
  },
  {
    "path": "simulator_tests/test_prompt_size_limit_bug.py",
    "chars": 7660,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPrompt Size Limit Bug Test\n\nThis test reproduces a critical bug where the prompt size limit c"
  },
  {
    "path": "simulator_tests/test_refactor_validation.py",
    "chars": 47396,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nRefactor Tool Validation Test\n\nTests the refactor tool's capabilities using the new workflow "
  },
  {
    "path": "simulator_tests/test_secaudit_validation.py",
    "chars": 26063,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nSECAUDIT Tool Validation Test\n\nTests the secaudit tool's capabilities using the workflow arch"
  },
  {
    "path": "simulator_tests/test_testgen_validation.py",
    "chars": 35466,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nTestGen Tool Validation Test\n\nTests the testgen tool's capabilities using the workflow archit"
  },
  {
    "path": "simulator_tests/test_thinkdeep_validation.py",
    "chars": 42918,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nThinkDeep Tool Validation Test\n\nTests the thinkdeep tool's capabilities using the new workflo"
  },
  {
    "path": "simulator_tests/test_token_allocation_validation.py",
    "chars": 14102,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nToken Allocation and Conversation History Validation Test\n\nThis test validates that:\n1. Token"
  },
  {
    "path": "simulator_tests/test_vision_capability.py",
    "chars": 6123,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nVision Capability Test\n\nTests vision capability with the chat tool using O3 model:\n- Test fil"
  },
  {
    "path": "simulator_tests/test_xai_models.py",
    "chars": 8347,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nX.AI GROK Model Tests\n\nTests that verify X.AI GROK functionality including:\n- Model alias res"
  },
  {
    "path": "systemprompts/__init__.py",
    "chars": 1008,
    "preview": "\"\"\"\nSystem prompts for Gemini tools\n\"\"\"\n\nfrom .analyze_prompt import ANALYZE_PROMPT\nfrom .chat_prompt import CHAT_PROMPT"
  },
  {
    "path": "systemprompts/analyze_prompt.py",
    "chars": 4804,
    "preview": "\"\"\"\nAnalyze tool system prompt\n\"\"\"\n\nANALYZE_PROMPT = \"\"\"\nROLE\nYou are a senior software analyst performing a holistic te"
  },
  {
    "path": "systemprompts/chat_prompt.py",
    "chars": 3942,
    "preview": "\"\"\"\nChat tool system prompt\n\"\"\"\n\nCHAT_PROMPT = \"\"\"\nYou are a senior engineering thought-partner collaborating with anoth"
  },
  {
    "path": "systemprompts/clink/codex_codereviewer.txt",
    "chars": 909,
    "preview": "/review You are the Codex CLI code reviewer operating inside the PAL MCP server with full repository access.\n\n- Inspect "
  },
  {
    "path": "systemprompts/clink/default.txt",
    "chars": 776,
    "preview": "You are an external CLI agent operating inside the PAL MCP server with full repository access.\n\n- Use terminal tools to "
  },
  {
    "path": "systemprompts/clink/default_codereviewer.txt",
    "chars": 879,
    "preview": "You are an external CLI code reviewer operating inside the PAL MCP server with full repository access.\n\n- Inspect any re"
  },
  {
    "path": "systemprompts/clink/default_planner.txt",
    "chars": 809,
    "preview": "You are the planning agent operating through the PAL MCP server.\n\n- Respond with JSON only using the planning schema fie"
  },
  {
    "path": "systemprompts/codereview_prompt.py",
    "chars": 6847,
    "preview": "\"\"\"\nCodeReview tool system prompt\n\"\"\"\n\nCODEREVIEW_PROMPT = \"\"\"\nROLE\nYou are an expert code reviewer, combining the deep "
  },
  {
    "path": "systemprompts/consensus_prompt.py",
    "chars": 6087,
    "preview": "\"\"\"\nConsensus tool system prompt for multi-model perspective gathering\n\"\"\"\n\nCONSENSUS_PROMPT = \"\"\"\nROLE\nYou are an exper"
  },
  {
    "path": "systemprompts/debug_prompt.py",
    "chars": 7665,
    "preview": "\"\"\"\nDebug tool system prompt\n\"\"\"\n\nDEBUG_ISSUE_PROMPT = \"\"\"\nROLE\nYou are an expert debugging assistant receiving systemat"
  },
  {
    "path": "systemprompts/docgen_prompt.py",
    "chars": 16773,
    "preview": "\"\"\"\nDocumentation generation tool system prompt\n\"\"\"\n\nDOCGEN_PROMPT = \"\"\"\nROLE\nYou're being guided through a systematic d"
  },
  {
    "path": "systemprompts/generate_code_prompt.py",
    "chars": 7825,
    "preview": "\"\"\"System prompt fragment enabling structured code generation exports.\n\nThis prompt is injected into the system prompt f"
  },
  {
    "path": "systemprompts/planner_prompt.py",
    "chars": 6455,
    "preview": "\"\"\"\nPlanner tool system prompts\n\"\"\"\n\nPLANNER_PROMPT = \"\"\"\nYou are an expert, seasoned planning consultant and systems ar"
  },
  {
    "path": "systemprompts/precommit_prompt.py",
    "chars": 9132,
    "preview": "\"\"\"\nPrecommit tool system prompt\n\"\"\"\n\nPRECOMMIT_PROMPT = \"\"\"\nROLE\nYou are an expert pre-commit reviewer and senior engin"
  },
  {
    "path": "systemprompts/refactor_prompt.py",
    "chars": 20039,
    "preview": "\"\"\"\nRefactor tool system prompt\n\"\"\"\n\nREFACTOR_PROMPT = \"\"\"\nROLE\nYou are a principal software engineer specializing in in"
  },
  {
    "path": "systemprompts/secaudit_prompt.py",
    "chars": 17183,
    "preview": "\"\"\"\nSECAUDIT tool system prompt\n\"\"\"\n\nSECAUDIT_PROMPT = \"\"\"\nROLE\nYou are an expert security auditor receiving systematic "
  },
  {
    "path": "systemprompts/testgen_prompt.py",
    "chars": 8590,
    "preview": "\"\"\"\nTestGen tool system prompt\n\"\"\"\n\nTESTGEN_PROMPT = \"\"\"\nROLE\nYou are a principal software engineer who specialises in w"
  },
  {
    "path": "systemprompts/thinkdeep_prompt.py",
    "chars": 3490,
    "preview": "\"\"\"\nThinkDeep tool system prompt\n\"\"\"\n\nTHINKDEEP_PROMPT = \"\"\"\nROLE\nYou are a senior engineering collaborator working alon"
  },
  {
    "path": "systemprompts/tracer_prompt.py",
    "chars": 6847,
    "preview": "\"\"\"\nTracer tool system prompts\n\"\"\"\n\nTRACER_PROMPT = \"\"\"\nYou are an expert, seasoned software architect and code analysis"
  },
  {
    "path": "tests/CASSETTE_MAINTENANCE.md",
    "chars": 8310,
    "preview": "# HTTP Cassette Testing - Maintenance Guide\n\n## Overview\n\nThis project uses HTTP cassettes (recorded HTTP interactions) "
  }
]

// ... and 160 more files (download for full content)

About this extraction

This page contains the full source code of the BeehiveInnovations/pal-mcp-server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 360 files (3.7 MB), approximately 991.5k tokens, and a symbol index with 2687 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!