Skip to content

Commit 88ce693

Browse files
committed
Enforce CLAUDE.md: restrict executor, pin deps, drop dead code
- Remove Python builtins injection into executor event_dict so only AC_-prefixed allowlist is callable via JSON/socket payloads. - Pin core and platform dependencies in pyproject.toml and align PySide6 between pyproject, requirements, and dev_requirements. - Make create_project_dir's lock module-level and cover all writes. - Replace get_dir_files_as_list's mutable getcwd() default with None sentinel evaluated per call. - Remove unused imports and deduplicate __all__.
1 parent 8968c9b commit 88ce693

14 files changed

Lines changed: 172 additions & 44 deletions

File tree

CLAUDE.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# CLAUDE.md — AutoControl
2+
3+
## Project Overview
4+
5+
AutoControl (`je_auto_control`) is a cross-platform Python GUI automation framework supporting Windows (Win32 API), macOS (pyobjc/Quartz), and Linux (X11). It provides mouse/keyboard control, image recognition, screen capture, action scripting, and report generation through a unified API.
6+
7+
- **Package name**: `je_auto_control`
8+
- **Python**: >= 3.10
9+
- **License**: MIT
10+
- **Author**: JE-Chen
11+
12+
## Architecture & Design Patterns
13+
14+
### Strategy Pattern — Platform Abstraction
15+
16+
`wrapper/platform_wrapper.py` auto-detects the OS and loads the correct backend. All wrapper modules (`auto_control_mouse.py`, `auto_control_keyboard.py`, etc.) delegate to the platform-specific implementation. New platform support is added by implementing the backend interface — no wrapper changes needed.
17+
18+
### Facade Pattern — Unified API Surface
19+
20+
`je_auto_control/__init__.py` re-exports all public functions from wrapper and utility modules, providing a single entry point. Users import only `je_auto_control` and access all features.
21+
22+
### Command Pattern — JSON Action Executor
23+
24+
`utils/executor/action_executor.py` maps string command names (e.g., `AC_click_mouse`) to callable functions. JSON action files define sequences of commands with parameters, enabling recording, serialization, and replay of automation flows.
25+
26+
### Observer Pattern — Callback Executor
27+
28+
`utils/callback/callback_function_executor.py` allows registering callback functions that fire after automation actions complete, supporting event-driven chaining.
29+
30+
### Template Method — Report Generation
31+
32+
`utils/generate_report/` provides HTML, JSON, and XML report generators sharing a common structure: collect test records, format output, write file. Each format implements its own rendering.
33+
34+
## Directory Structure
35+
36+
```
37+
je_auto_control/
38+
├── wrapper/ # Platform-agnostic API (Strategy consumers)
39+
├── windows/ # Win32 backend (ctypes)
40+
├── osx/ # macOS backend (pyobjc/Quartz)
41+
├── linux_with_x11/ # Linux X11 backend (python-Xlib)
42+
├── gui/ # PySide6 GUI application
43+
└── utils/
44+
├── executor/ # JSON action executor (Command pattern)
45+
├── callback/ # Callback executor (Observer pattern)
46+
├── cv2_utils/ # OpenCV: screenshot, template matching, video
47+
├── socket_server/ # TCP server for remote automation
48+
├── shell_process/ # Shell command manager
49+
├── generate_report/ # HTML/JSON/XML report generators
50+
├── test_record/ # Test action recording
51+
├── json/ # JSON action file I/O
52+
├── project/ # Project scaffolding
53+
├── package_manager/ # Dynamic package loading
54+
├── logging/ # Logging
55+
└── exception/ # Custom exceptions
56+
```
57+
58+
## Development Commands
59+
60+
```bash
61+
# Install dependencies
62+
pip install -r dev_requirements.txt
63+
64+
# Install with GUI support
65+
pip install -e .[gui]
66+
67+
# Run unit tests
68+
python -m pytest test/unit_test/
69+
70+
# Run integration tests
71+
python -m pytest test/integrated_test/
72+
73+
# Build package
74+
python -m build
75+
```
76+
77+
## Coding Standards
78+
79+
### Security First
80+
81+
- **Input validation**: Validate all external inputs (user input, file content, network data, JSON action commands) at system boundaries. Sanitize file paths to prevent path traversal. Never trust data from TCP socket clients without validation.
82+
- **Injection prevention**: When executing shell commands (`shell_process`), never construct command strings from unsanitized input. Use parameterized approaches or allowlists.
83+
- **Deserialization safety**: JSON action files and socket server payloads must be validated against expected schemas before execution. Reject unknown command names.
84+
- **No secrets in code**: Never commit credentials, API keys, tokens, or `.env` files. Keep secrets out of logs and reports.
85+
- **Principle of least privilege**: Socket server should bind to localhost by default. Document security implications of exposing to network.
86+
- **Dependency awareness**: Pin dependency versions. Review transitive dependencies for known vulnerabilities.
87+
88+
### Performance Best Practices
89+
90+
- **Lazy imports**: Platform-specific backends are loaded only for the current OS — do not import all backends unconditionally.
91+
- **Avoid redundant screenshots**: Image recognition operations should reuse screen captures when performing multiple searches on the same frame.
92+
- **Buffer management**: Screen recording and video capture must properly release resources (file handles, codec buffers) in `finally` blocks or context managers.
93+
- **Thread safety**: Socket server and recording threads must use proper synchronization. Avoid shared mutable state without locks.
94+
- **Minimize allocations in hot paths**: Mouse/keyboard event dispatch should avoid unnecessary object creation per event.
95+
96+
### Software Engineering Principles
97+
98+
- **SOLID**: Each module has a single responsibility. Platform backends are open for extension (new OS) without modifying wrappers. Depend on abstractions (wrapper API), not concrete implementations (Win32/X11/Quartz).
99+
- **DRY**: Common logic belongs in `wrapper/` or `utils/`, not duplicated across platform backends.
100+
- **YAGNI**: Do not add speculative features. Implement what is needed now.
101+
- **Fail fast**: Raise clear, specific exceptions (`AutoControlMouseException`, `AutoControlKeyboardException`, etc.) at the point of failure. Do not silently swallow errors.
102+
- **Immutable data where possible**: Action lists and configuration should be treated as read-only once loaded.
103+
104+
### Code Style
105+
106+
- Follow PEP 8.
107+
- Use type hints for all public function signatures.
108+
- Keep functions focused and short — one function, one task.
109+
- Prefer composition over inheritance for extending functionality.
110+
- Remove dead code immediately — no commented-out blocks, no unused imports, no unreachable branches.
111+
112+
## Commit Conventions
113+
114+
- Write concise commit messages focused on **why**, not what.
115+
- **Do not mention any AI tools, assistants, or models in commit messages** — no "Co-Authored-By" AI attributions, no references to AI-generated code.
116+
- Use imperative mood: "Add feature", "Fix bug", "Remove unused code".
117+
- Examples:
118+
- `Add image threshold parameter validation`
119+
- `Fix mouse scroll direction on macOS`
120+
- `Remove deprecated screen capture fallback`
121+
122+
## Testing
123+
124+
- **Unit tests**: `test/unit_test/` — test individual functions in isolation.
125+
- **Integration tests**: `test/integrated_test/` — test cross-module workflows.
126+
- **Manual tests**: `test/manual_test/` — require human verification (GUI, visual).
127+
- **GUI tests**: `test/gui_test/` — PySide6 interface tests.
128+
- All tests must pass before merging. Ensure cross-platform compatibility.
129+
130+
## Key Conventions
131+
132+
- All public API functions are exported from `je_auto_control/__init__.py` and listed in `__all__`.
133+
- JSON action command names use `AC_` prefix (e.g., `AC_click_mouse`).
134+
- Platform backends follow naming: `{platform}_{function}.py` (e.g., `win32_ctype_mouse_control.py`).
135+
- Virtual key mappings are in `core/utils/*_vk.py` per platform.

