jleechanorg-pr-automation 0.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of jleechanorg-pr-automation might be problematic. Click here for more details.

@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shared utilities for automation system
4
+
5
+ Consolidates common patterns:
6
+ - JSON file operations with thread safety
7
+ - Logging configuration setup
8
+ - Environment variable handling
9
+ - Email configuration management
10
+ """
11
+
12
+ import fcntl
13
+ import json
14
+ import logging
15
+ import os
16
+ import tempfile
17
+ import threading
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+ from typing import Dict, Any, Optional
21
+
22
+
23
+ class SafeJSONManager:
24
+ """Thread-safe and cross-process safe JSON file operations with file locking"""
25
+
26
+ def __init__(self):
27
+ self._locks = {}
28
+ self._locks_lock = threading.Lock()
29
+
30
+ def _get_lock(self, file_path: str) -> threading.RLock:
31
+ """Get or create a lock for a specific file"""
32
+ # Normalize path to prevent duplicate locks for same file
33
+ norm_path = os.path.abspath(file_path)
34
+ with self._locks_lock:
35
+ if norm_path not in self._locks:
36
+ self._locks[norm_path] = threading.RLock()
37
+ return self._locks[norm_path]
38
+
39
+ def _get_file_lock_path(self, file_path: str) -> str:
40
+ """Get file lock path for cross-process safety"""
41
+ return f"{file_path}.lock"
42
+
43
+ def read_json(self, file_path: str, default: Any = None) -> Any:
44
+ """Cross-process safe JSON file reading with default fallback"""
45
+ lock = self._get_lock(file_path)
46
+ with lock:
47
+ try:
48
+ if os.path.exists(file_path):
49
+ with open(file_path, 'r') as f:
50
+ # Add file lock for cross-process safety
51
+ fcntl.flock(f.fileno(), fcntl.LOCK_SH)
52
+ try:
53
+ return json.load(f)
54
+ finally:
55
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
56
+ else:
57
+ return default if default is not None else {}
58
+ except (json.JSONDecodeError, IOError) as e:
59
+ logging.warning(f"Failed to read JSON from {file_path}: {e}")
60
+ return default if default is not None else {}
61
+
62
+ def write_json(self, file_path: str, data: Any) -> bool:
63
+ """Cross-process safe JSON file writing with atomic operations"""
64
+ lock = self._get_lock(file_path)
65
+ with lock:
66
+ try:
67
+ # Ensure directory exists (guard against empty dirname for root files)
68
+ directory = os.path.dirname(file_path)
69
+ if directory:
70
+ os.makedirs(directory, exist_ok=True)
71
+
72
+ # Create temp file in same directory for atomic replace
73
+ dir_path = directory or "."
74
+ fd, temp_path = tempfile.mkstemp(
75
+ dir=dir_path,
76
+ prefix=os.path.basename(file_path),
77
+ suffix=".tmp"
78
+ )
79
+
80
+ try:
81
+ with os.fdopen(fd, 'w') as f:
82
+ # Add exclusive file lock for cross-process safety
83
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
84
+ try:
85
+ json.dump(data, f, indent=2, default=str)
86
+ f.flush()
87
+ os.fsync(f.fileno())
88
+ finally:
89
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
90
+
91
+ # Atomic replace
92
+ os.replace(temp_path, file_path)
93
+ return True
94
+ finally:
95
+ # Clean up temp file if replace failed
96
+ if os.path.exists(temp_path):
97
+ try:
98
+ os.remove(temp_path)
99
+ except OSError:
100
+ pass
101
+
102
+ except (IOError, OSError) as e:
103
+ logging.error(f"Failed to write JSON to {file_path}: {e}")
104
+ return False
105
+
106
+ def update_json(self, file_path: str, update_func, lock_timeout: int = 10) -> bool:
107
+ """Cross-process safe JSON file update with callback function"""
108
+ lock = self._get_lock(file_path)
109
+ with lock:
110
+ try:
111
+ data = self.read_json(file_path, {})
112
+ updated_data = update_func(data)
113
+ return self.write_json(file_path, updated_data)
114
+ except Exception as e:
115
+ logging.error(f"Failed to update JSON {file_path}: {e}")
116
+ return False
117
+
118
+
119
+ # Global instance for shared use
120
+ json_manager = SafeJSONManager()
121
+
122
+
123
+ def setup_logging(name: str, level: int = logging.INFO,
124
+ log_file: Optional[str] = None) -> logging.Logger:
125
+ """Standardized logging setup for automation components"""
126
+ logger = logging.getLogger(name)
127
+
128
+ # Avoid duplicate handlers
129
+ if logger.handlers:
130
+ return logger
131
+
132
+ logger.setLevel(level)
133
+
134
+ # Create formatter
135
+ formatter = logging.Formatter(
136
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
137
+ )
138
+
139
+ # Console handler
140
+ console_handler = logging.StreamHandler()
141
+ console_handler.setFormatter(formatter)
142
+ logger.addHandler(console_handler)
143
+
144
+ # File handler if specified
145
+ if log_file:
146
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
147
+ file_handler = logging.FileHandler(log_file)
148
+ file_handler.setFormatter(formatter)
149
+ logger.addHandler(file_handler)
150
+
151
+ return logger
152
+
153
+
154
+ def get_env_config(prefix: str = "AUTOMATION_") -> Dict[str, str]:
155
+ """Get all environment variables with specified prefix"""
156
+ config = {}
157
+ for key, value in os.environ.items():
158
+ if key.startswith(prefix):
159
+ # Remove prefix and convert to lowercase
160
+ config_key = key[len(prefix):].lower()
161
+ config[config_key] = value
162
+ return config
163
+
164
+
165
+ def get_email_config() -> Dict[str, str]:
166
+ """Get email configuration from environment variables"""
167
+ email_config = {
168
+ 'smtp_server': os.getenv('SMTP_SERVER', 'localhost'),
169
+ 'smtp_port': int(os.getenv('SMTP_PORT', '587')),
170
+ 'email_user': os.getenv('EMAIL_USER', ''),
171
+ 'email_pass': os.getenv('EMAIL_PASS', ''),
172
+ 'email_to': os.getenv('EMAIL_TO', ''),
173
+ 'email_from': os.getenv('EMAIL_FROM', os.getenv('EMAIL_USER', ''))
174
+ }
175
+ return email_config
176
+
177
+
178
+ def validate_email_config(config: Dict[str, str]) -> bool:
179
+ """Validate that required email configuration is present"""
180
+ required_fields = ['smtp_server', 'email_user', 'email_pass', 'email_to']
181
+ return all(config.get(field) for field in required_fields)
182
+
183
+
184
+ def get_automation_limits() -> Dict[str, int]:
185
+ """Get automation safety limits from environment or defaults"""
186
+ return {
187
+ 'pr_limit': int(os.getenv('AUTOMATION_PR_LIMIT', '5')),
188
+ 'global_limit': int(os.getenv('AUTOMATION_GLOBAL_LIMIT', '50')),
189
+ 'approval_hours': int(os.getenv('AUTOMATION_APPROVAL_HOURS', '24')),
190
+ 'subprocess_timeout': int(os.getenv('AUTOMATION_SUBPROCESS_TIMEOUT', '300'))
191
+ }
192
+
193
+
194
+ def ensure_directory(file_path: str) -> None:
195
+ """Ensure directory exists for given file path"""
196
+ directory = os.path.dirname(file_path)
197
+ if directory:
198
+ os.makedirs(directory, exist_ok=True)
199
+
200
+
201
+ def get_project_root() -> str:
202
+ """Get project root directory"""
203
+ current_dir = Path(__file__).resolve()
204
+ # Look for CLAUDE.md to identify project root
205
+ for parent in current_dir.parents:
206
+ if (parent / "CLAUDE.md").exists():
207
+ return str(parent)
208
+ # Fallback to parent of automation directory
209
+ return str(current_dir.parent.parent)
210
+
211
+
212
+ def format_timestamp(dt: datetime = None) -> str:
213
+ """Format datetime as ISO string"""
214
+ if dt is None:
215
+ dt = datetime.now()
216
+ return dt.isoformat()
217
+
218
+
219
+ def parse_timestamp(timestamp_str: str) -> datetime:
220
+ """Parse ISO timestamp string to datetime"""
221
+ return datetime.fromisoformat(timestamp_str)
222
+
223
+
224
+ def get_test_email_config() -> Dict[str, str]:
225
+ """Get standardized test email configuration"""
226
+ return {
227
+ 'SMTP_SERVER': 'smtp.example.com',
228
+ 'SMTP_PORT': '587',
229
+ 'EMAIL_USER': 'test@example.com',
230
+ 'EMAIL_PASS': 'testpass',
231
+ 'EMAIL_TO': 'admin@example.com'
232
+ }
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: jleechanorg-pr-automation
3
+ Version: 0.1.0
4
+ Summary: GitHub PR automation system with safety limits and actionable counting
5
+ Author-email: jleechan <jlee@jleechan.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/jleechanorg/worldarchitect.ai
8
+ Project-URL: Repository, https://github.com/jleechanorg/worldarchitect.ai
9
+ Project-URL: Issues, https://github.com/jleechanorg/worldarchitect.ai/issues
10
+ Keywords: github,automation,pr,pull-request,monitoring
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Software Development :: Version Control :: Git
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: requests>=2.25.0
24
+ Provides-Extra: email
25
+ Requires-Dist: keyring>=23.0.0; extra == "email"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
29
+ Requires-Dist: black>=22.0.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
31
+
32
+ # jleechanorg-pr-automation
33
+
34
+ A comprehensive GitHub PR automation system with safety limits, actionable counting, and intelligent filtering.
35
+
36
+ ## Features
37
+
38
+ - **Actionable PR Counting**: Only processes PRs that need attention, excluding already-processed ones
39
+ - **Safety Limits**: Built-in rate limiting and attempt tracking to prevent automation abuse
40
+ - **Cross-Process Safety**: Thread-safe operations with file-based persistence
41
+ - **Email Notifications**: Optional SMTP integration for automation alerts
42
+ - **Commit-Based Tracking**: Avoids duplicate processing using commit SHAs
43
+ - **Comprehensive Testing**: 200+ test cases with matrix-driven coverage
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install jleechanorg-pr-automation
49
+ ```
50
+
51
+ ### Optional Dependencies
52
+
53
+ For email notifications:
54
+ ```bash
55
+ pip install jleechanorg-pr-automation[email]
56
+ ```
57
+
58
+ For development:
59
+ ```bash
60
+ pip install jleechanorg-pr-automation[dev]
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ### Basic PR Monitoring
66
+
67
+ ```python
68
+ from jleechanorg_pr_automation import JleechanorgPRMonitor
69
+
70
+ # Initialize monitor
71
+ monitor = JleechanorgPRMonitor()
72
+
73
+ # Process up to 20 actionable PRs
74
+ result = monitor.run_monitoring_cycle_with_actionable_count(target_actionable_count=20)
75
+
76
+ print(f"Processed {result['actionable_processed']} actionable PRs")
77
+ print(f"Skipped {result['skipped_count']} non-actionable PRs")
78
+ ```
79
+
80
+ ### Safety Management
81
+
82
+ ```python
83
+ from jleechanorg_pr_automation import AutomationSafetyManager
84
+
85
+ # Initialize safety manager with data directory
86
+ safety = AutomationSafetyManager(data_dir="/tmp/automation_safety")
87
+
88
+ # Limits are configured via automation_safety_config.json inside the data directory
89
+ # or the AUTOMATION_PR_LIMIT / AUTOMATION_GLOBAL_LIMIT environment variables.
90
+
91
+ # Check if PR can be processed
92
+ if safety.can_process_pr(pr_number=123, repo="my-repo"):
93
+ # Process PR...
94
+ safety.record_pr_attempt(pr_number=123, result="success", repo="my-repo")
95
+ ```
96
+
97
+ ## Configuration
98
+
99
+ ### Environment Variables
100
+
101
+ - `GITHUB_TOKEN`: GitHub personal access token (required)
102
+ - `PR_AUTOMATION_WORKSPACE`: Custom workspace directory (optional)
103
+ - `AUTOMATION_PR_LIMIT`: Maximum attempts per PR (default: 5)
104
+ - `AUTOMATION_GLOBAL_LIMIT`: Maximum global automation runs (default: 50)
105
+ - `AUTOMATION_APPROVAL_HOURS`: Hours before approval expires (default: 24)
106
+
107
+ ### Email Configuration (Optional)
108
+
109
+ - `SMTP_SERVER`: SMTP server hostname
110
+ - `SMTP_PORT`: SMTP server port (default: 587)
111
+ - `EMAIL_USER`: SMTP username
112
+ - `EMAIL_PASS`: SMTP password
113
+ - `EMAIL_TO`: Notification recipient
114
+ - `EMAIL_FROM`: Sender address (defaults to EMAIL_USER)
115
+
116
+ ## Command Line Interface
117
+
118
+ ### PR Monitor
119
+
120
+ ```bash
121
+ # Monitor all repositories
122
+ jleechanorg-pr-monitor
123
+
124
+ # Process specific repository
125
+ jleechanorg-pr-monitor --single-repo worldarchitect.ai
126
+
127
+ # Process specific PR
128
+ jleechanorg-pr-monitor --target-pr 123 --target-repo jleechanorg/worldarchitect.ai
129
+
130
+ # Dry run (discovery only)
131
+ jleechanorg-pr-monitor --dry-run
132
+ ```
133
+
134
+ ### Safety Manager CLI
135
+
136
+ ```bash
137
+ # Check current status
138
+ automation-safety-cli status
139
+
140
+ # Clear all safety data
141
+ automation-safety-cli clear
142
+
143
+ # Check specific PR
144
+ automation-safety-cli check-pr 123 --repo my-repo
145
+ ```
146
+
147
+ ## Architecture
148
+
149
+ ### Actionable PR Logic
150
+
151
+ The system implements intelligent PR filtering:
152
+
153
+ 1. **State Check**: Only processes open PRs
154
+ 2. **Commit Tracking**: Skips PRs already processed with current commit SHA
155
+ 3. **Safety Limits**: Respects per-PR and global automation limits
156
+ 4. **Ordering**: Processes most recently updated PRs first
157
+
158
+ ### Safety Features
159
+
160
+ - **Dual Limiting**: Per-PR consecutive failure limits + global run limits
161
+ - **Cross-Process Safety**: File-based locking for concurrent automation instances
162
+ - **Attempt Tracking**: Full history of success/failure with timestamps
163
+ - **Graceful Degradation**: Continues processing other PRs if one fails
164
+
165
+ ### Testing
166
+
167
+ The library includes comprehensive test coverage:
168
+
169
+ - **Matrix Testing**: All PR state combinations (Open/Closed × New/Old Commits × Processed/Fresh)
170
+ - **Actionable Counting**: Batch processing with skip exclusion
171
+ - **Safety Limits**: Concurrent access and edge cases
172
+ - **Integration Tests**: Real GitHub API interactions (optional)
173
+
174
+ ## Development
175
+
176
+ ```bash
177
+ # Clone repository
178
+ git clone https://github.com/jleechanorg/worldarchitect.ai.git
179
+ cd worldarchitect.ai/automation
180
+
181
+ # Install in development mode
182
+ pip install -e .[dev]
183
+
184
+ # Run tests
185
+ pytest
186
+
187
+ # Run tests with coverage
188
+ pytest --cov=jleechanorg_pr_automation
189
+
190
+ # Format code
191
+ black .
192
+ ruff check .
193
+ ```
194
+
195
+ ## Contributing
196
+
197
+ 1. Fork the repository
198
+ 2. Create a feature branch
199
+ 3. Add tests for new functionality
200
+ 4. Ensure all tests pass
201
+ 5. Submit a pull request
202
+
203
+ ## License
204
+
205
+ MIT License - see LICENSE file for details.
206
+
207
+ ## Changelog
208
+
209
+ ### 0.1.0 (2025-09-28)
210
+
211
+ - Initial release
212
+ - Actionable PR counting system
213
+ - Safety management with dual limits
214
+ - Cross-process file-based persistence
215
+ - Email notification support
216
+ - Comprehensive test suite (200+ tests)
217
+ - CLI interfaces for monitoring and safety management
@@ -0,0 +1,23 @@
1
+ jleechanorg_pr_automation/__init__.py,sha256=qYUTmbETCH775SM_6ln8jX05WzAGPErD-JN7OTIzXrY,852
2
+ jleechanorg_pr_automation/automation_safety_manager.py,sha256=La3GStljdkxsSJBklWVj8CKAx6ndCsyI_aS6tSBZGJ8,27531
3
+ jleechanorg_pr_automation/automation_safety_wrapper.py,sha256=Oa88wGH7XKfOywVnDMxwL0EP_v5YHvkKaSHxTvyiR48,4059
4
+ jleechanorg_pr_automation/automation_utils.py,sha256=MInKTLuQSPwIXga8em70JtgGED3KU_OsV3cvX-4tN-Y,11608
5
+ jleechanorg_pr_automation/check_codex_comment.py,sha256=ccs4XjPLPnsuMiSS_pXo_u-EzNldR34PqyWTlu0U0H0,2221
6
+ jleechanorg_pr_automation/codex_branch_updater.py,sha256=490tjJDOCm58LoRS3F7vslOINVBU4F2C__2dWYRKcFs,9135
7
+ jleechanorg_pr_automation/codex_config.py,sha256=JOrAzVwdDac9gHdLFSYDco0fJ13pdOQT22kAiMYRHhg,2101
8
+ jleechanorg_pr_automation/jleechanorg_pr_monitor.py,sha256=6WDl0zwbJJKrRrHLZQ87J-680FC2DbLCZJ4Q2Cn62dU,48700
9
+ jleechanorg_pr_automation/utils.py,sha256=nhPyR7_wm2Bh-D2aE6ueqWVWLelzzXz_BCwkdC920ig,8168
10
+ jleechanorg_pr_automation/tests/conftest.py,sha256=GVE-RLmzVM9yBNVgfZOHu39onmZPMPjN_vrIsmcWlvU,388
11
+ jleechanorg_pr_automation/tests/test_actionable_counting_matrix.py,sha256=rQxFH6ontV3GpZigFRiHCXRkX0mEnespJv73L1DfPw0,12174
12
+ jleechanorg_pr_automation/tests/test_automation_over_running_reproduction.py,sha256=X1sIKr-ZpAp-lks0wVGnNDM4Yas1uCrgSokW6rYGm38,5759
13
+ jleechanorg_pr_automation/tests/test_automation_safety_limits.py,sha256=xFQIxLsXlP0VLnZRueFEUYOFhX1d9fxXXJf_1EzBY2Q,13340
14
+ jleechanorg_pr_automation/tests/test_automation_safety_manager_comprehensive.py,sha256=CLyWuhl3TQrMKDoLxUikpuMtSotm6FZZZXT_WPBCI_w,22740
15
+ jleechanorg_pr_automation/tests/test_codex_actor_matching.py,sha256=rY6AGM1DatKrYwGIryM36zYLBBK--ydOwJwkH2T7uRk,5252
16
+ jleechanorg_pr_automation/tests/test_graphql_error_handling.py,sha256=Fv0BWLSF-eQ4n5gHwf_JSc42Y0RizrjPPlCB4THLzaI,6401
17
+ jleechanorg_pr_automation/tests/test_pr_filtering_matrix.py,sha256=IBRn9APpNBSpedsvmKVb26ZRrHHj0EzE4F5AvCH9mP8,21415
18
+ jleechanorg_pr_automation/tests/test_pr_targeting.py,sha256=YXyWUGQj2LzUDyZ1NU57SoRXtNcUCuRNMtgCcqrcoGU,3798
19
+ jleechanorg_pr_automation-0.1.0.dist-info/METADATA,sha256=IUHkLXylUcpFalTqBYu82Rk35s1XmHYTSB-0_lHWUOQ,6333
20
+ jleechanorg_pr_automation-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ jleechanorg_pr_automation-0.1.0.dist-info/entry_points.txt,sha256=QD8UUHJ4H09_beMvHzZ5SOy5cRbthvD11mXLzaWjwrg,178
22
+ jleechanorg_pr_automation-0.1.0.dist-info/top_level.txt,sha256=1DJKrq0Be2B5_NL5jTICV1rvnqaMXFmyJpuOTUatcig,26
23
+ jleechanorg_pr_automation-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ automation-safety-cli = jleechanorg_pr_automation.automation_safety_manager:main
3
+ jleechanorg-pr-monitor = jleechanorg_pr_automation.jleechanorg_pr_monitor:main
@@ -0,0 +1 @@
1
+ jleechanorg_pr_automation