Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/iara-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
- name: Run Iara Code Review
uses: ./
with:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🐛 Potential bug: The 'groq_api_key' is being used directly from secrets. Ensure that this key is properly secured and not exposed in the workflow file.

provider: anthropic
anthropic_api_key: ${{ secrets.CLAUDE_API_KEY }}
provider: groq
groq_api_key: ${{ secrets.GROQ_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_repository: ${{ github.repository }}
github_event_path: ${{ github.event_path }}
4 changes: 2 additions & 2 deletions .iara.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
"review_mode": "inline"
},
"model": {
"provider": "anthropic",
"preferred": "claude-sonnet-4-5",
"provider": "groq",
"preferred": "llama-3.3-70b-versatile",
"fallback_enabled": true
},
"language": "en"
Expand Down
27 changes: 16 additions & 11 deletions iara/parsers/inline_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ def parse_inline_review(text: str) -> Dict[str, Any]:
Raises:
ValueError: If JSON is invalid or schema doesn't match
"""
# Strip markdown code blocks if present (e.g., ```json\n{...}\n```)
# Use regex to handle variations: ```json, ```, ````json, etc.
text = text.strip()
# Remove opening fence: ```json or ``` or ````json (optional language identifier)
text = re.sub(r'^```+(?:json)?\s*\n?', '', text, flags=re.IGNORECASE)
# Remove closing fence: ``` or ````
text = re.sub(r'\n?```+\s*$', '', text)
text = text.strip()
# Extract JSON if it's wrapped in markdown blocks ANYWHERE in the text
match = re.search(r'```(?:json)?\s*(.*?)\s*```', text, flags=re.IGNORECASE | re.DOTALL)
if match:
text = match.group(1).strip()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🐛 Potential bug: The parse_inline_review function now coerces invalid severity values to 'other' instead of raising a ValueError. This might lead to unexpected behavior if the severity is not properly validated.

else:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✨ Code style improvement: The regular expression used to extract JSON from markdown blocks could be more efficient. Consider using a more specific pattern to avoid potential false positives.

# Fallback: Extract from the first '{' to the last '}'
start = text.find('{')
end = text.rfind('}')
if start != -1 and end != -1 and end > start:
text = text[start:end+1]

# Try to parse JSON
try:
Expand Down Expand Up @@ -101,11 +103,14 @@ def parse_inline_review(text: str) -> Dict[str, Any]:
)

# Validate severity value (case-insensitive)
# Note: We intentionally coerce invalid severities to "other" instead of raising an exception.
# This prevents open-source LLMs that hallucinate unsupported severities (e.g., "config")
# from crashing the entire inline review parsing process.
if comment["severity"].lower() not in VALID_SEVERITIES:
raise ValueError(
f"Comment [{i}] invalid severity: '{comment['severity']}'. "
f"Must be one of: {', '.join(VALID_SEVERITIES)}"
logger.warning(
f"Comment [{i}] invalid severity: '{comment['severity']}'. Coercing to 'other'."
)
comment["severity"] = "other"

# Validate line number is positive
if comment["line"] <= 0:
Expand Down
31 changes: 26 additions & 5 deletions tests/test_inline_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def test_parse_comment_invalid_line_type(self):
self.assertIn("'line' must be integer", str(cm.exception))

def test_parse_comment_invalid_severity(self):
"""Test that invalid severity value raises ValueError."""
"""Test that invalid severity value is coerced to 'other'."""
json_text = json.dumps({
"summary": "Test",
"comments": [
Expand All @@ -146,10 +146,9 @@ def test_parse_comment_invalid_severity(self):
}
]
})

with self.assertRaises(ValueError) as cm:
parse_inline_review(json_text)
self.assertIn("invalid severity", str(cm.exception))

result = parse_inline_review(json_text)
self.assertEqual(result["comments"][0]["severity"], "other")

def test_parse_comment_negative_line(self):
"""Test that negative line number raises ValueError."""
Expand Down Expand Up @@ -187,6 +186,28 @@ def test_parse_all_severities(self):
severities = [c["severity"] for c in result["comments"]]
self.assertEqual(severities, ["bug", "security", "performance", "style", "other"])

def test_parse_with_hallucinated_text(self):
"""Test parsing JSON embedded in markdown or conversational text."""
raw_output = '''Here is your code review.
```json
{
"summary": "Tested extraction",
"comments": []
}
```
Thanks for using Groq!'''
result = parse_inline_review(raw_output)
self.assertEqual(result["summary"], "Tested extraction")

# Test fallback with curly braces but no markdown wrapper
raw_output_2 = '''Sure, here it is:
{
"summary": "Fallback extraction",
"comments": []
}
Have a nice day!'''
result_2 = parse_inline_review(raw_output_2)
self.assertEqual(result_2["summary"], "Fallback extraction")

if __name__ == '__main__':
unittest.main()
Loading