Skip to content

feat: add dead code detector tool#54

Open
nnennandukwe wants to merge 1 commit intofeature/44-complexity-analysisfrom
feature/45-dead-code-detection
Open

feat: add dead code detector tool#54
nnennandukwe wants to merge 1 commit intofeature/44-complexity-analysisfrom
feature/45-dead-code-detection

Conversation

@nnennandukwe
Copy link
Copy Markdown
Owner

Summary

Closes #45
Depends on: #53 (complexity analysis), #52 (core module)

Adds a new dead_code_detection MCP tool that identifies unused and unreachable Python code using Astroid AST analysis and a symbol usage graph.

What changed

  • New dead_code_detection/ module with four files:

    • detector.pyDeadCodeDetector class orchestrating all checks, plus detect_dead_code() convenience function
    • usage_graph.pyUsageGraph class and build_usage_graph() that walks the AST to track symbol definitions vs references, including __all__ awareness
    • patterns.pyDeadCodeCategory enum, DeadCodeIssue dataclass, Severity enum, EXTERNALLY_USED_DECORATORS set, and IGNORED_PARAMETERS set
    • __init__.py — public API re-exports
  • server.py changes (incremental on top of feat: add complexity analyzer tool #53):

    • Added from .dead_code_detection import DeadCodeDetector import
    • Registered dead_code_detection tool in _handle_list_tools with full JSON Schema (including check_imports, check_variables, check_functions, ignore_patterns options)
    • Added elif name == "dead_code_detection" dispatch in _handle_call_tool
    • Added _execute_dead_code_detection() handler method with validation, ignore_patterns type checking, and error handling
  • 64 new tests in tests/test_dead_code_detection.py covering:

    • Unused imports: regular, from-imports, aliased, used-not-flagged, __all__ exemption, multi-name partial usage
    • Unused variables: basic, used-not-flagged, underscore prefix skip, dunder skip, augmented assignment as reference
    • Unused functions: basic, called-not-flagged, test_ prefix skip, dunder methods skip, @property/@staticmethod/@fixture decorator skip, __all__ exemption, public vs private severity
    • Unused parameters: basic, used-not-flagged, self/cls skip, stub functions (pass/Ellipsis/docstring+pass) skip
    • Unreachable code: after return, raise, break, continue
    • Redundant conditions: if True, if False, while False
    • Ignore patterns: imports, functions, variables
    • Combined detection: multiple issue types, summary counts, empty module
    • MCP server integration: tool listing, schema, source_code/file_path invocation, error cases (no input, syntax error, both inputs, invalid ignore_patterns, non-string file_path), check option toggles

Dead code patterns detected

Pattern Category Severity
Unused imports unused_import info
Unused variables unused_variable warning
Unused functions (public) unused_function info
Unused functions (private _) unused_function warning
Unused parameters unused_parameter info
Code after return/raise/break/continue unreachable_code warning
if True / if False / while False redundant_condition warning

Additions beyond the original issue

  • UsageGraph with __all__ support — symbols listed in __all__ are automatically exempted from unused detection, since they're part of the public API
  • Stub function detection — parameters in functions whose body is just pass, ..., or docstring + pass are skipped (these are interface stubs)
  • Framework decorator awareness — functions decorated with @property, @staticmethod, @classmethod, @abstractmethod, @pytest.fixture, @app.route, @router.get/post/put/delete, @click.command, @celery.task are not flagged as unused
  • Severity differentiation — public unused functions get info severity (lower confidence, could be used externally) while private _-prefixed functions get warning (higher confidence they're truly dead)
  • detect_dead_code() convenience function — functional API alternative to the class-based DeadCodeDetector

What was NOT included from the issue

  • Unused classes — the issue mentioned this but the detector currently only checks functions, not class instantiation/subclassing. Could be added later.
  • Unused class attributes — requires tracking attribute access patterns (self.attr) which adds significant complexity. Deferred.

Test plan

  • All 269 tests pass (poetry run pytest — 205 from base + 64 new)
  • ruff check and ruff format --check pass
  • Pre-commit hooks pass
  • MCP integration tests verify tool registration, schema, invocation, and 10 error/edge cases
  • Final server.py is identical to the original working state (verified with git diff)

Note: This PR targets feature/44-complexity-analysis. Merge order: #52#53#54 (this).

main ← PR #52 (core) ← PR #53 (complexity) ← PR #54 (this)

🤖 Generated with Claude Code

Adds a new dead_code_detection MCP tool that identifies unused imports,
unused variables, unused functions, unused parameters, unreachable code
after return/raise/break/continue, and redundant conditions (if True,
if False, while False). Builds a usage graph from the Astroid AST to
track symbol definitions vs references. Supports ignore_patterns for
skipping intentional symbols and per-category check toggles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add dead code detection MCP tool with AST analysis

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds new dead_code_detection MCP tool for identifying unused Python code
  - Detects unused imports, variables, functions, parameters
  - Detects unreachable code after return/raise/break/continue
  - Detects redundant conditions (if True, if False, while False)
• Implements DeadCodeDetector class with AST-based analysis using Astroid
  - Builds usage graph to track symbol definitions vs references
  - Supports __all__ awareness for public API exports
  - Includes stub function detection and framework decorator awareness
• Registers tool in MCP server with full JSON Schema and validation
  - Supports per-category check toggles and regex ignore patterns
  - Handles both file_path and source_code inputs with error handling
• Adds 64 comprehensive tests covering all detection patterns and edge cases
Diagram
flowchart LR
  A["Python Source Code"] -->|parse| B["Astroid AST"]
  B -->|build| C["UsageGraph"]
  C -->|analyze| D["DeadCodeDetector"]
  D -->|detect| E["DeadCodeResult"]
  E -->|format| F["MCP Tool Response"]
  G["MCP Server"] -->|register| H["dead_code_detection Tool"]
  H -->|invoke| D
Loading

Grey Divider

File Changes

1. src/workshop_mcp/dead_code_detection/__init__.py ✨ Enhancement +18/-0

Public API module initialization

• Exports public API for dead code detection module
• Re-exports DeadCodeDetector, DeadCodeResult, DeadCodeSummary, detect_dead_code
• Re-exports DeadCodeCategory, DeadCodeIssue, UsageGraph, build_usage_graph

src/workshop_mcp/dead_code_detection/init.py


2. src/workshop_mcp/dead_code_detection/detector.py ✨ Enhancement +450/-0

Main dead code detector orchestration logic

• Implements DeadCodeDetector class orchestrating all dead code checks
• Detects unused imports, variables, functions, parameters with severity levels
• Detects unreachable code after return/raise/break/continue statements
• Detects redundant conditions (if True, if False, while False)
• Supports ignore patterns via regex, per-category check toggles, and __all__ awareness
• Includes stub function detection and framework decorator awareness
• Provides detect_dead_code() convenience function

src/workshop_mcp/dead_code_detection/detector.py


3. src/workshop_mcp/dead_code_detection/patterns.py ✨ Enhancement +57/-0

Dead code pattern definitions and enums

• Defines DeadCodeCategory enum with six issue types
• Defines Severity enum (INFO, WARNING, ERROR)
• Defines DeadCodeIssue dataclass for issue representation
• Defines EXTERNALLY_USED_DECORATORS set for framework-aware detection
• Defines IGNORED_PARAMETERS set for conventional parameters

src/workshop_mcp/dead_code_detection/patterns.py


View more (3)
4. src/workshop_mcp/dead_code_detection/usage_graph.py ✨ Enhancement +99/-0

Symbol usage graph construction and tracking

• Implements UsageGraph class tracking symbol definitions and references
• Implements SymbolDefinition dataclass for symbol metadata
• Implements build_usage_graph() to walk AST and collect references
• Extracts __all__ exports for public API awareness
• Handles Name nodes in read contexts and Attribute chains

src/workshop_mcp/dead_code_detection/usage_graph.py


5. src/workshop_mcp/server.py ✨ Enhancement +187/-0

MCP server integration for dead code detection

• Imports DeadCodeDetector from dead_code_detection module
• Registers dead_code_detection tool in _handle_list_tools with full JSON Schema
• Adds tool dispatch in _handle_call_tool for dead_code_detection
• Implements _execute_dead_code_detection() handler with validation and error handling
• Validates file_path/source_code inputs, ignore_patterns type checking, and syntax errors

src/workshop_mcp/server.py


6. tests/test_dead_code_detection.py 🧪 Tests +702/-0

Comprehensive test suite for dead code detection

• Adds 64 comprehensive tests covering detector initialization and all detection patterns
• Tests unused imports (regular, from-imports, aliased, __all__ exemption)
• Tests unused variables (basic, underscore prefix skip, augmented assignment)
• Tests unused functions (public vs private severity, decorators, test_ prefix skip)
• Tests unused parameters (self/cls skip, stub function skip, framework decorators)
• Tests unreachable code after return/raise/break/continue
• Tests redundant conditions (if True, if False, while False)
• Tests ignore patterns, combined detection, and MCP server integration
• Tests error cases (no input, syntax error, both inputs, invalid ignore_patterns)

tests/test_dead_code_detection.py


Grey Divider

Qodo Logo


🛠️ Relevant configurations:


These are the relevant configurations for this tool:

[config]

is_auto_command: True
is_new_pr: True
model_reasoning: vertex_ai/gemini-2.5-pro
model: gpt-5.2-2025-12-11
model_turbo: anthropic/claude-haiku-4-5-20251001
fallback_models: ['anthropic/claude-sonnet-4-5-20250929', 'bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0']
second_model_for_exhaustive_mode: o4-mini
git_provider: github
publish_output: True
publish_output_no_suggestions: True
publish_output_progress: True
verbosity_level: 0
publish_logs: False
debug_mode: False
use_wiki_settings_file: True
use_repo_settings_file: True
use_global_settings_file: True
use_global_wiki_settings_file: False
disable_auto_feedback: False
ai_timeout: 150
response_language: en-US
clone_repo_instead_of_fetch: True
always_clone: False
add_repo_metadata: True
clone_repo_time_limit: 300
publish_inline_comments_fallback_batch_size: 5
publish_inline_comments_fallback_sleep_time: 2
max_model_tokens: 32000
custom_model_max_tokens: -1
patch_extension_skip_types: ['.md', '.txt']
extra_allowed_extensions: []
allow_dynamic_context: True
allow_forward_dynamic_context: True
max_extra_lines_before_dynamic_context: 12
patch_extra_lines_before: 5
patch_extra_lines_after: 1
ai_handler: litellm
cli_mode: False
TRIAL_GIT_ORG_MAX_INVOKES_PER_MONTH: 30
TRIAL_RATIO_CLOSE_TO_LIMIT: 0.8
invite_only_mode: False
enable_request_access_msg_on_new_pr: False
check_also_invites_field: False
calculate_context: True
disable_checkboxes: False
output_relevant_configurations: True
large_patch_policy: clip
seed: -1
temperature: 0.2
allow_dynamic_context_ab_testing: False
choose_dynamic_context_ab_testing_ratio: 0.5
ignore_pr_title: ['^\\[Auto\\]', '^Auto']
ignore_pr_target_branches: []
ignore_pr_source_branches: []
ignore_pr_labels: []
ignore_ticket_labels: []
allow_only_specific_folders: []
ignore_pr_authors: []
ignore_repositories: []
ignore_language_framework: []
enable_ai_metadata: True
present_reasoning: True
max_tickets: 10
max_tickets_chars: 8000
prevent_any_approval: False
enable_comment_approval: False
enable_auto_approval: False
auto_approve_for_low_review_effort: -1
auto_approve_for_no_suggestions: False
ensure_ticket_compliance: False
new_diff_format: True
new_diff_format_add_external_references: True
tasks_queue_ttl_from_dequeue_in_seconds: 900
enable_custom_labels: False

[pr_description]

publish_labels: False
add_original_user_description: True
generate_ai_title: False
extra_instructions: 
enable_pr_type: True
final_update_message: True
enable_help_text: False
enable_help_comment: False
bring_latest_tag: False
enable_pr_diagram: True
publish_description_as_comment: False
publish_description_as_comment_persistent: True
enable_semantic_files_types: True
collapsible_file_list: adaptive
collapsible_file_list_threshold: 8
inline_file_summary: False
use_description_markers: False
include_generated_by_header: True
enable_large_pr_handling: True
max_ai_calls: 4
auto_create_ticket: False

@qodo-code-review
Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (5) 📎 Requirement gaps (2)

Grey Divider


Action required

1. Dead code tool wrong path 📎 Requirement gap ✓ Correctness
Description
• The dead code detection implementation is added under src/workshop_mcp/dead_code_detection/
  instead of the required src/workshop_mcp/tools/dead_code_detection/ location.
• This breaks the repository’s prescribed tool structure/discoverability expectations and can cause
  import/packaging inconsistencies for MCP tools.
Code

src/workshop_mcp/dead_code_detection/init.py[R1-8]

+"""Dead code detection tools for identifying unused Python code."""
+
+__version__ = "0.1.0"
+
+from .detector import DeadCodeDetector, DeadCodeResult, DeadCodeSummary, detect_dead_code
+from .patterns import DeadCodeCategory, DeadCodeIssue
+from .usage_graph import UsageGraph, build_usage_graph
+
Evidence
Compliance ID 8 requires the implementation to live under
src/workshop_mcp/tools/dead_code_detection/, but the PR adds the module under
src/workshop_mcp/dead_code_detection/ and imports it from that location.

Create tool module directory at src/workshop_mcp/tools/dead_code_detection/
src/workshop_mcp/dead_code_detection/init.py[1-8]
src/workshop_mcp/server.py[17-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The dead code detection tool code is not located in the required tool module directory (`src/workshop_mcp/tools/dead_code_detection/`).

## Issue Context
The compliance checklist mandates this directory structure for discoverability/consistency.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/__init__.py[1-18]
- src/workshop_mcp/dead_code_detection/detector.py[1-450]
- src/workshop_mcp/dead_code_detection/patterns.py[1-57]
- src/workshop_mcp/dead_code_detection/usage_graph.py[1-99]
- src/workshop_mcp/server.py[15-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. No unused class detection 📎 Requirement gap ✓ Correctness
Description
• The detector currently reports unused imports/variables/functions/parameters plus unreachable and
  redundant conditions, but does not implement detection for unused classes or class attributes.
• This means the tool does not meet the required detection coverage and will miss entire categories
  of dead code that the interface promises to detect.
Code

src/workshop_mcp/dead_code_detection/detector.py[R86-120]

+    def detect_all(self) -> DeadCodeResult:
+        """Run all enabled dead code checks.
+
+        Returns:
+            DeadCodeResult with issues and summary.
+        """
+        result = DeadCodeResult()
+
+        if self.check_imports:
+            issues = self._detect_unused_imports()
+            result.issues.extend(issues)
+            result.summary.unused_imports = len(issues)
+
+        if self.check_variables:
+            issues = self._detect_unused_variables()
+            result.issues.extend(issues)
+            result.summary.unused_variables = len(issues)
+
+        if self.check_functions:
+            issues = self._detect_unused_functions()
+            result.issues.extend(issues)
+            result.summary.unused_functions = len(issues)
+
+        param_issues = self._detect_unused_parameters()
+        result.issues.extend(param_issues)
+        result.summary.unused_parameters = len(param_issues)
+
+        unreachable_issues = self._detect_unreachable_code()
+        result.issues.extend(unreachable_issues)
+        result.summary.unreachable_blocks = len(unreachable_issues)
+
+        redundant_issues = self._detect_redundant_conditions()
+        result.issues.extend(redundant_issues)
+        result.summary.redundant_conditions = len(redundant_issues)
+
Evidence
Compliance ID 11 requires detecting unused classes and class attributes; the implemented categories
and the detect_all() orchestration contain no class/class-attribute checks or categories.

Detect unused variables, functions, classes, parameters, and class attributes
src/workshop_mcp/dead_code_detection/detector.py[86-120]
src/workshop_mcp/dead_code_detection/patterns.py[7-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tool does not detect unused classes or unused class attributes, but compliance requires both.

## Issue Context
The current implementation only checks imports/variables/functions/parameters/unreachable/redundant, and `DeadCodeCategory` does not include class/class-attribute categories.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/patterns.py[7-16]
- src/workshop_mcp/dead_code_detection/detector.py[86-240]
- src/workshop_mcp/dead_code_detection/usage_graph.py[35-99]
- tests/test_dead_code_detection.py[1-702]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. PermissionError not handled 📘 Rule violation ⛯ Reliability
Description
• The server reads file_path via Path(file_path).read_text(...) but does not handle
  PermissionError/permission-denied cases explicitly.
• This can produce an internal error response instead of a graceful, user-safe, structured error
  when the file exists but is not readable.
Code

src/workshop_mcp/server.py[R846-907]

+            # Read source from file if needed
+            if file_path and not source_code:
+                source_code = Path(file_path).read_text(encoding="utf-8")
+
+            detector = DeadCodeDetector(
+                source_code=source_code,
+                file_path=file_path,
+                check_imports=check_imports,
+                check_variables=check_variables,
+                check_functions=check_functions,
+                ignore_patterns=ignore_patterns,
+            )
+
+            detection_result = detector.detect_all()
+
+            issues_data = [
+                {
+                    "tool": "dead_code",
+                    "category": issue.category.value,
+                    "severity": issue.severity.value,
+                    "message": issue.message,
+                    "line": issue.line,
+                    "name": issue.name,
+                    "suggestion": issue.suggestion,
+                }
+                for issue in detection_result.issues
+            ]
+
+            result = {
+                "content": [
+                    {
+                        "type": "json",
+                        "json": {
+                            "success": True,
+                            "file_analyzed": file_path or "source_code",
+                            "issues": issues_data,
+                            "summary": asdict(detection_result.summary),
+                        },
+                    }
+                ],
+            }
+            return self._success_response(request_id, result)
+
+        except ValueError as exc:
+            logger.warning("ValueError in dead_code_detection: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Invalid parameters"),
+            )
+        except FileNotFoundError as exc:
+            logger.warning("FileNotFoundError in dead_code_detection: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Resource not found"),
+            )
+        except SyntaxError as exc:
+            logger.warning("SyntaxError in dead_code_detection: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Invalid source code syntax"),
+            )
+        except SecurityValidationError as exc:
Evidence
Compliance ID 25 requires permission errors to be handled gracefully. The implementation reads the
file and has specific exception handlers, but none for PermissionError, so permission-denied will
fall into the generic exception path.

AGENTS.md
src/workshop_mcp/server.py[846-849]
src/workshop_mcp/server.py[895-907]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dead_code_detection` reads files and can raise `PermissionError`, which is not handled explicitly, leading to generic internal errors.

## Issue Context
Compliance requires graceful handling of permission issues for filesystem access.

## Fix Focus Areas
- src/workshop_mcp/server.py[820-923]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
4. Invalid regex not validated 📘 Rule violation ⛯ Reliability
Description
• User-provided ignore_patterns are compiled with re.compile(...) without validation; invalid
  patterns can raise re.error at runtime.
• This violates robust edge case management because a common user input error (bad regex) becomes a
  tool failure instead of a clear invalid-params response.
Code

src/workshop_mcp/dead_code_detection/detector.py[R81-86]

+        self.check_imports = check_imports
+        self.check_variables = check_variables
+        self.check_functions = check_functions
+        self._ignore_res = [re.compile(p) for p in (ignore_patterns or [])]
+
+    def detect_all(self) -> DeadCodeResult:
Evidence
Compliance ID 3 requires potential failure points (like regex compilation) to be handled with
meaningful context and graceful degradation. The code compiles patterns without guarding against
invalid regex, and the server-side handler does not map that case to an invalid-params error.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/workshop_mcp/dead_code_detection/detector.py[81-85]
src/workshop_mcp/server.py[913-922]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Invalid regex strings in `ignore_patterns` can raise `re.error` and cause an internal error instead of a structured invalid-params response.

## Issue Context
This is an external input field, so invalid patterns are a foreseeable user error and should be handled explicitly.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/detector.py[43-86]
- src/workshop_mcp/server.py[829-923]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. DeadCodeDetector.__init__ missing return hint 📘 Rule violation ✓ Correctness
Description
DeadCodeDetector.__init__ does not declare an explicit return type (-> None).
• This violates the project requirement that all new/modified functions include type hints, and can
  reduce mypy/static-analysis clarity.
Code

src/workshop_mcp/dead_code_detection/detector.py[R43-52]

+    def __init__(
+        self,
+        source_code: str | None = None,
+        *,
+        file_path: str | None = None,
+        check_imports: bool = True,
+        check_variables: bool = True,
+        check_functions: bool = True,
+        ignore_patterns: list[str] | None = None,
+    ):
Evidence
Compliance ID 18 requires type hints for all new/modified functions; the __init__ signature lacks
an explicit return type annotation.

CLAUDE.md
src/workshop_mcp/dead_code_detection/detector.py[43-52]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DeadCodeDetector.__init__` is missing an explicit return type annotation.

## Issue Context
The compliance checklist requires type hints for all functions.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/detector.py[43-52]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Method calls not counted 🐞 Bug ✓ Correctness
Description
• Class methods are analyzed for “unused function” status, but the usage graph does not record
  attribute/member names as references.
• As a result, calls like obj.method() / self.method() won’t mark method as referenced,
  leading to false-positive UNUSED_FUNCTION issues for real codebases.
Code

src/workshop_mcp/dead_code_detection/usage_graph.py[R88-96]

+    # For attribute access like 'os.path.join', we still want 'os' as referenced
+    elif isinstance(node, astroid.Attribute):
+        # Walk down to the leftmost Name in the chain
+        leftmost = node
+        while isinstance(leftmost, astroid.Attribute):
+            leftmost = leftmost.expr
+        if isinstance(leftmost, astroid.Name):
+            graph.references.add(leftmost.name)
+
Evidence
The usage graph explicitly only records the *leftmost* symbol of an attribute chain (e.g., self
for self.method()), and never records the attribute name (method). Meanwhile, the detector
explicitly checks class-level functions (methods) and decides “unused” solely via
graph.is_referenced(name) where name is the method name—so attribute-invoked methods will appear
unreferenced and be flagged.

src/workshop_mcp/dead_code_detection/usage_graph.py[70-96]
src/workshop_mcp/dead_code_detection/detector.py[188-239]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Class methods can be falsely flagged as unused because `UsageGraph` does not record attribute/method names from calls like `self.foo()`/`obj.foo()`, but `DeadCodeDetector` checks class-level functions (methods) using only `graph.is_referenced(method_name)`.

## Issue Context
`UsageGraph._collect_references()` currently only adds the leftmost name of an attribute chain (e.g., `self` for `self.foo()`), which means method names will not appear in `graph.references`.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/usage_graph.py[70-99]
- src/workshop_mcp/dead_code_detection/detector.py[188-239]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. TOCTOU on file read 🐞 Bug ⛨ Security
Description
• The server validates a path with PathValidator.validate_exists() (which resolves symlinks), but
  then reads the file using the original user-provided string.
• Between validation and read, a symlink can be swapped to point outside allowed roots, turning a
  validated request into an arbitrary file read.
Code

src/workshop_mcp/server.py[R820-848]

+        if file_path:
+            try:
+                self.path_validator.validate_exists(file_path, must_be_file=True)
+            except PathValidationError as e:
+                return self._error_response(
+                    request_id,
+                    JsonRpcError(-32602, str(e)),
+                )
+
+        check_imports = arguments.get("check_imports", True)
+        check_variables = arguments.get("check_variables", True)
+        check_functions = arguments.get("check_functions", True)
+        ignore_patterns = arguments.get("ignore_patterns")
+
+        if ignore_patterns is not None and (
+            not isinstance(ignore_patterns, list)
+            or not all(isinstance(p, str) for p in ignore_patterns)
+        ):
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "ignore_patterns must be a list of strings"),
+            )
+
+        try:
+            logger.info("Executing dead code detection")
+
+            # Read source from file if needed
+            if file_path and not source_code:
+                source_code = Path(file_path).read_text(encoding="utf-8")
Evidence
validate_exists() returns a resolved Path after Path.resolve() (explicitly documenting symlink
resolution), but the returned resolved Path is discarded. The subsequent read uses Path(file_path)
again, which re-evaluates symlinks at open-time, reintroducing a classic validation/open TOCTOU
window.

