cicaddy-github 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/Dockerfile +1 -1
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/PKG-INFO +1 -1
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/pyproject.toml +1 -1
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/agents.py +48 -1
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tasks/pr_review.yml +3 -0
- cicaddy_github-0.3.0/tests/unit/test_agents.py +113 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.claude/skills/cicaddy-action/SKILL.md +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.github/dependabot.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.github/workflows/changelog.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.github/workflows/ci.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.github/workflows/pr-review.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.github/workflows/release.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.gitignore +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/.pre-commit-config.yaml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/CLAUDE.md +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/CONTRIBUTING.md +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/LICENSE +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/README.md +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/action.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/entrypoint.sh +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/__init__.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/config/__init__.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/config/settings.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/__init__.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/analyzer.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/detector.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/tools.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/plugin.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/security/__init__.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/security/leak_detector.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/validation.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tasks/changelog_report.yml +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/templates/report_template.html +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/__init__.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/conftest.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/__init__.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/test_analyzer.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/test_detector.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/test_leak_detector.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/test_plugin.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/test_settings.py +0 -0
- {cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/tests/unit/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cicaddy-github
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: GitHub Actions plugin for cicaddy AI agent framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/redhat-community-ai-tools/cicaddy-action
|
|
6
6
|
Project-URL: Repository, https://github.com/redhat-community-ai-tools/cicaddy-action.git
|
{cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/agents.py
RENAMED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""GitHub AI Agents for PR review and task execution."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import re
|
|
5
|
+
import textwrap
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
from cicaddy.agent.base import BaseAIAgent
|
|
@@ -16,6 +18,50 @@ logger = get_logger(__name__)
|
|
|
16
18
|
|
|
17
19
|
BOT_COMMENT_MARKER_PR_REVIEW = "<!-- cicaddy-action:pr-review -->"
|
|
18
20
|
|
|
21
|
+
# Pattern matches fenced code blocks (possibly indented by list nesting).
|
|
22
|
+
_FENCED_CODE_BLOCK = re.compile(
|
|
23
|
+
r"^([ \t]*(?:`{3,}|~{3,})[^\n]*)\n(.*?)\n([ \t]*(?:`{3,}|~{3,}))[ \t]*$",
|
|
24
|
+
re.MULTILINE | re.DOTALL,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def dedent_code_blocks(text: str) -> str:
|
|
29
|
+
"""Remove common leading whitespace from fenced code block content.
|
|
30
|
+
|
|
31
|
+
AI models often indent code block content to match surrounding list
|
|
32
|
+
indentation. GitHub renders that whitespace literally, so we strip
|
|
33
|
+
the common prefix using ``textwrap.dedent`` while preserving relative
|
|
34
|
+
indentation within the block.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def _dedent(match: re.Match) -> str:
|
|
38
|
+
opener = match.group(1).lstrip()
|
|
39
|
+
content = match.group(2)
|
|
40
|
+
closer = match.group(3).lstrip()
|
|
41
|
+
return f"{opener}\n{textwrap.dedent(content)}\n{closer}"
|
|
42
|
+
|
|
43
|
+
return _FENCED_CODE_BLOCK.sub(_dedent, text)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Matches output wrapped in a single ```markdown ... ``` fence.
|
|
47
|
+
_MARKDOWN_WRAPPER = re.compile(
|
|
48
|
+
r"^\s*```(?:markdown|md)\s*\n(.*?)\n\s*```\s*$",
|
|
49
|
+
re.DOTALL | re.IGNORECASE,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def strip_markdown_wrapper(text: str) -> str:
|
|
54
|
+
"""Strip a wrapping ```markdown fence from the entire AI output.
|
|
55
|
+
|
|
56
|
+
Some models interpret ``output_format: markdown`` as "wrap the response
|
|
57
|
+
in a markdown code fence", which causes GitHub to render the comment as
|
|
58
|
+
a literal code block instead of formatted markdown.
|
|
59
|
+
"""
|
|
60
|
+
m = _MARKDOWN_WRAPPER.match(text.strip())
|
|
61
|
+
if m:
|
|
62
|
+
return m.group(1)
|
|
63
|
+
return text
|
|
64
|
+
|
|
19
65
|
|
|
20
66
|
class GitHubTaskAgent(BaseAIAgent):
|
|
21
67
|
"""AI Agent for scheduled tasks and changelog generation."""
|
|
@@ -254,7 +300,8 @@ Please provide your comprehensive analysis in markdown format.
|
|
|
254
300
|
comment = f"{BOT_COMMENT_MARKER_PR_REVIEW}\n"
|
|
255
301
|
|
|
256
302
|
if "ai_analysis" in analysis_result:
|
|
257
|
-
|
|
303
|
+
cleaned = strip_markdown_wrapper(analysis_result["ai_analysis"])
|
|
304
|
+
comment += dedent_code_blocks(cleaned) + "\n"
|
|
258
305
|
|
|
259
306
|
comment += (
|
|
260
307
|
"\n<!-- cicaddy-footer -->\n---\n"
|
|
@@ -69,6 +69,9 @@ constraints:
|
|
|
69
69
|
- Assess test coverage implications
|
|
70
70
|
- When reviewing code that uses external libraries, use Context7 tools to look up
|
|
71
71
|
current API documentation and best practices
|
|
72
|
+
- All fenced code blocks (```) must start at column 0 with no leading whitespace,
|
|
73
|
+
even when inside list items. Content inside code blocks must not have extra
|
|
74
|
+
indentation from list nesting
|
|
72
75
|
|
|
73
76
|
reasoning: chain_of_thought
|
|
74
77
|
output_format: markdown
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Tests for dedent_code_blocks and strip_markdown_wrapper in agents module."""
|
|
2
|
+
|
|
3
|
+
from cicaddy_github.github_integration.agents import dedent_code_blocks, strip_markdown_wrapper
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDedentCodeBlocks:
|
|
7
|
+
"""Test code block dedenting for AI-generated markdown."""
|
|
8
|
+
|
|
9
|
+
def test_dedents_indented_code_block(self):
|
|
10
|
+
"""Code block content indented by list nesting is dedented."""
|
|
11
|
+
text = (
|
|
12
|
+
"* **Example:**\n"
|
|
13
|
+
" ```diff\n"
|
|
14
|
+
" --- a/file.txt\n"
|
|
15
|
+
" +++ b/file.txt\n"
|
|
16
|
+
" @@ -1,3 +1,4 @@\n"
|
|
17
|
+
" ```"
|
|
18
|
+
)
|
|
19
|
+
result = dedent_code_blocks(text)
|
|
20
|
+
assert "```diff\n--- a/file.txt\n+++ b/file.txt" in result
|
|
21
|
+
|
|
22
|
+
def test_preserves_relative_indentation(self):
|
|
23
|
+
"""Relative indentation within the code block is preserved."""
|
|
24
|
+
text = " ```python\n def foo():\n return 42\n ```"
|
|
25
|
+
result = dedent_code_blocks(text)
|
|
26
|
+
assert "```python\ndef foo():\n return 42\n```" in result
|
|
27
|
+
|
|
28
|
+
def test_no_change_for_unindented_blocks(self):
|
|
29
|
+
"""Already flush code blocks are unchanged."""
|
|
30
|
+
text = "```python\ndef foo():\n return 42\n```"
|
|
31
|
+
result = dedent_code_blocks(text)
|
|
32
|
+
assert result == text
|
|
33
|
+
|
|
34
|
+
def test_multiple_code_blocks(self):
|
|
35
|
+
"""Multiple code blocks in the same text are all dedented."""
|
|
36
|
+
text = (
|
|
37
|
+
"Item 1:\n"
|
|
38
|
+
" ```python\n"
|
|
39
|
+
" print('a')\n"
|
|
40
|
+
" ```\n"
|
|
41
|
+
"\n"
|
|
42
|
+
"Item 2:\n"
|
|
43
|
+
" ```bash\n"
|
|
44
|
+
" echo hello\n"
|
|
45
|
+
" ```"
|
|
46
|
+
)
|
|
47
|
+
result = dedent_code_blocks(text)
|
|
48
|
+
assert "```python\nprint('a')\n```" in result
|
|
49
|
+
assert "```bash\necho hello\n```" in result
|
|
50
|
+
|
|
51
|
+
def test_tilde_delimiters(self):
|
|
52
|
+
"""Tilde-fenced code blocks are also dedented."""
|
|
53
|
+
text = " ~~~python\n print('hello')\n ~~~"
|
|
54
|
+
result = dedent_code_blocks(text)
|
|
55
|
+
assert "~~~python\nprint('hello')\n~~~" in result
|
|
56
|
+
|
|
57
|
+
def test_trailing_whitespace_on_closer(self):
|
|
58
|
+
"""Trailing whitespace after closing fence is tolerated."""
|
|
59
|
+
text = " ```python\n x = 1\n ``` "
|
|
60
|
+
result = dedent_code_blocks(text)
|
|
61
|
+
assert "```python\nx = 1\n```" in result
|
|
62
|
+
|
|
63
|
+
def test_text_without_code_blocks(self):
|
|
64
|
+
"""Plain text without code blocks is unchanged."""
|
|
65
|
+
text = "This is plain markdown with **bold** and *italic*."
|
|
66
|
+
assert dedent_code_blocks(text) == text
|
|
67
|
+
|
|
68
|
+
def test_mixed_content_preserves_surrounding_text(self):
|
|
69
|
+
"""List structure around code blocks is preserved."""
|
|
70
|
+
text = "* Item one\n ```diff\n +added line\n ```\n* Item two"
|
|
71
|
+
result = dedent_code_blocks(text)
|
|
72
|
+
assert result.startswith("* Item one\n")
|
|
73
|
+
assert result.endswith("* Item two")
|
|
74
|
+
assert "+added line" in result
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestStripMarkdownWrapper:
|
|
78
|
+
"""Test stripping wrapping ```markdown fences from AI output."""
|
|
79
|
+
|
|
80
|
+
def test_strips_markdown_wrapper(self):
|
|
81
|
+
"""Output wrapped in ```markdown is unwrapped."""
|
|
82
|
+
text = "```markdown\n### Summary\nSome analysis.\n```"
|
|
83
|
+
result = strip_markdown_wrapper(text)
|
|
84
|
+
assert result == "### Summary\nSome analysis."
|
|
85
|
+
|
|
86
|
+
def test_strips_md_wrapper(self):
|
|
87
|
+
"""Output wrapped in ```md is also unwrapped."""
|
|
88
|
+
text = "```md\n## Title\nContent.\n```"
|
|
89
|
+
result = strip_markdown_wrapper(text)
|
|
90
|
+
assert result == "## Title\nContent."
|
|
91
|
+
|
|
92
|
+
def test_strips_case_insensitive(self):
|
|
93
|
+
"""Output wrapped in ```Markdown or ```MD is also unwrapped."""
|
|
94
|
+
for tag in ("Markdown", "MARKDOWN", "MD", "Md"):
|
|
95
|
+
text = f"```{tag}\nContent here.\n```"
|
|
96
|
+
result = strip_markdown_wrapper(text)
|
|
97
|
+
assert result == "Content here.", f"Failed for tag: {tag}"
|
|
98
|
+
|
|
99
|
+
def test_no_change_without_wrapper(self):
|
|
100
|
+
"""Plain markdown without wrapper is unchanged."""
|
|
101
|
+
text = "### Summary\nSome analysis."
|
|
102
|
+
assert strip_markdown_wrapper(text) == text
|
|
103
|
+
|
|
104
|
+
def test_preserves_internal_code_blocks(self):
|
|
105
|
+
"""Code blocks inside the markdown wrapper are preserved."""
|
|
106
|
+
text = "```markdown\n### Example\n```python\nprint('hi')\n```\n```"
|
|
107
|
+
result = strip_markdown_wrapper(text)
|
|
108
|
+
assert "```python\nprint('hi')\n```" in result
|
|
109
|
+
|
|
110
|
+
def test_no_change_for_non_markdown_fence(self):
|
|
111
|
+
"""A ```python wrapper is NOT stripped."""
|
|
112
|
+
text = "```python\nprint('hi')\n```"
|
|
113
|
+
assert strip_markdown_wrapper(text) == text
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/__init__.py
RENAMED
|
File without changes
|
{cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/analyzer.py
RENAMED
|
File without changes
|
{cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/detector.py
RENAMED
|
File without changes
|
{cicaddy_github-0.1.0 → cicaddy_github-0.3.0}/src/cicaddy_github/github_integration/tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|