sanityops-cli 0.1.3__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.
- sanityops_cli-0.1.3/.github/ISSUE_TEMPLATE/bug_report.yml +99 -0
- sanityops_cli-0.1.3/.github/ISSUE_TEMPLATE/config.yml +8 -0
- sanityops_cli-0.1.3/.github/ISSUE_TEMPLATE/feature_request.yml +45 -0
- sanityops_cli-0.1.3/.github/PULL_REQUEST_TEMPLATE.md +56 -0
- sanityops_cli-0.1.3/.github/dependabot.yml +28 -0
- sanityops_cli-0.1.3/.github/scripts/pr_review_bot.py +168 -0
- sanityops_cli-0.1.3/.github/workflows/ai-review.yml +37 -0
- sanityops_cli-0.1.3/.github/workflows/ci.yml +52 -0
- sanityops_cli-0.1.3/.github/workflows/release.yml +232 -0
- sanityops_cli-0.1.3/.gitignore +73 -0
- sanityops_cli-0.1.3/CHANGELOG.md +98 -0
- sanityops_cli-0.1.3/CODE_OF_CONDUCT.md +90 -0
- sanityops_cli-0.1.3/CONTRIBUTING.md +170 -0
- sanityops_cli-0.1.3/LICENSE +201 -0
- sanityops_cli-0.1.3/NOTICE +5 -0
- sanityops_cli-0.1.3/PKG-INFO +213 -0
- sanityops_cli-0.1.3/README.md +173 -0
- sanityops_cli-0.1.3/SECURITY.md +59 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-13-model-config-comments-in-init-template.md +347 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-14-loading-animations.md +1107 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-14-skills-files-banner-readme.md +354 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-28-copyright-compliance.md +604 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-08-config-command.md +1512 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-20-cross-artifact-rendering.md +829 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-20-help-panel-migration.md +538 -0
- sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-20-repair-feature.md +1442 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-13-model-config-comments-in-init-template-design.md +111 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-14-loading-animations-design.md +303 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-14-skills-files-banner-readme-design.md +134 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-28-copyright-compliance-design.md +117 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-08-config-command-design.md +199 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-20-cross-artifact-rendering-design.md +313 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-20-help-panel-migration-design.md +182 -0
- sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-20-repair-feature-design.md +276 -0
- sanityops_cli-0.1.3/entry.py +20 -0
- sanityops_cli-0.1.3/pyproject.toml +79 -0
- sanityops_cli-0.1.3/release.sh +78 -0
- sanityops_cli-0.1.3/scripts/generate-checksums.ps1 +28 -0
- sanityops_cli-0.1.3/scripts/install.ps1 +97 -0
- sanityops_cli-0.1.3/scripts/install.sh +152 -0
- sanityops_cli-0.1.3/src/sanityops_cli/__init__.py +16 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/__init__.py +19 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/agent.py +230 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/prompts.py +100 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/tools/__init__.py +19 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/tools/store_repairs_tool.py +144 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/agent.py +332 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/hooks/progress_hook.py +152 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/models/finding.py +120 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/prompts.py +316 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/grep_tool.py +667 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/listfiles_tool.py +88 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/readfile_tool.py +804 -0
- sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/storefindings_tool.py +178 -0
- sanityops_cli-0.1.3/src/sanityops_cli/api/__init__.py +16 -0
- sanityops_cli-0.1.3/src/sanityops_cli/api/client.py +403 -0
- sanityops_cli-0.1.3/src/sanityops_cli/commands/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/commands/config.py +312 -0
- sanityops_cli-0.1.3/src/sanityops_cli/commands/init.py +132 -0
- sanityops_cli-0.1.3/src/sanityops_cli/commands/inspect.py +646 -0
- sanityops_cli-0.1.3/src/sanityops_cli/constants/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/constants/config_defaults.py +22 -0
- sanityops_cli-0.1.3/src/sanityops_cli/constants/exit_codes.py +19 -0
- sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/__init__.py +16 -0
- sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/checker.py +100 -0
- sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/llm_config.py +112 -0
- sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/markdown_reporter.py +249 -0
- sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/renderer.py +203 -0
- sanityops_cli-0.1.3/src/sanityops_cli/exceptions/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/exceptions/api_exceptions.py +60 -0
- sanityops_cli-0.1.3/src/sanityops_cli/exceptions/base_exceptions.py +25 -0
- sanityops_cli-0.1.3/src/sanityops_cli/help_panel.py +49 -0
- sanityops_cli-0.1.3/src/sanityops_cli/logging/__init__.py +18 -0
- sanityops_cli-0.1.3/src/sanityops_cli/logging/logger.py +108 -0
- sanityops_cli-0.1.3/src/sanityops_cli/main.py +123 -0
- sanityops_cli-0.1.3/src/sanityops_cli/progress/__init__.py +18 -0
- sanityops_cli-0.1.3/src/sanityops_cli/progress/tracker.py +159 -0
- sanityops_cli-0.1.3/src/sanityops_cli/renderers/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/renderers/command_renderer/inspect_command_renderer.py +87 -0
- sanityops_cli-0.1.3/src/sanityops_cli/templates/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/templates/inspect_config.yaml +55 -0
- sanityops_cli-0.1.3/src/sanityops_cli/utils/__init__.py +14 -0
- sanityops_cli-0.1.3/src/sanityops_cli/utils/artifact_packer.py +407 -0
- sanityops_cli-0.1.3/src/sanityops_cli/utils/config_loader.py +296 -0
- sanityops_cli-0.1.3/src/sanityops_cli/utils/config_resolver.py +358 -0
- sanityops_cli-0.1.3/src/sanityops_cli/utils/validators.py +117 -0
- sanityops_cli-0.1.3/tests/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/commands/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/commands/test_config.py +411 -0
- sanityops_cli-0.1.3/tests/commands/test_init.py +263 -0
- sanityops_cli-0.1.3/tests/commands/test_inspect_repair.py +144 -0
- sanityops_cli-0.1.3/tests/test_help_panel.py +142 -0
- sanityops_cli-0.1.3/tests/unit/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/agents/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/agents/repair_agent/__init__.py +1 -0
- sanityops_cli-0.1.3/tests/unit/agents/repair_agent/test_agent.py +73 -0
- sanityops_cli-0.1.3/tests/unit/agents/repair_agent/test_store_repairs_tool.py +132 -0
- sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/models/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/models/test_finding.py +108 -0
- sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/test_agent.py +106 -0
- sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/test_progress_hook.py +99 -0
- sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/test_prompts.py +85 -0
- sanityops_cli-0.1.3/tests/unit/api/__init__.py +14 -0
- sanityops_cli-0.1.3/tests/unit/api/test_client.py +239 -0
- sanityops_cli-0.1.3/tests/unit/commands/test_inspect.py +231 -0
- sanityops_cli-0.1.3/tests/unit/commands/test_version.py +47 -0
- sanityops_cli-0.1.3/tests/unit/defect_checker/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/defect_checker/test_checker.py +142 -0
- sanityops_cli-0.1.3/tests/unit/defect_checker/test_llm_config.py +225 -0
- sanityops_cli-0.1.3/tests/unit/defect_checker/test_markdown_reporter.py +321 -0
- sanityops_cli-0.1.3/tests/unit/defect_checker/test_renderer.py +310 -0
- sanityops_cli-0.1.3/tests/unit/logging/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/logging/test_logger.py +80 -0
- sanityops_cli-0.1.3/tests/unit/progress/__init__.py +0 -0
- sanityops_cli-0.1.3/tests/unit/progress/test_tracker.py +92 -0
- sanityops_cli-0.1.3/tests/unit/utils/test_artifact_packer.py +174 -0
- sanityops_cli-0.1.3/tests/unit/utils/test_config_loader.py +487 -0
- sanityops_cli-0.1.3/tests/utils/test_config_resolver.py +472 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: Bug Report
|
|
2
|
+
description: Report a bug to help us improve
|
|
3
|
+
labels: ["bug", "needs-triage"]
|
|
4
|
+
assignees: []
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Thanks for taking the time to fill out this bug report! Please fill out the sections below.
|
|
10
|
+
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: description
|
|
13
|
+
attributes:
|
|
14
|
+
label: Description
|
|
15
|
+
description: A clear description of what the bug is
|
|
16
|
+
placeholder: "When I run `sanityops-cli inspect`, I get an error..."
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: steps
|
|
22
|
+
attributes:
|
|
23
|
+
label: Steps to Reproduce
|
|
24
|
+
description: Steps to reproduce the behavior
|
|
25
|
+
placeholder: |
|
|
26
|
+
1. Run `sanityops-cli inspect init`
|
|
27
|
+
2. Edit config file to add artifact paths
|
|
28
|
+
3. Run `sanityops-cli inspect`
|
|
29
|
+
4. See error
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
|
|
33
|
+
- type: textarea
|
|
34
|
+
id: expected
|
|
35
|
+
attributes:
|
|
36
|
+
label: Expected Behavior
|
|
37
|
+
description: What you expected to happen
|
|
38
|
+
placeholder: "The inspection should complete successfully..."
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
|
|
42
|
+
- type: textarea
|
|
43
|
+
id: actual
|
|
44
|
+
attributes:
|
|
45
|
+
label: Actual Behavior
|
|
46
|
+
description: What actually happened
|
|
47
|
+
placeholder: "I get a ValidationError..."
|
|
48
|
+
validations:
|
|
49
|
+
required: true
|
|
50
|
+
|
|
51
|
+
- type: input
|
|
52
|
+
id: version
|
|
53
|
+
attributes:
|
|
54
|
+
label: Version
|
|
55
|
+
description: What version of sanityops-cli are you running?
|
|
56
|
+
placeholder: "0.1.4"
|
|
57
|
+
validations:
|
|
58
|
+
required: true
|
|
59
|
+
|
|
60
|
+
- type: dropdown
|
|
61
|
+
id: os
|
|
62
|
+
attributes:
|
|
63
|
+
label: Operating System
|
|
64
|
+
description: What operating system are you using?
|
|
65
|
+
options:
|
|
66
|
+
- macOS (Intel)
|
|
67
|
+
- macOS (Apple Silicon)
|
|
68
|
+
- Linux
|
|
69
|
+
- Windows
|
|
70
|
+
validations:
|
|
71
|
+
required: true
|
|
72
|
+
|
|
73
|
+
- type: input
|
|
74
|
+
id: python
|
|
75
|
+
attributes:
|
|
76
|
+
label: Python Version
|
|
77
|
+
description: What version of Python are you using?
|
|
78
|
+
placeholder: "3.11.4"
|
|
79
|
+
validations:
|
|
80
|
+
required: true
|
|
81
|
+
|
|
82
|
+
- type: textarea
|
|
83
|
+
id: logs
|
|
84
|
+
attributes:
|
|
85
|
+
label: Relevant Logs
|
|
86
|
+
description: Please paste any relevant error messages or logs (use `--verbose` flag if helpful)
|
|
87
|
+
placeholder: "Paste logs here..."
|
|
88
|
+
render: shell
|
|
89
|
+
validations:
|
|
90
|
+
required: false
|
|
91
|
+
|
|
92
|
+
- type: textarea
|
|
93
|
+
id: additional
|
|
94
|
+
attributes:
|
|
95
|
+
label: Additional Context
|
|
96
|
+
description: Add any other context about the problem here
|
|
97
|
+
placeholder: "This started happening after..."
|
|
98
|
+
validations:
|
|
99
|
+
required: false
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Documentation
|
|
4
|
+
url: https://sanityops.org
|
|
5
|
+
about: Check the documentation for usage guides and API reference
|
|
6
|
+
- name: GitHub Discussions
|
|
7
|
+
url: https://github.com/sanityops-org/sanityops-cli/discussions
|
|
8
|
+
about: Ask questions and discuss ideas with the community
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Feature Request
|
|
2
|
+
description: Suggest an idea for this project
|
|
3
|
+
labels: ["enhancement", "needs-triage"]
|
|
4
|
+
assignees: []
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Thanks for suggesting a feature! Please fill out the sections below.
|
|
10
|
+
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: problem
|
|
13
|
+
attributes:
|
|
14
|
+
label: Problem Description
|
|
15
|
+
description: Is your feature request related to a problem? Please describe it.
|
|
16
|
+
placeholder: "I'm always frustrated when..."
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: solution
|
|
22
|
+
attributes:
|
|
23
|
+
label: Proposed Solution
|
|
24
|
+
description: Describe the solution you'd like to see
|
|
25
|
+
placeholder: "I would like sanityops-cli to..."
|
|
26
|
+
validations:
|
|
27
|
+
required: true
|
|
28
|
+
|
|
29
|
+
- type: textarea
|
|
30
|
+
id: alternatives
|
|
31
|
+
attributes:
|
|
32
|
+
label: Alternatives Considered
|
|
33
|
+
description: Describe any alternative solutions or features you've considered
|
|
34
|
+
placeholder: "I considered using..."
|
|
35
|
+
validations:
|
|
36
|
+
required: false
|
|
37
|
+
|
|
38
|
+
- type: textarea
|
|
39
|
+
id: additional
|
|
40
|
+
attributes:
|
|
41
|
+
label: Additional Context
|
|
42
|
+
description: Add any other context, screenshots, or examples about the feature request here
|
|
43
|
+
placeholder: "This feature would help with..."
|
|
44
|
+
validations:
|
|
45
|
+
required: false
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
<!-- What does this PR change? Provide a clear description of the changes. -->
|
|
4
|
+
|
|
5
|
+
## Type of Change
|
|
6
|
+
|
|
7
|
+
<!-- Mark the relevant option with an 'x' -->
|
|
8
|
+
|
|
9
|
+
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
10
|
+
- [ ] New feature (non-breaking change which adds functionality)
|
|
11
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
12
|
+
- [ ] Documentation update
|
|
13
|
+
- [ ] Refactoring (no functional changes)
|
|
14
|
+
- [ ] Test improvement
|
|
15
|
+
|
|
16
|
+
## Related Issues
|
|
17
|
+
|
|
18
|
+
<!-- Link any related issues here using #issue-number format -->
|
|
19
|
+
|
|
20
|
+
Fixes #
|
|
21
|
+
|
|
22
|
+
## Testing
|
|
23
|
+
|
|
24
|
+
<!-- Describe how you tested this change -->
|
|
25
|
+
|
|
26
|
+
- [ ] Tests added/updated for new functionality
|
|
27
|
+
- [ ] All existing tests pass
|
|
28
|
+
- [ ] Manual testing performed
|
|
29
|
+
|
|
30
|
+
### Test Commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Run tests
|
|
34
|
+
pytest
|
|
35
|
+
|
|
36
|
+
# Run linting
|
|
37
|
+
ruff check .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Checklist
|
|
41
|
+
|
|
42
|
+
<!-- Mark completed items with an 'x' -->
|
|
43
|
+
|
|
44
|
+
- [ ] Code follows the style guidelines (ruff check passes)
|
|
45
|
+
- [ ] Code is self-documented with clear comments where needed
|
|
46
|
+
- [ ] Documentation updated (README, docstrings, etc.) if needed
|
|
47
|
+
- [ ] No new warnings introduced
|
|
48
|
+
- [ ] Changes are backwards compatible (or documented as breaking)
|
|
49
|
+
|
|
50
|
+
## Screenshots (if applicable)
|
|
51
|
+
|
|
52
|
+
<!-- Add screenshots here if the change affects the UI or output -->
|
|
53
|
+
|
|
54
|
+
## Additional Notes
|
|
55
|
+
|
|
56
|
+
<!-- Any other information that reviewers should know -->
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
day: "monday"
|
|
8
|
+
time: "09:00"
|
|
9
|
+
timezone: "Asia/Shanghai"
|
|
10
|
+
open-pull-requests-limit: 5
|
|
11
|
+
commit-message:
|
|
12
|
+
prefix: "chore(deps)"
|
|
13
|
+
labels:
|
|
14
|
+
- "dependencies"
|
|
15
|
+
|
|
16
|
+
- package-ecosystem: "github-actions"
|
|
17
|
+
directory: "/"
|
|
18
|
+
schedule:
|
|
19
|
+
interval: "weekly"
|
|
20
|
+
day: "monday"
|
|
21
|
+
time: "09:00"
|
|
22
|
+
timezone: "Asia/Shanghai"
|
|
23
|
+
open-pull-requests-limit: 5
|
|
24
|
+
commit-message:
|
|
25
|
+
prefix: "chore(deps)"
|
|
26
|
+
labels:
|
|
27
|
+
- "dependencies"
|
|
28
|
+
- "github-actions"
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PR review bot: uses the Anthropic SDK to review a pull request diff
|
|
4
|
+
and post the review as a PR review comment via the GitHub API.
|
|
5
|
+
|
|
6
|
+
Run inside a GitHub Actions workflow. Requires the following env vars:
|
|
7
|
+
ANTHROPIC_API_KEY - API key (stored as a GitHub secret)
|
|
8
|
+
GITHUB_TOKEN - GitHub token to post the review (auto-provided)
|
|
9
|
+
GITHUB_REPOSITORY - e.g. owner/repo (auto-provided)
|
|
10
|
+
GITHUB_EVENT_PATH - path to the pull_request event JSON (auto-provided)
|
|
11
|
+
ANTHROPIC_MODEL - (optional) model id, default claude-sonnet-4-5
|
|
12
|
+
ANTHROPIC_BASE_URL - (optional) custom API endpoint
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.request
|
|
20
|
+
|
|
21
|
+
# Anthropic SDK
|
|
22
|
+
from anthropic import Anthropic
|
|
23
|
+
|
|
24
|
+
DEFAULT_MODEL = "claude-sonnet-4-5"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def read_inputs() -> dict:
|
|
28
|
+
"""Read required inputs from the environment."""
|
|
29
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY", "").strip()
|
|
30
|
+
token = os.environ.get("GITHUB_TOKEN", "").strip()
|
|
31
|
+
repo = os.environ.get("GITHUB_REPOSITORY", "").strip()
|
|
32
|
+
event_path = os.environ.get("GITHUB_EVENT_PATH", "").strip()
|
|
33
|
+
model = os.environ.get("ANTHROPIC_MODEL", DEFAULT_MODEL).strip()
|
|
34
|
+
base_url = os.environ.get("ANTHROPIC_BASE_URL", "").strip() or None
|
|
35
|
+
|
|
36
|
+
if not api_key:
|
|
37
|
+
raise RuntimeError("ANTHROPIC_API_KEY is not set")
|
|
38
|
+
if not token:
|
|
39
|
+
raise RuntimeError("GITHUB_TOKEN is not set")
|
|
40
|
+
if not repo:
|
|
41
|
+
raise RuntimeError("GITHUB_REPOSITORY is not set")
|
|
42
|
+
if not event_path or not os.path.exists(event_path):
|
|
43
|
+
raise RuntimeError(f"GITHUB_EVENT_PATH is not set or missing: {event_path!r}")
|
|
44
|
+
|
|
45
|
+
with open(event_path) as f:
|
|
46
|
+
event = json.load(f)
|
|
47
|
+
|
|
48
|
+
pr_number = (event.get("pull_request") or {}).get("number")
|
|
49
|
+
if not pr_number:
|
|
50
|
+
raise RuntimeError("Event is not a pull_request event with a number")
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"api_key": api_key,
|
|
54
|
+
"token": token,
|
|
55
|
+
"repo": repo,
|
|
56
|
+
"model": model,
|
|
57
|
+
"base_url": base_url,
|
|
58
|
+
"pr_number": pr_number,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def fetch_pr_diff(repo: str, pr_number: int, token: str) -> str:
|
|
63
|
+
"""Fetch the pull request diff via the GitHub API."""
|
|
64
|
+
url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
|
65
|
+
req = urllib.request.Request(
|
|
66
|
+
url,
|
|
67
|
+
headers={
|
|
68
|
+
"Authorization": f"Bearer {token}",
|
|
69
|
+
"Accept": "application/vnd.github.v3.diff",
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
try:
|
|
73
|
+
with urllib.request.urlopen(req) as resp:
|
|
74
|
+
return resp.read().decode("utf-8", errors="replace")
|
|
75
|
+
except urllib.error.HTTPError as e:
|
|
76
|
+
raise RuntimeError(f"Failed to fetch diff: {e.code} {e.read().decode(errors='replace')[:300]}") from e
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run_review(payload: dict, diff: str) -> str:
|
|
80
|
+
"""Call the Anthropic API to review the diff and return the review text."""
|
|
81
|
+
pr_number = payload["pr_number"]
|
|
82
|
+
|
|
83
|
+
# Initialize Anthropic client (SDK handles auth headers correctly)
|
|
84
|
+
client = Anthropic(
|
|
85
|
+
api_key=payload["api_key"],
|
|
86
|
+
base_url=payload.get("base_url"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
system_prompt = (
|
|
90
|
+
"You are a senior code reviewer. Review the provided pull request diff and "
|
|
91
|
+
"report issues. Be specific and technical. For each issue include the "
|
|
92
|
+
"file path and line if identifiable. Categorize findings as Critical, "
|
|
93
|
+
"Important, or Minor. End with an overall assessment: Ready to merge (Yes/No/With fixes). "
|
|
94
|
+
"Keep the review concise and actionable. Write in the same language as the "
|
|
95
|
+
"PR description or codebase conventions."
|
|
96
|
+
)
|
|
97
|
+
user_prompt = (
|
|
98
|
+
f"Here is the diff for PR #{pr_number}:\n\n"
|
|
99
|
+
f"```diff\n{diff}\n```"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
message = client.messages.create(
|
|
103
|
+
model=payload["model"],
|
|
104
|
+
max_tokens=4096,
|
|
105
|
+
system=system_prompt,
|
|
106
|
+
messages=[{"role": "user", "content": user_prompt}],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Extract text from response
|
|
110
|
+
text_parts = []
|
|
111
|
+
for block in message.content:
|
|
112
|
+
if hasattr(block, "text"):
|
|
113
|
+
text_parts.append(block.text)
|
|
114
|
+
text = "\n".join(text_parts).strip()
|
|
115
|
+
if not text:
|
|
116
|
+
raise RuntimeError("Anthropic returned no text content")
|
|
117
|
+
return text
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def post_review(payload: dict, body: str) -> None:
|
|
121
|
+
"""Post the review as a PR review comment via the GitHub API."""
|
|
122
|
+
url = f"https://api.github.com/repos/{payload['repo']}/pulls/{payload['pr_number']}/reviews"
|
|
123
|
+
data = json.dumps({
|
|
124
|
+
"event": "COMMENT",
|
|
125
|
+
"body": f"## 🤖 Claude Code Review\n\n{body}",
|
|
126
|
+
}).encode("utf-8")
|
|
127
|
+
req = urllib.request.Request(
|
|
128
|
+
url,
|
|
129
|
+
data=data,
|
|
130
|
+
headers={
|
|
131
|
+
"Authorization": f"Bearer {payload['token']}",
|
|
132
|
+
"Accept": "application/vnd.github+json",
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
},
|
|
135
|
+
method="POST",
|
|
136
|
+
)
|
|
137
|
+
try:
|
|
138
|
+
with urllib.request.urlopen(req) as resp:
|
|
139
|
+
resp.read()
|
|
140
|
+
except urllib.error.HTTPError as e:
|
|
141
|
+
print(f"Warning: could not post review: {e.code} {e.read().decode(errors='replace')[:200]}", file=sys.stderr)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main() -> int:
|
|
145
|
+
try:
|
|
146
|
+
payload = read_inputs()
|
|
147
|
+
except RuntimeError as e:
|
|
148
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
149
|
+
return 2
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
diff = fetch_pr_diff(payload["repo"], payload["pr_number"], payload["token"])
|
|
153
|
+
print(f"Fetched diff for PR #{payload['pr_number']} ({len(diff)} chars)")
|
|
154
|
+
if not diff.strip():
|
|
155
|
+
print("Diff is empty; nothing to review.")
|
|
156
|
+
return 0
|
|
157
|
+
|
|
158
|
+
review = run_review(payload, diff)
|
|
159
|
+
post_review(payload, review)
|
|
160
|
+
print("Review posted successfully.")
|
|
161
|
+
return 0
|
|
162
|
+
except RuntimeError as e:
|
|
163
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
sys.exit(main())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: AI Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
review:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.11'
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: pip install anthropic
|
|
29
|
+
|
|
30
|
+
- name: Run Claude code review
|
|
31
|
+
env:
|
|
32
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
34
|
+
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-5' }}
|
|
35
|
+
ANTHROPIC_BASE_URL: ${{ vars.ANTHROPIC_BASE_URL }}
|
|
36
|
+
run: |
|
|
37
|
+
python .github/scripts/pr_review_bot.py
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout repository
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.11"
|
|
21
|
+
|
|
22
|
+
- name: Install ruff
|
|
23
|
+
run: pip install "ruff>=0.6.0"
|
|
24
|
+
|
|
25
|
+
- name: Run ruff
|
|
26
|
+
run: ruff check --output-format=github .
|
|
27
|
+
|
|
28
|
+
test:
|
|
29
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
needs: lint
|
|
32
|
+
strategy:
|
|
33
|
+
matrix:
|
|
34
|
+
python-version: ["3.11", "3.12"]
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout repository
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
41
|
+
uses: actions/setup-python@v5
|
|
42
|
+
with:
|
|
43
|
+
python-version: ${{ matrix.python-version }}
|
|
44
|
+
|
|
45
|
+
- name: Install uv
|
|
46
|
+
uses: astral-sh/setup-uv@v5
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: uv sync --all-extras
|
|
50
|
+
|
|
51
|
+
- name: Run tests
|
|
52
|
+
run: uv run pytest -v
|