src/workshop_mcp/server.py[820-848]
src/workshop_mcp/security/path_validator.py[22-31]
src/workshop_mcp/security/path_validator.py[154-178]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
File reads occur using the original `file_path` string after validation, allowing a symlink swap between validation and open to bypass allowed-root constraints.

## Issue Context
`PathValidator.validate_exists()` already returns a resolved `Path` (via `Path.resolve()`), which should be used for subsequent filesystem operations.

## Fix Focus Areas
- src/workshop_mcp/server.py[820-849]
- src/workshop_mcp/security/path_validator.py[102-178]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. check_imports type not validated 📘 Rule violation ⛨ Security
Description
• The handler accepts check_imports, check_variables, and check_functions from external input
  without validating they are booleans.
• Non-boolean values (e.g., strings) can change behavior via truthiness and undermine predictable,
  secure input handling.
Code

src/workshop_mcp/server.py[R829-833]

+        check_imports = arguments.get("check_imports", True)
+        check_variables = arguments.get("check_variables", True)
+        check_functions = arguments.get("check_functions", True)
+        ignore_patterns = arguments.get("ignore_patterns")
+
Evidence
Compliance ID 6 requires validation of external inputs. The code directly reads these fields from
arguments without enforcing boolean type constraints.

Rule 6: Generic: Security-First Input Validation and Data Handling
src/workshop_mcp/server.py[829-833]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `check_*` toggles are not type-validated, allowing non-boolean inputs that can change behavior unexpectedly.

