name: Code Quality permissions: contents: read on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: code-quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Set up Python 3.10 uses: actions/setup-python@v6 with: python-version: '3.10' - name: Cache pip dependencies uses: actions/cache@v5 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-quality-${{ hashFiles('**/requirements-dev.txt') }} restore-keys: | ${{ runner.os }}-pip-quality- - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Format check with Black run: | echo "Checking code formatting with Black..." black --check --diff . echo "✓ Black formatting check passed" - name: Lint with flake8 run: | echo "Linting with flake8..." # Stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # Exit-zero treats all errors as warnings. GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics echo "✓ flake8 linting passed" - name: Type checking with mypy run: | echo "Type checking with mypy..." # Only check our main package, ignore test files for now mypy kikotools/ --ignore-missing-imports --no-strict-optional || true echo "✓ mypy type checking completed" - name: Check import structure run: | python -c " import sys import os sys.path.insert(0, os.getcwd()) # Test that all imports work correctly try: from kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS print('✓ Package-level imports successful') except ImportError as e: print(f'Warning: Package-level imports failed: {e}') # This is expected since we don't have ComfyUI installed # Test individual module imports from kikotools.base import ComfyAssetsBaseNode from kikotools.tools.resolution_calculator import ResolutionCalculatorNode from kikotools.tools.resolution_calculator.logic import extract_dimensions from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode as NodeClass # Test Width Height Selector imports from kikotools.tools.width_height_selector import WidthHeightSelectorNode from kikotools.tools.width_height_selector.logic import get_preset_dimensions from kikotools.tools.width_height_selector.presets import PRESET_OPTIONS, PRESET_METADATA # Test Sampler Combo imports from kikotools.tools.sampler_combo import SamplerComboNode from kikotools.tools.sampler_combo.logic import get_sampler_combo, SAMPLERS, SCHEDULERS # Test Seed History imports from kikotools.tools.seed_history import SeedHistoryNode from kikotools.tools.seed_history.logic import generate_random_seed, validate_seed_value # Test Kiko Save Image imports from kikotools.tools.kiko_save_image import KikoSaveImageNode from kikotools.tools.kiko_save_image.logic import process_image_batch, validate_save_inputs # Test Model Downloader imports from kikotools.tools.model_downloader import ModelDownloaderNode from kikotools.tools.model_downloader.detector import URLDetector, DownloaderType from kikotools.tools.model_downloader.base import BaseDownloader # Test Text Input imports from kikotools.tools.text_input import TextInputNode print('✓ All module imports successful') " - name: Check code style consistency run: | echo "Checking code style consistency..." # Check for consistent naming find kikotools/ -name "*.py" -exec grep -l "class.*Node" {} \; | while read file; do if ! grep -q "ComfyAssetsBaseNode" "$file" && ! grep -q "class ComfyAssetsBaseNode" "$file"; then echo "Checking $file for ComfyUI node inheritance..." fi done # Check for proper docstrings python -c " import ast import os def check_docstrings(filepath): with open(filepath, 'r') as f: tree = ast.parse(f.read()) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.ClassDef)): if not ast.get_docstring(node) and not node.name.startswith('_'): print(f'Warning: {filepath}:{node.lineno} - {node.name} missing docstring') for root, dirs, files in os.walk('kikotools'): for file in files: if file.endswith('.py') and not file.startswith('__'): filepath = os.path.join(root, file) check_docstrings(filepath) print('✓ Docstring check completed') " security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Set up Python 3.10 uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install bandit run: | pip install bandit[toml] - name: Run security scan with bandit run: | echo "Running security scan..." bandit -r kikotools/ -ll || true echo "✓ Security scan completed" - name: Check for hardcoded secrets run: | echo "Checking for potential secrets..." # Check for common secret patterns if grep -r -i "password\|secret\|key\|token" kikotools/ --include="*.py" | grep -v "# " | grep -v "def " | grep -v "class "; then echo "Warning: Potential hardcoded secrets found" else echo "✓ No obvious hardcoded secrets found" fi architecture: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Set up Python 3.10 uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Check architecture compliance run: | python -c " import sys import os sys.path.insert(0, os.getcwd()) print('Checking architecture compliance...') # Test separation of concerns from kikotools.tools.resolution_calculator import logic, node # Logic module should not import node-specific things import inspect logic_source = inspect.getsource(logic) if 'ComfyUI' in logic_source and 'INPUT_TYPES' not in logic_source: print('⚠️ Warning: Logic module contains ComfyUI-specific code') else: print('✓ Logic module properly separated') # Node module should inherit from base from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode from kikotools.base import ComfyAssetsBaseNode if issubclass(ResolutionCalculatorNode, ComfyAssetsBaseNode): print('✓ Node properly inherits from base class') else: print('❌ Node does not inherit from base class') sys.exit(1) # Check that nodes have proper ComfyUI interface required_attrs = ['INPUT_TYPES', 'RETURN_TYPES', 'RETURN_NAMES', 'FUNCTION', 'CATEGORY'] # Test Resolution Calculator Node for attr in required_attrs: if not hasattr(ResolutionCalculatorNode, attr): print(f'❌ ResolutionCalculatorNode missing required attribute: {attr}') sys.exit(1) # Test Width Height Selector Node from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode if issubclass(WidthHeightSelectorNode, ComfyAssetsBaseNode): print('✓ WidthHeightSelectorNode properly inherits from base class') else: print('❌ WidthHeightSelectorNode does not inherit from base class') sys.exit(1) for attr in required_attrs: if not hasattr(WidthHeightSelectorNode, attr): print(f'❌ WidthHeightSelectorNode missing required attribute: {attr}') sys.exit(1) # Test Sampler Combo Node from kikotools.tools.sampler_combo.node import SamplerComboNode if issubclass(SamplerComboNode, ComfyAssetsBaseNode): print('✓ SamplerComboNode properly inherits from base class') else: print('❌ SamplerComboNode does not inherit from base class') sys.exit(1) for attr in required_attrs: if not hasattr(SamplerComboNode, attr): print(f'❌ SamplerComboNode missing required attribute: {attr}') sys.exit(1) # Test Seed History Node from kikotools.tools.seed_history.node import SeedHistoryNode if issubclass(SeedHistoryNode, ComfyAssetsBaseNode): print('✓ SeedHistoryNode properly inherits from base class') else: print('❌ SeedHistoryNode does not inherit from base class') sys.exit(1) for attr in required_attrs: if not hasattr(SeedHistoryNode, attr): print(f'❌ SeedHistoryNode missing required attribute: {attr}') sys.exit(1) # Test Kiko Save Image Node from kikotools.tools.kiko_save_image.node import KikoSaveImageNode if issubclass(KikoSaveImageNode, ComfyAssetsBaseNode): print('✓ KikoSaveImageNode properly inherits from base class') else: print('❌ KikoSaveImageNode does not inherit from base class') sys.exit(1) # KikoSaveImage is an output node, so it doesn't have RETURN_TYPES/RETURN_NAMES save_required_attrs = ['INPUT_TYPES', 'FUNCTION', 'CATEGORY'] for attr in save_required_attrs: if not hasattr(KikoSaveImageNode, attr): print(f'❌ KikoSaveImageNode missing required attribute: {attr}') sys.exit(1) # Check that it's properly marked as an output node if not hasattr(KikoSaveImageNode, 'OUTPUT_NODE') or not KikoSaveImageNode.OUTPUT_NODE: print('❌ KikoSaveImageNode missing OUTPUT_NODE = True') sys.exit(1) # Test Model Downloader Node from kikotools.tools.model_downloader.node import ModelDownloaderNode if issubclass(ModelDownloaderNode, ComfyAssetsBaseNode): print('✓ ModelDownloaderNode properly inherits from base class') else: print('❌ ModelDownloaderNode does not inherit from base class') sys.exit(1) # ModelDownloader is an output node, so it doesn't have RETURN_TYPES/RETURN_NAMES download_required_attrs = ['INPUT_TYPES', 'FUNCTION', 'CATEGORY'] for attr in download_required_attrs: if not hasattr(ModelDownloaderNode, attr): print(f'❌ ModelDownloaderNode missing required attribute: {attr}') sys.exit(1) # Check that it's properly marked as an output node if not hasattr(ModelDownloaderNode, 'OUTPUT_NODE') or not ModelDownloaderNode.OUTPUT_NODE: print('❌ ModelDownloaderNode missing OUTPUT_NODE = True') sys.exit(1) # Test Text Input Node from kikotools.tools.text_input.node import TextInputNode if issubclass(TextInputNode, ComfyAssetsBaseNode): print('✓ TextInputNode properly inherits from base class') else: print('❌ TextInputNode does not inherit from base class') sys.exit(1) for attr in required_attrs: if not hasattr(TextInputNode, attr): print(f'❌ TextInputNode missing required attribute: {attr}') sys.exit(1) print('✓ All architecture checks passed for all tools') " - name: Check test coverage expectations run: | python -c " import os # Count test files vs implementation files test_files = 0 impl_files = 0 for root, dirs, files in os.walk('tests'): test_files += len([f for f in files if f.startswith('test_') and f.endswith('.py')]) for root, dirs, files in os.walk('kikotools'): impl_files += len([f for f in files if f.endswith('.py') and not f.startswith('__')]) print(f'Implementation files: {impl_files}') print(f'Test files: {test_files}') if test_files >= impl_files * 0.5: # At least 50% test coverage by file count print('✓ Adequate test file coverage') else: print('⚠️ Warning: Low test file coverage') "