This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
vibe-heal is an AI-powered SonarQube issue remediation tool that automatically fixes code quality problems. The project is initialized from the cookiecutter-uv template and uses modern Python tooling.
This project uses uv as the package manager (not pip or poetry). All Python commands should be run via uv run.
make install # Sets up environment and pre-commit hooks
This command:
uv sync# Run all tests with coverage
make test
# Run tests manually
uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml
# Run tests across multiple Python versions (requires multiple Python installations)
tox
Test files are located in tests/ directory. When writing tests, remember that tests/* files have S101 (assert usage) ignored in ruff configuration.
# Run all quality checks (recommended before committing)
make check
# Run pre-commit hooks manually
uv run pre-commit run -a
# Type checking with mypy
uv run mypy
# Check for obsolete dependencies
uv run deptry src
The project uses ruff for linting and formatting (not black or flake8). Configuration is in pyproject.toml:
Pre-commit hooks will automatically run ruff-check and ruff-format on commits.
# Build and serve docs locally
make docs
# Test documentation build
make docs-test
Documentation uses MkDocs with Material theme. Configuration is in mkdocs.yml.
# Build wheel file
make build
# Clean build artifacts
make clean-build
Build uses hatchling as the backend. Package source is in src/vibe_heal/.
src/vibe_heal/: Main package source codetests/: Test files (pytest)docs/: MkDocs documentation sourcepyproject.toml: Project configuration, dependencies, and tool settingsMakefile: Common development commandstox.ini: Multi-version Python testing configurationmypy is configured with strict settings:
disallow_untyped_defs = truedisallow_any_unimported = truesrc/All functions should have type annotations.
pytest-cov is configured)testpaths = ["tests"] in pytest configurationDevelopment dependencies are defined in [dependency-groups] in pyproject.toml. To add dependencies:
# Add a runtime dependency
uv add <package>
# Add a dev dependency
uv add --dev <package>
The project has GitHub Actions workflows for:
Workflows use uv and are configured in .github/workflows/.
Supports Python 3.11 through 3.13. The tox.ini configuration tests against all these versions.
vibe-heal is an AI-powered SonarQube issue remediation tool that automatically fixes code quality problems using AI coding assistants (Claude Code or Aider).
Core Workflow:
CLI Layer (cli.py)
↓
Orchestrator (orchestrator.py) - coordinates entire workflow
↓
├─ Config (config/) - loads .env.vibeheal
├─ SonarQubeClient (sonarqube/) - fetches issues via API
├─ IssueProcessor (processor/) - sorts/filters issues
├─ AITool (ai_tools/) - fixes issues (ClaudeCodeTool implemented)
└─ GitManager (git/) - creates commits
config/: Pydantic-based configuration from .env.vibeheal or .env
VibeHealConfig model with validationenv_file parameter in constructor--env-file option to override default config filesonarqube/: Async HTTP client for SonarQube Web API
SonarQubeClient.get_issues_for_file(file_path) - uses components parameter for file-specific queriesSonarQubeClient.create_project(key, name) - creates new SonarQube projectSonarQubeClient.delete_project(key) - deletes SonarQube projectSonarQubeClient.project_exists(key) - checks if project existsSonarQubeIssue model - supports both old and new SonarQube API formatshttpx for async requestsProjectManager - manages temporary project lifecycle for branch cleanup
create_temp_project(base_key, branch_name, user_email) - creates uniquely named temp project{base_key}_{sanitized_email}_{sanitized_branch}_{timestamp}yymmdd-hhmm (e.g., 251024-1630)my-project_user_example_com_feature_api_251024-1630delete_project(project_key) - deletes temporary projectproject_exists(project_key) - checks project existence_sanitize_identifier(value) - sanitizes strings for project keys (alphanumeric + underscore, lowercase)TempProjectMetadata model - tracks created projects for cleanupAnalysisRunner - executes SonarQube analysis via sonar-scanner CLI
run_analysis(project_key, project_name, project_dir, sources) - runs scanner and waits for completionasyncio.create_subprocess_exec for non-blocking execution/api/ce/task?id={taskId} to wait for server-side analysis completionvalidate_scanner_available() - checks if sonar-scanner is installedAnalysisResult model - returns success status, task_id, dashboard_url, error detailssonar-scanner CLI must be installed Downloadprocessor/: Business logic for issue handling
is_fixable property checks for line number, non-resolved status)ai_tools/: Abstract interface + implementations
AITool ABC with fix_issue() and fix_duplication() abstract methodsAIToolType enum (CLAUDE_CODE, AIDER, GEMINI)ClaudeCodeTool - invokes claude CLI with --print --output-format jsonGeminiCliTool - invokes gemini CLI with --output-format json --approval-mode auto_edit; writes prompt to a temp file in cwd (not system temp dir) and passes the filename as the CLI prompt argumentAiderTool - invokes aider with optional model/api-key/api-base from configAIToolFactory with auto-detection (priority order: Claude Code → Aider → Gemini)run_command() in ai_tools/utils.py - shared async subprocess helper used by all tool implementationsFixResult model for fix outcomesgit/: Git operations via GitPython
GitManager.commit_fix() - creates conventional commitsfix: [SQ-RULE] message with full issue details in bodyBranchAnalyzer - analyzes branch differences for branch cleanup feature
get_modified_files(base_branch='origin/main') - returns files modified vs base branchget_current_branch() - returns active branch namevalidate_branch_exists(branch) - checks local and remote branchesget_user_email() - retrieves git user email for project namingorchestrator.py: Main workflow coordination
VibeHealOrchestrator.fix_file() - end-to-end flow for fixing a single filecleanup/: Branch cleanup workflow
CleanupOrchestrator - orchestrates branch cleanup workflow
cleanup_branch(base_branch, max_iterations, file_patterns) - cleans up all modified files_cleanup_file(file_path, project_key, project_name, max_iterations) - fixes single file until no issues remainVibeHealOrchestrator.fix_file() for actual fixing_filter_files(files, patterns) - filters files by glob patternsCleanupResult model - tracks overall cleanup resultsFileCleanupResult model - tracks per-file cleanup results["*.py", "src/**/*.ts"])deduplication/: Code duplication removal module
DuplicationClient - SonarQube duplications API client
get_duplications_for_file(file_path) - uses /api/duplications/show endpointSonarQubeClient for auth and request handlingDuplicationBlock model - represents single duplication location with line range
from_line - starting line numbersize - number of linesto_line property - calculated ending line numberget_snippet_lines(source_lines) - extracts first 3 + last 3 lines for promptsDuplicationGroup model - group of duplicate blocks (same code in multiple places)
get_target_block(target_file_ref) - get block for specific fileget_other_blocks(target_file_ref) - get blocks from other filesDuplicationsResponse model - API response wrapper with files mapDuplicationProcessor - sorts and filters duplications
DeduplicationOrchestrator - single file deduplication workflow
dedupe_file(file_path, dry_run, max_duplications) - main workflowDedupeBranchOrchestrator - branch-wide deduplication workflow
dedupe_branch(base_branch, max_iterations, file_patterns, verbose) - main workflowDeduplicationOrchestrator.dedupe_file() for per-file processingFileDedupResult model - tracks per-file deduplication resultsDedupeBranchResult model - tracks overall branch deduplication resultscli.py: Command-line interface with typer
vibe-heal fix <file> - fix issues in a single file
--dry-run, --max-issues, --min-severity, --ai-tool, --env-file, --verbosevibe-heal dedupe <file> - remove code duplications from a single file
--dry-run, --max-duplications, --ai-tool, --env-file, --verbosevibe-heal cleanup - clean up all modified files in current branch
--base-branch (default: origin/main), --max-iterations (default: 10), --pattern (file filters), --ai-tool, --env-file, --verbosevibe-heal dedupe-branch - remove duplications from all modified files in current branch
--base-branch (default: origin/main), --max-iterations (default: 10), --pattern (file filters), --ai-tool, --env-file, --verbosevibe-heal config - shows current configuration
--env-filevibe-heal version - shows version informationReverse Line Order: Issues and duplications are fixed from highest line number to lowest. This prevents line number shifts from earlier fixes affecting later fixes.
Component Path Construction: SonarQube API queries use components=projectkey:filepath (lowercase project key). The old approach of querying with componentKeys and filtering client-side was incorrect.
Issue Status Filtering: Use issueStatuses=OPEN,CONFIRMED instead of resolved=false for more precise filtering.
Git Safety: Only checks that the specific file being fixed has no uncommitted changes. Other files can have changes (requirement was relaxed from “clean working directory”).
Async/Await: SonarQube client and AI tool operations use async/await pattern. CLI wraps with asyncio.run().
AI Tool Integration: Claude Code is invoked with permission mode acceptEdits and tool restriction to Edit,Read only for security. Uses --print and --output-format json flags.
Deduplication Specifics:
refactor: [duplication] remove duplicate code at line X with duplication details in bodyBy default, configuration is loaded from .env.vibeheal or .env in the current directory.
SONARQUBE_URL=https://sonar.example.com
SONARQUBE_TOKEN=your_token # Preferred
# OR: SONARQUBE_USERNAME + SONARQUBE_PASSWORD
SONARQUBE_PROJECT_KEY=your_project
# AI_TOOL=claude-code # Optional, auto-detects if not set
# Context enrichment (optional, enhances AI fix quality)
# CODE_CONTEXT_LINES=5 # Lines before/after issue to show AI (default: 5)
# INCLUDE_RULE_DESCRIPTION=true # Include full rule docs in prompts (default: true)
Custom environment files: All CLI commands support --env-file to specify a custom configuration file:
vibe-heal fix src/file.py --env-file .env.production
vibe-heal cleanup --env-file ~/configs/project-a.env
vibe-heal config --env-file .env.staging
Running vibe-heal locally:
# Install in development mode
uv pip install -e .
# Test with actual SonarQube (requires .env.vibeheal)
vibe-heal config # Verify configuration
vibe-heal fix src/file.py --dry-run # Preview issue fixes
vibe-heal fix src/file.py --max-issues 1 # Fix one issue
vibe-heal dedupe src/file.py --dry-run # Preview deduplication
vibe-heal dedupe src/file.py --max-duplications 1 # Fix one duplication
Testing specific modules:
# Run specific test file
uv run pytest tests/sonarqube/test_client.py -v
# Run with one test function
uv run pytest tests/sonarqube/test_client.py::TestSonarQubeClient::test_get_issues_for_file -v
# Run tests for one module with coverage
uv run pytest tests/processor/ -v --cov=src/vibe_heal/processor
Adding a new AI tool:
src/vibe_heal/ai_tools/new_tool.py implementing AITool ABCAIToolType enum in base.pyAIToolFactory._tool_maptests/ai_tools/test_new_tool.pyModifying SonarQube API queries:
src/vibe_heal/sonarqube/client.pymodel_config = {"extra": "ignore"})tests/sonarqube/fixtures/api_responses.jsonChanging commit message format:
GitManager._create_commit_message() in src/vibe_heal/git/manager.pytests/git/test_manager.pySubprocess conventions:
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL when output is not consumed — capture_output=True buffers output into memory for nothing and hides diagnostics.shlex.split(), not str.split() — the latter breaks on quoted arguments and paths with spaces.subprocess.run calls that can fail due to misconfiguration in try/except OSError and raise a domain-specific error (e.g. GitOperationError).Mocking stdlib symbols in tests:
patch("vibe_heal.git.manager.subprocess.run") and patch("vibe_heal.git.manager.shutil.which") rather than patch("subprocess.run") or patch("shutil.which"). This keeps mocks scoped to the unit under test and prevents leakage across tests.Retry loops and mypy:
return None # unreachable at the end of functions — mypy accepts it but it’s misleading dead code. Instead, collect the last exception in a last_error variable, break out of the loop, then assert last_error is not None followed by raise last_error. The assert is genuinely reachable (any failure path sets last_error), mypy is satisfied, and there is no dead code.