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.
Files changed (120) hide show
  1. sanityops_cli-0.1.3/.github/ISSUE_TEMPLATE/bug_report.yml +99 -0
  2. sanityops_cli-0.1.3/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. sanityops_cli-0.1.3/.github/ISSUE_TEMPLATE/feature_request.yml +45 -0
  4. sanityops_cli-0.1.3/.github/PULL_REQUEST_TEMPLATE.md +56 -0
  5. sanityops_cli-0.1.3/.github/dependabot.yml +28 -0
  6. sanityops_cli-0.1.3/.github/scripts/pr_review_bot.py +168 -0
  7. sanityops_cli-0.1.3/.github/workflows/ai-review.yml +37 -0
  8. sanityops_cli-0.1.3/.github/workflows/ci.yml +52 -0
  9. sanityops_cli-0.1.3/.github/workflows/release.yml +232 -0
  10. sanityops_cli-0.1.3/.gitignore +73 -0
  11. sanityops_cli-0.1.3/CHANGELOG.md +98 -0
  12. sanityops_cli-0.1.3/CODE_OF_CONDUCT.md +90 -0
  13. sanityops_cli-0.1.3/CONTRIBUTING.md +170 -0
  14. sanityops_cli-0.1.3/LICENSE +201 -0
  15. sanityops_cli-0.1.3/NOTICE +5 -0
  16. sanityops_cli-0.1.3/PKG-INFO +213 -0
  17. sanityops_cli-0.1.3/README.md +173 -0
  18. sanityops_cli-0.1.3/SECURITY.md +59 -0
  19. sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-13-model-config-comments-in-init-template.md +347 -0
  20. sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-14-loading-animations.md +1107 -0
  21. sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-14-skills-files-banner-readme.md +354 -0
  22. sanityops_cli-0.1.3/docs/superpowers/plans/2026-08-28-copyright-compliance.md +604 -0
  23. sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-08-config-command.md +1512 -0
  24. sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-20-cross-artifact-rendering.md +829 -0
  25. sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-20-help-panel-migration.md +538 -0
  26. sanityops_cli-0.1.3/docs/superpowers/plans/2026-09-20-repair-feature.md +1442 -0
  27. sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-13-model-config-comments-in-init-template-design.md +111 -0
  28. sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-14-loading-animations-design.md +303 -0
  29. sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-14-skills-files-banner-readme-design.md +134 -0
  30. sanityops_cli-0.1.3/docs/superpowers/specs/2026-08-28-copyright-compliance-design.md +117 -0
  31. sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-08-config-command-design.md +199 -0
  32. sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-20-cross-artifact-rendering-design.md +313 -0
  33. sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-20-help-panel-migration-design.md +182 -0
  34. sanityops_cli-0.1.3/docs/superpowers/specs/2026-09-20-repair-feature-design.md +276 -0
  35. sanityops_cli-0.1.3/entry.py +20 -0
  36. sanityops_cli-0.1.3/pyproject.toml +79 -0
  37. sanityops_cli-0.1.3/release.sh +78 -0
  38. sanityops_cli-0.1.3/scripts/generate-checksums.ps1 +28 -0
  39. sanityops_cli-0.1.3/scripts/install.ps1 +97 -0
  40. sanityops_cli-0.1.3/scripts/install.sh +152 -0
  41. sanityops_cli-0.1.3/src/sanityops_cli/__init__.py +16 -0
  42. sanityops_cli-0.1.3/src/sanityops_cli/agents/__init__.py +14 -0
  43. sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/__init__.py +19 -0
  44. sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/agent.py +230 -0
  45. sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/prompts.py +100 -0
  46. sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/tools/__init__.py +19 -0
  47. sanityops_cli-0.1.3/src/sanityops_cli/agents/repair_agent/tools/store_repairs_tool.py +144 -0
  48. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/agent.py +332 -0
  49. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/hooks/progress_hook.py +152 -0
  50. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/models/finding.py +120 -0
  51. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/prompts.py +316 -0
  52. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/grep_tool.py +667 -0
  53. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/listfiles_tool.py +88 -0
  54. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/readfile_tool.py +804 -0
  55. sanityops_cli-0.1.3/src/sanityops_cli/agents/scanner_agent/tools/storefindings_tool.py +178 -0
  56. sanityops_cli-0.1.3/src/sanityops_cli/api/__init__.py +16 -0
  57. sanityops_cli-0.1.3/src/sanityops_cli/api/client.py +403 -0
  58. sanityops_cli-0.1.3/src/sanityops_cli/commands/__init__.py +14 -0
  59. sanityops_cli-0.1.3/src/sanityops_cli/commands/config.py +312 -0
  60. sanityops_cli-0.1.3/src/sanityops_cli/commands/init.py +132 -0
  61. sanityops_cli-0.1.3/src/sanityops_cli/commands/inspect.py +646 -0
  62. sanityops_cli-0.1.3/src/sanityops_cli/constants/__init__.py +14 -0
  63. sanityops_cli-0.1.3/src/sanityops_cli/constants/config_defaults.py +22 -0
  64. sanityops_cli-0.1.3/src/sanityops_cli/constants/exit_codes.py +19 -0
  65. sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/__init__.py +16 -0
  66. sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/checker.py +100 -0
  67. sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/llm_config.py +112 -0
  68. sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/markdown_reporter.py +249 -0
  69. sanityops_cli-0.1.3/src/sanityops_cli/defect_checker/renderer.py +203 -0
  70. sanityops_cli-0.1.3/src/sanityops_cli/exceptions/__init__.py +14 -0
  71. sanityops_cli-0.1.3/src/sanityops_cli/exceptions/api_exceptions.py +60 -0
  72. sanityops_cli-0.1.3/src/sanityops_cli/exceptions/base_exceptions.py +25 -0
  73. sanityops_cli-0.1.3/src/sanityops_cli/help_panel.py +49 -0
  74. sanityops_cli-0.1.3/src/sanityops_cli/logging/__init__.py +18 -0
  75. sanityops_cli-0.1.3/src/sanityops_cli/logging/logger.py +108 -0
  76. sanityops_cli-0.1.3/src/sanityops_cli/main.py +123 -0
  77. sanityops_cli-0.1.3/src/sanityops_cli/progress/__init__.py +18 -0
  78. sanityops_cli-0.1.3/src/sanityops_cli/progress/tracker.py +159 -0
  79. sanityops_cli-0.1.3/src/sanityops_cli/renderers/__init__.py +14 -0
  80. sanityops_cli-0.1.3/src/sanityops_cli/renderers/command_renderer/inspect_command_renderer.py +87 -0
  81. sanityops_cli-0.1.3/src/sanityops_cli/templates/__init__.py +14 -0
  82. sanityops_cli-0.1.3/src/sanityops_cli/templates/inspect_config.yaml +55 -0
  83. sanityops_cli-0.1.3/src/sanityops_cli/utils/__init__.py +14 -0
  84. sanityops_cli-0.1.3/src/sanityops_cli/utils/artifact_packer.py +407 -0
  85. sanityops_cli-0.1.3/src/sanityops_cli/utils/config_loader.py +296 -0
  86. sanityops_cli-0.1.3/src/sanityops_cli/utils/config_resolver.py +358 -0
  87. sanityops_cli-0.1.3/src/sanityops_cli/utils/validators.py +117 -0
  88. sanityops_cli-0.1.3/tests/__init__.py +0 -0
  89. sanityops_cli-0.1.3/tests/commands/__init__.py +0 -0
  90. sanityops_cli-0.1.3/tests/commands/test_config.py +411 -0
  91. sanityops_cli-0.1.3/tests/commands/test_init.py +263 -0
  92. sanityops_cli-0.1.3/tests/commands/test_inspect_repair.py +144 -0
  93. sanityops_cli-0.1.3/tests/test_help_panel.py +142 -0
  94. sanityops_cli-0.1.3/tests/unit/__init__.py +0 -0
  95. sanityops_cli-0.1.3/tests/unit/agents/__init__.py +0 -0
  96. sanityops_cli-0.1.3/tests/unit/agents/repair_agent/__init__.py +1 -0
  97. sanityops_cli-0.1.3/tests/unit/agents/repair_agent/test_agent.py +73 -0
  98. sanityops_cli-0.1.3/tests/unit/agents/repair_agent/test_store_repairs_tool.py +132 -0
  99. sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/__init__.py +0 -0
  100. sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/models/__init__.py +0 -0
  101. sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/models/test_finding.py +108 -0
  102. sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/test_agent.py +106 -0
  103. sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/test_progress_hook.py +99 -0
  104. sanityops_cli-0.1.3/tests/unit/agents/scanner_agent/test_prompts.py +85 -0
  105. sanityops_cli-0.1.3/tests/unit/api/__init__.py +14 -0
  106. sanityops_cli-0.1.3/tests/unit/api/test_client.py +239 -0
  107. sanityops_cli-0.1.3/tests/unit/commands/test_inspect.py +231 -0
  108. sanityops_cli-0.1.3/tests/unit/commands/test_version.py +47 -0
  109. sanityops_cli-0.1.3/tests/unit/defect_checker/__init__.py +0 -0
  110. sanityops_cli-0.1.3/tests/unit/defect_checker/test_checker.py +142 -0
  111. sanityops_cli-0.1.3/tests/unit/defect_checker/test_llm_config.py +225 -0
  112. sanityops_cli-0.1.3/tests/unit/defect_checker/test_markdown_reporter.py +321 -0
  113. sanityops_cli-0.1.3/tests/unit/defect_checker/test_renderer.py +310 -0
  114. sanityops_cli-0.1.3/tests/unit/logging/__init__.py +0 -0
  115. sanityops_cli-0.1.3/tests/unit/logging/test_logger.py +80 -0
  116. sanityops_cli-0.1.3/tests/unit/progress/__init__.py +0 -0
  117. sanityops_cli-0.1.3/tests/unit/progress/test_tracker.py +92 -0
  118. sanityops_cli-0.1.3/tests/unit/utils/test_artifact_packer.py +174 -0
  119. sanityops_cli-0.1.3/tests/unit/utils/test_config_loader.py +487 -0
  120. 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