dev_requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ build
44
twine
55
sphinx
66
sphinx-rtd-theme
7-
Pyside6==6.10.1
8-
qt-material
9-
mss
7+
PySide6==6.11.0
8+
qt-material==2.17
9+
mss==10.1.0

je_auto_control/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
from je_auto_control.wrapper.auto_control_mouse import send_mouse_event_to_window
100100
from je_auto_control.wrapper.auto_control_mouse import set_mouse_position
101101
from je_auto_control.wrapper.auto_control_mouse import special_mouse_keys_table
102-
# test_record
102+
# record
103103
from je_auto_control.wrapper.auto_control_record import record
104104
from je_auto_control.wrapper.auto_control_record import stop_record
105105
# import screen
@@ -120,7 +120,7 @@
120120
"AutoControlScreenException", "ImageNotFoundException", "AutoControlJsonActionException",
121121
"AutoControlRecordException", "AutoControlActionNullException", "AutoControlActionException", "record",
122122
"stop_record", "read_action_json", "write_action_json", "execute_action", "execute_files", "executor",
123-
"add_command_to_executor", "test_record_instance", "screenshot", "pil_screenshot",
123+
"add_command_to_executor", "test_record_instance", "pil_screenshot",
124124
"generate_html", "generate_html_report", "generate_json", "generate_json_report", "generate_xml",
125125
"generate_xml_report", "get_dir_files_as_list", "create_project_dir", "start_autocontrol_socket_server",
126126
"callback_executor", "package_manager", "ShellManager", "default_shell_manager",

je_auto_control/gui/main_widget.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import json
2-
import sys
3-
from threading import Thread
42

53
from PySide6.QtCore import QTimer, Signal, QObject
64
from PySide6.QtGui import QIntValidator, QDoubleValidator, QKeyEvent, Qt
75
from PySide6.QtWidgets import (
86
QWidget, QLineEdit, QComboBox, QPushButton, QVBoxLayout, QLabel,
97
QGridLayout, QHBoxLayout, QRadioButton, QButtonGroup, QMessageBox,
10-
QTabWidget, QTextEdit, QFileDialog, QCheckBox, QGroupBox, QSplitter,
11-
QListWidget
8+
QTabWidget, QTextEdit, QFileDialog, QCheckBox, QGroupBox
129
)
1310

1411
from je_auto_control.gui.language_wrapper.multi_language_wrapper import language_wrapper
1512
from je_auto_control.wrapper.auto_control_keyboard import (
16-
type_keyboard, press_keyboard_key, release_keyboard_key, hotkey, write, check_key_is_press
13+
type_keyboard, hotkey, write
1714
)
1815
from je_auto_control.wrapper.auto_control_mouse import (
19-
click_mouse, get_mouse_position, set_mouse_position, press_mouse, release_mouse, mouse_scroll
16+
click_mouse, get_mouse_position, mouse_scroll
2017
)
2118
from je_auto_control.wrapper.auto_control_screen import screen_size, screenshot, get_pixel
2219
from je_auto_control.wrapper.auto_control_image import locate_all_image, locate_image_center, locate_and_click

je_auto_control/gui/main_window.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import sys
22

33
from PySide6.QtWidgets import QMainWindow, QApplication, QComboBox, QLabel, QHBoxLayout, QWidget
4-
from PySide6.QtGui import QAction
54
from qt_material import QtStyleTools
65

76
from je_auto_control.gui.language_wrapper.multi_language_wrapper import language_wrapper

je_auto_control/osx/screen/osx_screen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
import ctypes
3-
from ctypes import c_void_p, c_double, c_uint32
3+
from ctypes import c_void_p, c_uint32
44
from typing import Tuple
55

66
from je_auto_control.utils.exception.exception_tags import osx_import_error_message

je_auto_control/utils/executor/action_executor.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import builtins
21
import types
3-
from inspect import getmembers, isbuiltin
42
from typing import Any, Dict, List, Union
53

