From db9067a3809114097936f8c9afca8d9cf0ca513e Mon Sep 17 00:00:00 2001 From: rookiestar28 Date: Thu, 4 Jun 2026 14:40:09 +0800 Subject: [PATCH] chore(packaging): document hygiene boundaries --- pyproject.toml | 6 +- .../devtools/debug_s35_import.py | 6 +- .../devtools/verify_s30_doctor.py | 5 +- services/package_hygiene.py | 84 ++++++++++++++++ tests/test_r173_package_hygiene.py | 97 +++++++++++++++++++ 5 files changed, 194 insertions(+), 4 deletions(-) rename debug_s35_import.py => scripts/devtools/debug_s35_import.py (86%) rename verify_s30_doctor.py => scripts/devtools/verify_s30_doctor.py (85%) create mode 100644 services/package_hygiene.py create mode 100644 tests/test_r173_package_hygiene.py diff --git a/pyproject.toml b/pyproject.toml index 0be6527..001e481 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ Icon = "" [tool.ruff] # Ruff replaces flake8, isort, and other linters target-version = "py310" -line-length = 120 +line-length = 88 indent-width = 4 [tool.ruff.lint] @@ -64,6 +64,10 @@ indent-style = "space" skip-magic-trailing-comma = false line-ending = "lf" +[tool.black] +line-length = 88 +target-version = ["py310"] + [tool.mypy] python_version = "3.10" warn_return_any = true diff --git a/debug_s35_import.py b/scripts/devtools/debug_s35_import.py similarity index 86% rename from debug_s35_import.py rename to scripts/devtools/debug_s35_import.py index 09c5304..5581ace 100644 --- a/debug_s35_import.py +++ b/scripts/devtools/debug_s35_import.py @@ -3,11 +3,13 @@ Debug script for S35 Transform Isolation. Verifies that the correct executor (TransformProcessRunner) is allowed/loaded. """ -import os +from pathlib import Path import sys # Ensure project root is in path -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) from services.constrained_transforms import get_transform_executor from services.transform_runner import TransformProcessRunner diff --git a/verify_s30_doctor.py b/scripts/devtools/verify_s30_doctor.py similarity index 85% rename from verify_s30_doctor.py rename to scripts/devtools/verify_s30_doctor.py index 3b8a556..f0444f3 100644 --- a/verify_s30_doctor.py +++ b/scripts/devtools/verify_s30_doctor.py @@ -3,10 +3,13 @@ Verify S30 Security Doctor output. """ import os +from pathlib import Path import sys # Add project root to path -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) from services.security_doctor import run_security_doctor diff --git a/services/package_hygiene.py b/services/package_hygiene.py new file mode 100644 index 0000000..a329cf6 --- /dev/null +++ b/services/package_hygiene.py @@ -0,0 +1,84 @@ +""" +Dependency-light package hygiene contract for repo packaging/tooling ownership. + +Keep this import-safe: tests and future packaging checks should be able to read +the contract without importing ComfyUI, aiohttp, connector adapters, or stateful +runtime modules. +""" + +from __future__ import annotations + +import copy +from typing import Any, Dict + +PACKAGE_HYGIENE_CONTRACT_VERSION = 1 + +_PACKAGE_HYGIENE_CONTRACT: Dict[str, Any] = { + "version": PACKAGE_HYGIENE_CONTRACT_VERSION, + "developer_helpers": [ + { + "id": "s35_transform_isolation_debug", + "path": "scripts/devtools/debug_s35_import.py", + "owner": "devtools", + "root_tracked": False, + "rationale": "developer-only transform isolation probe; not a package entrypoint", + }, + { + "id": "s30_security_doctor_verify", + "path": "scripts/devtools/verify_s30_doctor.py", + "owner": "devtools", + "root_tracked": False, + "rationale": "developer-only Security Doctor probe; not a package entrypoint", + }, + ], + "retained_artifacts": [ + { + "path": "package-lock.json", + "owner": "frontend", + "tracked": True, + "rationale": ( + "retained as the npm ci source of truth for Playwright/Vitest " + "validation and supply-chain lockfile scanning" + ), + }, + { + "path": "pyproject.toml", + "owner": "python_package", + "tracked": True, + "rationale": ( + "retained as package metadata plus formatter, coverage, and " + "quality-gate configuration source of truth" + ), + }, + ], + "cache_ownership": [ + { + "id": "runtime_state_cache", + "owner": "state_dir", + "tracked": False, + "path_contract": "services.state_dir.get_cache_dir()", + "cleanup": "preserve_unless_operator_requests_state_cleanup", + "rationale": "runtime cache belongs under configured OpenClaw state, not package source", + }, + { + "id": "repo_local_tool_cache", + "owner": "validation_tooling", + "tracked": False, + "path_contract": ".tmp/", + "cleanup": "safe_to_delete_when_tools_are_not_running", + "rationale": "pre-commit, Black, Playwright, and test temp caches are generated local artifacts", + }, + { + "id": "frontend_dependencies", + "owner": "npm", + "tracked": False, + "path_contract": "node_modules/", + "cleanup": "recreate_with_npm_ci", + "rationale": "dependency install output is regenerated from package-lock.json", + }, + ], +} + + +def get_package_hygiene_contract() -> Dict[str, Any]: + return copy.deepcopy(_PACKAGE_HYGIENE_CONTRACT) diff --git a/tests/test_r173_package_hygiene.py b/tests/test_r173_package_hygiene.py new file mode 100644 index 0000000..32435be --- /dev/null +++ b/tests/test_r173_package_hygiene.py @@ -0,0 +1,97 @@ +import pathlib +import runpy +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] + + +def _read_simple_toml_value(section_name: str, key_name: str): + current_section = None + for raw_line in (ROOT / "pyproject.toml").read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if line.startswith("[") and line.endswith("]"): + current_section = line.strip("[]") + continue + if current_section == section_name and "=" in line: + key, value = [part.strip() for part in line.split("=", 1)] + if key == key_name: + return int(value.split("#", 1)[0].strip()) + raise AssertionError(f"missing pyproject value: [{section_name}] {key_name}") + + +class R173PackageHygieneTests(unittest.TestCase): + def test_developer_helpers_are_not_tracked_at_repo_root(self): + forbidden_root_helpers = { + "debug_s35_import.py", + "verify_s30_doctor.py", + } + for helper_name in forbidden_root_helpers: + self.assertFalse( + (ROOT / helper_name).exists(), + f"{helper_name} should live under scripts/devtools, not repo root", + ) + + expected_devtools = { + "scripts/devtools/debug_s35_import.py", + "scripts/devtools/verify_s30_doctor.py", + } + for helper_path in expected_devtools: + self.assertTrue( + (ROOT / helper_path).is_file(), + f"missing relocated helper: {helper_path}", + ) + + def test_relocated_developer_helpers_resolve_repo_root(self): + for helper_path in ( + "scripts/devtools/debug_s35_import.py", + "scripts/devtools/verify_s30_doctor.py", + ): + namespace = runpy.run_path(str(ROOT / helper_path)) + self.assertEqual(namespace["ROOT"], ROOT) + + def test_python_formatter_line_lengths_are_aligned(self): + ruff_line_length = _read_simple_toml_value("tool.ruff", "line-length") + black_line_length = _read_simple_toml_value("tool.black", "line-length") + isort_line_length = _read_simple_toml_value("tool.isort", "line_length") + + self.assertEqual(ruff_line_length, black_line_length) + self.assertEqual(black_line_length, isort_line_length) + + def test_package_hygiene_contract_documents_artifact_and_cache_ownership(self): + from services.package_hygiene import get_package_hygiene_contract + + contract = get_package_hygiene_contract() + + retained_artifacts = { + artifact["path"]: artifact for artifact in contract["retained_artifacts"] + } + self.assertIn("package-lock.json", retained_artifacts) + self.assertEqual(retained_artifacts["package-lock.json"]["owner"], "frontend") + self.assertIn("npm ci", retained_artifacts["package-lock.json"]["rationale"]) + + cache_owners = { + cache["id"]: cache for cache in contract["cache_ownership"] + } + self.assertEqual(cache_owners["runtime_state_cache"]["owner"], "state_dir") + self.assertFalse(cache_owners["runtime_state_cache"]["tracked"]) + self.assertEqual( + cache_owners["repo_local_tool_cache"]["cleanup"], + "safe_to_delete_when_tools_are_not_running", + ) + + helper_paths = { + helper["path"] for helper in contract["developer_helpers"] + } + self.assertEqual( + helper_paths, + { + "scripts/devtools/debug_s35_import.py", + "scripts/devtools/verify_s30_doctor.py", + }, + ) + + +if __name__ == "__main__": + unittest.main()