## Issue Context
These fields are user-controlled MCP arguments and should be validated as booleans.

## Fix Focus Areas
- src/workshop_mcp/server.py[829-842]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Long test line exceeds 100 📘 Rule violation ✓ Correctness
Description
• The new test file includes lines that exceed the configured 100-character limit, which will
  violate Ruff formatting/lint expectations.
• This increases review friction and can cause CI lint failures where Ruff is enforced.
Code

tests/test_dead_code_detection.py[R35-36]

+        import_issues = [i for i in result.issues if i.category == DeadCodeCategory.UNUSED_IMPORT]
+        assert len(import_issues) == 1
Evidence
Compliance ID 17 requires Ruff formatting with a 100-character line length; the shown list
comprehension line is well over 100 characters.

CLAUDE.md; AGENTS.md
tests/test_dead_code_detection.py[35-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Some added test lines exceed the 100-character limit required by Ruff.

## Issue Context
Ruff formatting/linting is expected to be enforced for Python sources.

## Fix Focus Areas
- tests/test_dead_code_detection.py[27-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +1 to +8
"""Dead code detection tools for identifying unused Python code."""

__version__ = "0.1.0"

from .detector import DeadCodeDetector, DeadCodeResult, DeadCodeSummary, detect_dead_code
from .patterns import DeadCodeCategory, DeadCodeIssue
from .usage_graph import UsageGraph, build_usage_graph

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Dead code tool wrong path 📎 Requirement gap ✓ Correctness

• The dead code detection implementation is added under src/workshop_mcp/dead_code_detection/
  instead of the required src/workshop_mcp/tools/dead_code_detection/ location.
• This breaks the repository’s prescribed tool structure/discoverability expectations and can cause
  import/packaging inconsistencies for MCP tools.
Agent Prompt
## Issue description
The dead code detection tool code is not located in the required tool module directory (`src/workshop_mcp/tools/dead_code_detection/`).

## Issue Context
The compliance checklist mandates this directory structure for discoverability/consistency.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/__init__.py[1-18]
- src/workshop_mcp/dead_code_detection/detector.py[1-450]
- src/workshop_mcp/dead_code_detection/patterns.py[1-57]
- src/workshop_mcp/dead_code_detection/usage_graph.py[1-99]
- src/workshop_mcp/server.py[15-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +86 to +120
def detect_all(self) -> DeadCodeResult:
"""Run all enabled dead code checks.

Returns:
DeadCodeResult with issues and summary.
"""
result = DeadCodeResult()

if self.check_imports:
issues = self._detect_unused_imports()
result.issues.extend(issues)
result.summary.unused_imports = len(issues)

if self.check_variables:
issues = self._detect_unused_variables()
result.issues.extend(issues)
result.summary.unused_variables = len(issues)

if self.check_functions:
issues = self._detect_unused_functions()
result.issues.extend(issues)
result.summary.unused_functions = len(issues)

param_issues = self._detect_unused_parameters()
result.issues.extend(param_issues)
result.summary.unused_parameters = len(param_issues)

unreachable_issues = self._detect_unreachable_code()
result.issues.extend(unreachable_issues)
result.summary.unreachable_blocks = len(unreachable_issues)

redundant_issues = self._detect_redundant_conditions()
result.issues.extend(redundant_issues)
result.summary.redundant_conditions = len(redundant_issues)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. No unused class detection 📎 Requirement gap ✓ Correctness

• The detector currently reports unused imports/variables/functions/parameters plus unreachable and
  redundant conditions, but does not implement detection for unused classes or class attributes.
• This means the tool does not meet the required detection coverage and will miss entire categories
  of dead code that the interface promises to detect.
Agent Prompt
## Issue description
The tool does not detect unused classes or unused class attributes, but compliance requires both.

## Issue Context
The current implementation only checks imports/variables/functions/parameters/unreachable/redundant, and `DeadCodeCategory` does not include class/class-attribute categories.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/patterns.py[7-16]
- src/workshop_mcp/dead_code_detection/detector.py[86-240]
- src/workshop_mcp/dead_code_detection/usage_graph.py[35-99]
- tests/test_dead_code_detection.py[1-702]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +846 to +907
# Read source from file if needed
if file_path and not source_code:
source_code = Path(file_path).read_text(encoding="utf-8")

detector = DeadCodeDetector(
source_code=source_code,
file_path=file_path,
check_imports=check_imports,
check_variables=check_variables,
check_functions=check_functions,
ignore_patterns=ignore_patterns,
)

detection_result = detector.detect_all()

issues_data = [
{
"tool": "dead_code",
"category": issue.category.value,
"severity": issue.severity.value,
"message": issue.message,
"line": issue.line,
"name": issue.name,
"suggestion": issue.suggestion,
}
for issue in detection_result.issues
]

result = {
"content": [
{
"type": "json",
"json": {
"success": True,
"file_analyzed": file_path or "source_code",
"issues": issues_data,
"summary": asdict(detection_result.summary),
},
}
],
}
return self._success_response(request_id, result)

except ValueError as exc:
logger.warning("ValueError in dead_code_detection: %s", exc)
return self._error_response(
request_id,
JsonRpcError(-32602, "Invalid parameters"),
)
except FileNotFoundError as exc:
logger.warning("FileNotFoundError in dead_code_detection: %s", exc)
return self._error_response(
request_id,
JsonRpcError(-32602, "Resource not found"),
)
except SyntaxError as exc:
logger.warning("SyntaxError in dead_code_detection: %s", exc)
return self._error_response(
request_id,
JsonRpcError(-32602, "Invalid source code syntax"),
)
except SecurityValidationError as exc:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Permissionerror not handled 📘 Rule violation ⛯ Reliability

• The server reads file_path via Path(file_path).read_text(...) but does not handle
  PermissionError/permission-denied cases explicitly.
• This can produce an internal error response instead of a graceful, user-safe, structured error
  when the file exists but is not readable.
Agent Prompt
## Issue description
`dead_code_detection` reads files and can raise `PermissionError`, which is not handled explicitly, leading to generic internal errors.

## Issue Context
Compliance requires graceful handling of permission issues for filesystem access.

## Fix Focus Areas
- src/workshop_mcp/server.py[820-923]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +81 to +86
self.check_imports = check_imports
self.check_variables = check_variables
self.check_functions = check_functions
self._ignore_res = [re.compile(p) for p in (ignore_patterns or [])]

def detect_all(self) -> DeadCodeResult:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Invalid regex not validated 📘 Rule violation ⛯ Reliability

• User-provided ignore_patterns are compiled with re.compile(...) without validation; invalid
  patterns can raise re.error at runtime.
• This violates robust edge case management because a common user input error (bad regex) becomes a
  tool failure instead of a clear invalid-params response.
Agent Prompt
## Issue description
Invalid regex strings in `ignore_patterns` can raise `re.error` and cause an internal error instead of a structured invalid-params response.

## Issue Context
This is an external input field, so invalid patterns are a foreseeable user error and should be handled explicitly.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/detector.py[43-86]
- src/workshop_mcp/server.py[829-923]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +43 to +52
def __init__(
self,
source_code: str | None = None,
*,
file_path: str | None = None,
check_imports: bool = True,
check_variables: bool = True,
check_functions: bool = True,
ignore_patterns: list[str] | None = None,
):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. deadcodedetector.init missing return hint 📘 Rule violation ✓ Correctness

DeadCodeDetector.__init__ does not declare an explicit return type (-> None).
• This violates the project requirement that all new/modified functions include type hints, and can
  reduce mypy/static-analysis clarity.
Agent Prompt
## Issue description
`DeadCodeDetector.__init__` is missing an explicit return type annotation.

## Issue Context
The compliance checklist requires type hints for all functions.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/detector.py[43-52]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +88 to +96
# For attribute access like 'os.path.join', we still want 'os' as referenced
elif isinstance(node, astroid.Attribute):
# Walk down to the leftmost Name in the chain
leftmost = node
while isinstance(leftmost, astroid.Attribute):
leftmost = leftmost.expr
if isinstance(leftmost, astroid.Name):
graph.references.add(leftmost.name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

6. Method calls not counted 🐞 Bug ✓ Correctness

• Class methods are analyzed for “unused function” status, but the usage graph does not record
  attribute/member names as references.
• As a result, calls like obj.method() / self.method() won’t mark method as referenced,
  leading to false-positive UNUSED_FUNCTION issues for real codebases.
Agent Prompt
## Issue description
Class methods can be falsely flagged as unused because `UsageGraph` does not record attribute/method names from calls like `self.foo()`/`obj.foo()`, but `DeadCodeDetector` checks class-level functions (methods) using only `graph.is_referenced(method_name)`.

## Issue Context
`UsageGraph._collect_references()` currently only adds the leftmost name of an attribute chain (e.g., `self` for `self.foo()`), which means method names will not appear in `graph.references`.

## Fix Focus Areas
- src/workshop_mcp/dead_code_detection/usage_graph.py[70-99]
- src/workshop_mcp/dead_code_detection/detector.py[188-239]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +820 to +848
if file_path:
try:
self.path_validator.validate_exists(file_path, must_be_file=True)
except PathValidationError as e:
return self._error_response(
request_id,
JsonRpcError(-32602, str(e)),
)

check_imports = arguments.get("check_imports", True)
check_variables = arguments.get("check_variables", True)
check_functions = arguments.get("check_functions", True)
ignore_patterns = arguments.get("ignore_patterns")

if ignore_patterns is not None and (
not isinstance(ignore_patterns, list)
or not all(isinstance(p, str) for p in ignore_patterns)
):
return self._error_response(
request_id,
JsonRpcError(-32602, "ignore_patterns must be a list of strings"),
)

try:
logger.info("Executing dead code detection")

# Read source from file if needed
if file_path and not source_code:
source_code = Path(file_path).read_text(encoding="utf-8")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

7. Toctou on file read 🐞 Bug ⛨ Security

• The server validates a path with PathValidator.validate_exists() (which resolves symlinks), but
  then reads the file using the original user-provided string.
• Between validation and read, a symlink can be swapped to point outside allowed roots, turning a
  validated request into an arbitrary file read.
Agent Prompt
## Issue description
File reads occur using the original `file_path` string after validation, allowing a symlink swap between validation and open to bypass allowed-root constraints.

## Issue Context
`PathValidator.validate_exists()` already returns a resolved `Path` (via `Path.resolve()`), which should be used for subsequent filesystem operations.

## Fix Focus Areas
- src/workshop_mcp/server.py[820-849]
- src/workshop_mcp/security/path_validator.py[102-178]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant