git:20260807.d28ba55 to git:20260829.7d4bb43
22 added, 269 removed. Audit A to A.
# QA Runner Module
- **Fast quality assurance execution for Python projects.**
-
- Provides automated QA runner that executes all Python quality checks (ruff, mypy, black, pytest, bandit) and provides structured summary for downstream processing.
-
- ## Purpose
-
- - Execute Python QA tools quickly via subprocess
- - Parse tool output and extract error/warning counts
- - Generate structured results (JSON-serializable)
- - Support integration with hook system or CLI
-
- ## Architecture
-
- ### Core Classes
-
- **QAExecutionError**
-
- - Exception raised when QA tool execution fails
- - Wraps subprocess errors (timeout, crash, etc.)
-
- **ToolResult**
-
- - Data class representing single tool execution
- - Fields: tool_name, passed, error_count, warning_count, output, duration_ms, files_affected
- - Serializable to dict/JSON
-
- **QAResult**
-
- - Data class representing overall QA execution
- - Fields: status (passed/failed), tools_run, total_errors, total_warnings, tool_results, summary
- - Contains results from all tools
- - Serializable to dict/JSON
-
- **QARunner**
-
- - Main orchestrator class
- - Methods:
- - `run_ruff()` - Execute ruff linting (JSON output)
- - `run_mypy()` - Execute mypy type checking
- - `run_black()` - Execute black format check
- - `run_pytest()` - Execute pytest tests
- - `run_bandit()` - Execute bandit security linting (optional)
- - `run_all()` - Execute all configured tools
- - `save_results()` - Save QAResult to JSON file
- - `print_summary()` - Print results to console
-
- ### Parsing
-
- Static helper methods to extract counts from tool output:
-
- - `_parse_ruff_output()` - Parse JSON array from ruff
- - `_parse_mypy_output()` - Count errors from "Found X errors" pattern
- - `_parse_black_output()` - Count "would reformat" lines
- - `_parse_pytest_output()` - Count "X failed" from output
- - `_parse_bandit_output()` - Parse JSON results array
-
- ## Usage
-
- ### As Module
-
- ```python
- from claude_code_hooks_daemon.qa import QARunner
-
- runner = QARunner(project_root="/path/to/project")
- result = runner.run_all()
-
- print(f"Status: {result.status}")
- print(f"Total errors: {result.total_errors}")
-
- # Save to JSON
- filepath = runner.save_results(result)
- ```
-
- ### As Command Line
-
- ```bash
- ./scripts/run-qa-runner.sh /path/to/project "ruff,mypy,black,pytest" true
- ```
-
- Exit codes:
-
- - 0: All checks passed
- - 1: Some checks failed
- - 2: Execution error
-
- ## Configuration
-
- **Tools to run** (via `tools_to_run` attribute):
-
- ```python
- runner.tools_to_run = ["ruff", "mypy", "black", "pytest"]
- # Optional: add bandit for security checks
- runner.tools_to_run = ["ruff", "mypy", "black", "pytest", "bandit"]
- ```
-
- **Output directory** (for JSON results):
-
- ```python
- runner = QARunner(
- project_root="/path/to/project",
- output_dir="/var/qa"
- )
- ```
-
- ## Output Format
-
- ### JSON Structure
-
- ```json
- {
- "status": "passed",
- "tools_run": ["ruff", "mypy", "black", "pytest"],
- "total_errors": 0,
- "total_warnings": 0,
- "timestamp": "2025-01-20T10:00:00Z",
- "tool_results": [
- {
- "tool_name": "ruff",
- "passed": true,
- "error_count": 0,
- "warning_count": 0,
- "output": "[]",
- "duration_ms": 1234
- },
- {
- "tool_name": "mypy",
- "passed": true,
- "error_count": 0,
- "warning_count": 0,
- "output": "Success: no issues found in 42 source files",
- "duration_ms": 5678
- }
- ],
- "summary": "..."
- }
- ```
-
- ### Console Output
-
- ```
- ======================================================================
- QA SUMMARY (Python Tools)
- ======================================================================
- Total Errors: 0
- Total Warnings: 0
- Tools Passed: 4
- Tools Failed: 0
-
- Status: PASSED
- ======================================================================
- ```
-
- ## Tool-Specific Output Parsing
-
- ### Ruff (JSON)
-
- ```json
- [
- {
- "code": "F401",
- "message": "'os' imported but unused",
- "location": {"row": 1, "column": 0},
- "filename": "src/module.py"
- }
- ]
- ```
-
- ### Mypy (Text)
-
- ```
- src/module.py:10: error: Incompatible return value type
- Found 5 errors in 2 files
- ```
-
- ### Black (Text)
-
- ```
- would reformat src/module.py
- would reformat tests/test_module.py
- Oh no! 2 files would be reformatted, 10 files would be left unchanged.
- ```
-
- ### Pytest (Text)
-
- ```
- collected 28 items
- tests/test_module.py ............................ [100%]
- ============================== 28 passed in 1.5s ===============================
- ```
-
- ### Bandit (JSON)
-
- ```json
- {
- "results": [
- {
- "test_id": "B602",
- "issue_text": "subprocess call with shell=True",
- "filename": "src/module.py",
- "line_number": 10
- }
- ],
- "errors": []
- }
- ```
-
- ## Error Handling
-
- Errors during tool execution:
-
- - Timeout: QAExecutionError("Command timeout: ...")
- - Crash: QAExecutionError("Execution error: ...")
- - Exception: Caught and logged in tool result with error_count=-1
-
- Tool execution is resilient - one tool failure doesn't stop others.
-
- ## Performance
-
- - Typical execution: 15-60 seconds for all checks
- - ruff check: ~1-3s (very fast)
- - mypy check: ~5-15s (can be slow on large codebases)
- - black check: ~1-3s
- - pytest: ~5-30s (depends on test count)
- - bandit check: ~2-5s
-
- ## Testing
-
- All functionality covered by unit tests:
-
- - Data class serialisation
- - Tool execution (mocked subprocess)
- - Output parsing (regex patterns)
- - Error handling
- - File I/O (results saving)
- - Integration flow
-
- Run tests:
-
- ```bash
- python3 -m pytest tests/unit/test_qa_runner.py -v
- ```
-
- ## Dependencies
-
- **Required** (in pyproject.toml):
-
- - ruff>=0.1.0
- - mypy>=1.0
- - black>=23.0
- - pytest>=7.0
- - pytest-cov>=4.0
-
- **Optional**:
-
- - bandit>=1.7 (security linting)
+ Automated QA execution for Python projects: `runner.py` runs the configured
+ tools (ruff, mypy, black, pytest, optionally bandit) via subprocess, parses
+ their output into structured, JSON-serialisable results, and prints a
+ summary. `strategy_pattern_checker.py` AST-checks strategy modules;
+ `pytest_text_report.py` renders pytest output for LLM consumption.
- ## Integration Points
+ **The code is the API reference.** Class and method behaviour
+ (`QARunner`, `QAResult`, `ToolResult`, `QAExecutionError`, the per-tool
+ parsers) is documented by the module's own docstrings โ read `runner.py`
+ rather than a hand-maintained mirror here.
- - **Hooks daemon**: Can be invoked as handler for QA validation
- - **CLI**: Command-line interface for manual QA runs
- - **Agents**: QA agents can import and use this module
- - **Build system**: Can be integrated into build hooks
- - **CI/CD**: Exit codes suitable for CI pipelines
+ ## Entry points
- ## Maintenance
+ - Python: `from claude_code_hooks_daemon.qa import QARunner` โ
+ `QARunner(project_root=...).run_all()`; `save_results()` writes JSON.
+ - Shell: `./scripts/run-qa-runner.sh /path/to/project "ruff,mypy,black,pytest" true`
+ - Exit codes: `0` all passed, `1` some checks failed, `2` execution error
+ (CI-suitable).
- - Tests cover all public methods
- - Output parsing via stable regex patterns (based on tool output formats)
- - Robust error handling for tool failures
- - Clear error messages for debugging
+ ## Local invariants
- ---
+ - One tool failing never stops the others โ execution is resilient; a tool
+ that crashes is recorded in its result rather than aborting the run.
+ - This module is NOT the project's own QA gate: contributors run
+ `./scripts/qa/llm_qa.py all` (see [CLAUDE/QA.md](/CLAUDE/QA.md), the
+ canonical QA policy).
- **Module**: claude_code_hooks_daemon.qa.runner
- **Python Tools**: ruff, mypy, black, pytest, bandit (optional)
+ Tests: `tests/unit/test_qa_runner.py`.