64
from je_auto_control.utils.exception.exception_tags import (
@@ -107,10 +105,6 @@ def __init__(self):
107105
"AC_execute_process": start_exe,
108106
}
109107

110-
# 加入所有 Python 內建函式 Add all Python builtins
111-
for function in getmembers(builtins, isbuiltin):
112-
self.event_dict[str(function[0])] = function[1]
113-
114108
def _execute_event(self, action: list) -> Any:
115109
"""
116110
執行單一事件

je_auto_control/utils/file_process/get_dir_file_list.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
from os import getcwd, walk
22
from os.path import abspath, join
3-
from typing import List
3+
from typing import List, Optional
44

55

66
def get_dir_files_as_list(
7-
dir_path: str = getcwd(),
7+
dir_path: Optional[str] = None,
88
default_search_file_extension: str = ".json"
99
) -> List[str]:
1010
"""
1111
Get all files in a directory that end with a specific extension.
1212
遍歷指定目錄,取得所有符合副檔名的檔案清單
1313
14-
:param dir_path: Directory path to search 要搜尋的目錄路徑
14+
:param dir_path: Directory path to search 要搜尋的目錄路徑 (預設為呼叫時的當前工作目錄)
1515
:param default_search_file_extension: File extension to filter 要搜尋的副檔名 (預設 ".json")
1616
:return: List of absolute file paths 符合條件的檔案絕對路徑清單
1717
"""
18+
if dir_path is None:
19+
dir_path = getcwd()
1820
extension = default_search_file_extension.lower()
1921
return [
2022
abspath(join(root, file))

je_auto_control/utils/project/create_project_structure.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
template_keyword_1, template_keyword_2, bad_template_1
1212
)
1313

14+
_project_lock = Lock()
15+
1416

1517
def create_dir(dir_name: str) -> None:
1618
"""
@@ -47,17 +49,16 @@ def create_template(parent_name: str, project_path: str = None) -> None:
4749

4850
keyword_dir_path = Path(project_path) / parent_name / "keyword"
4951
executor_dir_path = Path(project_path) / parent_name / "executor"
50-
lock = Lock()
5152

52-
# 建立 keyword JSON 檔案 Create keyword JSON files
53-
if keyword_dir_path.exists() and keyword_dir_path.is_dir():
54-
write_action_json(str(keyword_dir_path / "keyword1.json"), template_keyword_1)
55-
write_action_json(str(keyword_dir_path / "keyword2.json"), template_keyword_2)
56-
write_action_json(str(keyword_dir_path / "bad_keyword_1.json"), bad_template_1)
53+
with _project_lock:
54+
# 建立 keyword JSON 檔案 Create keyword JSON files
55+
if keyword_dir_path.exists() and keyword_dir_path.is_dir():
56+
write_action_json(str(keyword_dir_path / "keyword1.json"), template_keyword_1)
57+
write_action_json(str(keyword_dir_path / "keyword2.json"), template_keyword_2)
58+
write_action_json(str(keyword_dir_path / "bad_keyword_1.json"), bad_template_1)
5759

58-
# 建立 executor Python 檔案 Create executor Python files
59-
if executor_dir_path.exists() and executor_dir_path.is_dir():
60-
with lock:
60+
# 建立 executor Python 檔案 Create executor Python files
61+
if executor_dir_path.exists() and executor_dir_path.is_dir():
6162
_write_file(
6263
executor_dir_path / "executor_one_file.py",
6364
executor_template_1.replace("{temp}", str(keyword_dir_path / "keyword1.json"))

je_auto_control/utils/xml/change_xml_structure/change_xml_structure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections import defaultdict
22
from xml.etree import ElementTree
3-
from typing import Union, Dict, Any
3+
from typing import Dict, Any
44

55

66
def elements_tree_to_dict(elements_tree: ElementTree.Element) -> Dict[str, Any]:

0 commit comments

Comments
 (0)