Improve CyclomaticComplexityAssessor with streaming subprocess and lizard scoring#498
Improve CyclomaticComplexityAssessor with streaming subprocess and lizard scoring#498mikebonnet wants to merge 13 commits into
Conversation
Adds a streaming variant of safe_subprocess_run that yields stdout line-by-line via a Popen-backed iterator, enabling progress display for long-running commands while enforcing the same security guardrails (timeout, shell rejection, path validation, output size limits, error sanitization). Includes 20 new tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrates all three complexity assessment methods (radon, lizard, gocyclo) from buffered safe_subprocess_run to streaming safe_subprocess_run_stream, enabling incremental output processing for large repositories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse AvgCCN from the last line of lizard's summary output to produce a proper complexity Finding. Remove the returncode check since lizard returns non-zero when warnings are present during normal operation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MissingToolError on non-zero exit was misleading — it implies radon is not installed (FileNotFoundError), not that it failed during execution. Now raises CalledProcessError with sanitized stderr instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Check golangci-lint config before running gocyclo - Parse average from last line only instead of scanning every line - Raise CalledProcessError with sanitized stderr on gocyclo failure - Only raise MissingToolError on FileNotFoundError - Add -top 1 flag to limit per-function output Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass -i -1 to suppress non-zero exit codes caused by warnings, so the new returncode check only fires on genuine failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update callers in CyclomaticComplexityAssessor to pass stderr strings instead of wrapping them in Exception(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass the last line of output as stdout when raising CalledProcessError in radon, lizard, and gocyclo failure paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Enterprise Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a StreamingSubprocess API and safe_subprocess_run_stream, re-exports streaming helpers, tests streaming semantics, and refactors Python/Lizard/Go complexity assessors to stream tool output and parse average complexity from final lines with improved sanitized error reporting. ChangesStreaming subprocess infrastructure and integration
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/agentready/assessors/code_quality.py (1)
658-659:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMissing tools should degrade this assessor to
skipped, not escape as exceptions.These branches re-raise
MissingToolErrorforradon,lizard, andgocyclo. That pushes tool availability out of the assessor boundary instead of returning a skipped finding for this attribute.As per coding guidelines,
src/agentready/assessors/*.py: "Return 'skipped' status for graceful degradation when required tools are missing, never crash."Also applies to: 725-726, 818-822
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/agentready/assessors/code_quality.py` around lines 658 - 659, Replace the except FileNotFoundError handlers that currently raise MissingToolError for radon, lizard, and gocyclo with logic that returns a "skipped" assessment result so the assessor gracefully degrades instead of propagating an exception; specifically, in the except blocks for the radon/lizard/gocyclo tool invocations inside src/agentready/assessors/code_quality.py (the FileNotFoundError handlers shown and the similar handlers around the other occurrences) return a finding/assessment object or status marked skipped (consistent with other assessors' skipped pattern) and include a short message that the external tool is unavailable rather than raising MissingToolError.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/agentready/utils/subprocess_utils.py`:
- Around line 193-200: The normal-success path currently skips stream cleanup
because _finalize() sets self._closed before __exit__()/close() can run
_cleanup(), leaking descriptors; change the lifecycle so cleanup always runs:
either have _finalize() call self.close() (so close() performs _cleanup and
joins the watchdog) instead of setting self._closed directly, or modify close()
to run _cleanup() even if _closed is True (and ensure _closed is set only after
_cleanup()), and confirm the watchdog join happens inside _cleanup()/close();
apply the same change for the other identical block handling lines 259-262.
- Around line 123-188: The streamed stdout path in __next__ currently returns
full lines without enforcing _max_output_size, so add a byte counter for stdout
(e.g., self._stdout_bytes_read) and check length of each read line (preferably
bytes length via encoding or reading as bytes) against _max_output_size before
returning; if returning the line would exceed the cap, set self._stderr_error
(or raise/SubprocessSecurityError) and perform the same cleanup/timed-out
behavior used for stderr overflows, ensuring the check is also applied in the
other streaming loop referenced (the similar read loop around lines 202-213) so
large newline-free output cannot exceed the advertised cap.
---
Outside diff comments:
In `@src/agentready/assessors/code_quality.py`:
- Around line 658-659: Replace the except FileNotFoundError handlers that
currently raise MissingToolError for radon, lizard, and gocyclo with logic that
returns a "skipped" assessment result so the assessor gracefully degrades
instead of propagating an exception; specifically, in the except blocks for the
radon/lizard/gocyclo tool invocations inside
src/agentready/assessors/code_quality.py (the FileNotFoundError handlers shown
and the similar handlers around the other occurrences) return a
finding/assessment object or status marked skipped (consistent with other
assessors' skipped pattern) and include a short message that the external tool
is unavailable rather than raising MissingToolError.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: de8d9a47-e394-4673-a210-69fae340d482
📒 Files selected for processing (4)
src/agentready/assessors/code_quality.pysrc/agentready/utils/__init__.pysrc/agentready/utils/subprocess_utils.pytests/unit/utils/test_subprocess_utils.py
When gocyclo succeeds but output lacks an "Average:" line, the method previously fell off the end and returned None. Add a not_applicable fallback and a catch-all exception handler matching the other assess methods. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On normal stream completion, _finalize() set _closed before __exit__ could run _cleanup(), skipping pipe close and thread joins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Description
Summary
Type of Change
Changes Made
Testing
Test plan
pytest tests/unit/utils/test_subprocess_utils.py — 18 new tests covering streaming basics, timeout, stderr accumulation, size limits, cleanup, early close, idempotent close, and kwarg stripping
pytest tests/unit/test_assessors_code_quality.py — existing cyclomatic complexity tests still pass
pytest --cov=src/agentready — verify coverage for new code
Run agentready assess against a real repo with Python, Go, and multi-language files to validate radon/lizard/gocyclo scoring end-to-end
Unit tests pass (
pytest)Integration tests pass
Manual testing performed
No new warnings or errors
Checklist
Summary by CodeRabbit
Refactor
Tests