diff --git a/.cursorrules b/.cursorrules index 70cb37ea..0f62596f 100644 --- a/.cursorrules +++ b/.cursorrules @@ -8,6 +8,7 @@ - Instead of `Optional` use `| None` - Create a `__init__.py` file in each folder - Never pass literals, e.g., `error_msg`, directly to `Exceptions`, but instead assign them to variables and pass them to the exception, e.g., `raise FileNotFoundError(error_msg)` instead of `raise FileNotFoundError(f"Thread {thread_id} not found")` +- Always specify `encoding="utf-8"` for all file read and write operations (e.g., `Path.read_text()`, `Path.write_text()`, `open()`, etc.) to ensure proper handling of Unicode characters across all platforms ## FastAPI - Instead of defining `response_model` within route annotation, use the model as the response type in the function signature @@ -16,6 +17,15 @@ # Testing - Use `pytest-mock` for mocking in tests wherever you need to mock something and pytest-mock can do the job. +# Git +- Never use `git add .` - always explicitly add files that are related to the task using `git add ...` +- Use conventional commits format for commit messages (e.g., `feat:`, `fix:`, `docs:`, `style:`, `refactor:`, `test:`, `chore:`) +- Always run linting/pdm commands before committing to ensure code quality. The following commands must pass: + - `pdm run format` - code formatting with ruff (must pass) + - `pdm run lint` - linting with ruff (must pass, or use `pdm run lint:fix` to auto-fix issues) + - `pdm run typecheck:all` - type checking with mypy (may have pre-existing errors from venv/optional dependencies, but should not introduce new errors) + - Alternatively, use `pdm run qa:fix` which runs `typecheck:all`, `format`, and `lint:fix` + # Documentation ## Docstrings diff --git a/src/askui/chat/api/workflows/service.py b/src/askui/chat/api/workflows/service.py index 29aaa1aa..31e6862d 100644 --- a/src/askui/chat/api/workflows/service.py +++ b/src/askui/chat/api/workflows/service.py @@ -70,7 +70,9 @@ def retrieve( ) -> Workflow: try: workflow_path = self._get_workflow_path(workflow_id) - workflow = Workflow.model_validate_json(workflow_path.read_text()) + workflow = Workflow.model_validate_json( + workflow_path.read_text(encoding="utf-8") + ) # Check workspace access if workspace_id is not None and workflow.workspace_id != workspace_id: diff --git a/src/askui/models/askui/ai_element_utils.py b/src/askui/models/askui/ai_element_utils.py index 48800e4b..25fc96d5 100644 --- a/src/askui/models/askui/ai_element_utils.py +++ b/src/askui/models/askui/ai_element_utils.py @@ -64,7 +64,7 @@ def from_json_file(cls, json_file_path: pathlib.Path) -> "AiElement": image=Image.open(image_path), image_path=image_path, json_path=json_file_path, - metadata=json.loads(json_file_path.read_text()), + metadata=json.loads(json_file_path.read_text(encoding="utf-8")), ) diff --git a/src/askui/reporting.py b/src/askui/reporting.py index ae3cd52b..1116f009 100644 --- a/src/askui/reporting.py +++ b/src/askui/reporting.py @@ -736,7 +736,7 @@ def generate(self) -> None: f"{random.randint(0, 1000):03}.html" ) self.report_dir.mkdir(parents=True, exist_ok=True) - report_path.write_text(html) + report_path.write_text(html, encoding="utf-8") class AllureReporter(Reporter): diff --git a/src/askui/telemetry/anonymous_id.py b/src/askui/telemetry/anonymous_id.py index 2f6c6572..03b92b50 100644 --- a/src/askui/telemetry/anonymous_id.py +++ b/src/askui/telemetry/anonymous_id.py @@ -15,7 +15,7 @@ def _read_anonymous_id_from_file() -> str | None: """Read anonymous ID from file if it exists.""" try: if _ANONYMOUS_ID_FILE_PATH.exists(): - return _ANONYMOUS_ID_FILE_PATH.read_text().strip() + return _ANONYMOUS_ID_FILE_PATH.read_text(encoding="utf-8").strip() except OSError as e: logger.warning("Failed to read anonymous ID from file", extra={"error": str(e)}) return None @@ -25,7 +25,7 @@ def _write_anonymous_id_to_file(anonymous_id: str) -> bool: """Write anonymous ID to file, creating directories if needed.""" try: _ANONYMOUS_ID_FILE_PATH.parent.mkdir(parents=True, exist_ok=True) - _ANONYMOUS_ID_FILE_PATH.write_text(anonymous_id) + _ANONYMOUS_ID_FILE_PATH.write_text(anonymous_id, encoding="utf-8") except OSError as e: logger.warning("Failed to write anonymous ID to file", extra={"error": str(e)}) else: diff --git a/src/askui/tools/askui/askui_controller_settings.py b/src/askui/tools/askui/askui_controller_settings.py index 376b2c60..6ad23769 100644 --- a/src/askui/tools/askui/askui_controller_settings.py +++ b/src/askui/tools/askui/askui_controller_settings.py @@ -164,7 +164,7 @@ def _find_remote_device_controller_by_component_registry_file( return None component_registry = AskUiComponentRegistry.model_validate_json( - self.component_registry_file.read_text() + self.component_registry_file.read_text(encoding="utf-8") ) return ( component_registry.installed_packages.remote_device_controller_uuid.executables.askui_remote_device_controller # noqa: E501 diff --git a/src/askui/tools/testing/execution_service.py b/src/askui/tools/testing/execution_service.py index 22639b2e..7348b1fa 100644 --- a/src/askui/tools/testing/execution_service.py +++ b/src/askui/tools/testing/execution_service.py @@ -59,7 +59,9 @@ def list_(self, query: ExecutionListQuery) -> ListResponse[Execution]: def retrieve(self, execution_id: ExecutionId) -> Execution: try: execution_path = self._get_execution_path(execution_id) - return Execution.model_validate_json(execution_path.read_text()) + return Execution.model_validate_json( + execution_path.read_text(encoding="utf-8") + ) except FileNotFoundError as e: error_msg = f"Execution {execution_id} not found" raise NotFoundError(error_msg) from e diff --git a/src/askui/tools/testing/feature_service.py b/src/askui/tools/testing/feature_service.py index ba9520db..97862170 100644 --- a/src/askui/tools/testing/feature_service.py +++ b/src/askui/tools/testing/feature_service.py @@ -57,7 +57,7 @@ def list_( def retrieve(self, feature_id: FeatureId) -> Feature: try: feature_path = self._get_feature_path(feature_id) - return Feature.model_validate_json(feature_path.read_text()) + return Feature.model_validate_json(feature_path.read_text(encoding="utf-8")) except FileNotFoundError as e: error_msg = f"Feature {feature_id} not found" raise NotFoundError(error_msg) from e diff --git a/src/askui/tools/testing/scenario_service.py b/src/askui/tools/testing/scenario_service.py index e0680683..8c1e0891 100644 --- a/src/askui/tools/testing/scenario_service.py +++ b/src/askui/tools/testing/scenario_service.py @@ -62,7 +62,9 @@ def list_(self, query: ScenarioListQuery) -> ListResponse[Scenario]: def retrieve(self, scenario_id: ScenarioId) -> Scenario: try: scenario_path = self._get_scenario_path(scenario_id) - return Scenario.model_validate_json(scenario_path.read_text()) + return Scenario.model_validate_json( + scenario_path.read_text(encoding="utf-8") + ) except FileNotFoundError as e: error_msg = f"Scenario {scenario_id} not found" raise NotFoundError(error_msg) from e