release: v0.2.0 repository hygiene, CI, and docs sync

Stop tracking personal workflow artifacts at repo root, add CI and MIT
license, align README and agent skills with artifacts/ defaults, and
enable Ruff in dev/CI so releases are verifiable without local-only runs.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
windyboy
2026-06-01 15:32:03 +08:00
co-authored by Claude Sonnet 4.5 Cursor
parent e70bd35498
commit 6f0df5a774
101 changed files with 1451 additions and 653105 deletions
+4 -4
View File
@@ -3,11 +3,11 @@
Tests series completeness analysis, duplicate detection, and quality comparison.
"""
import pytest
from pathlib import Path
from datetime import datetime, timezone
from vlm.models import SeriesIdentity, SeasonCompleteness, MovieIdentity, VideoFile, DuplicateGroup
from vlm.analysis import analyze_series_completeness, detect_duplicates, compare_quality
from pathlib import Path
from vlm.analysis import analyze_series_completeness, compare_quality, detect_duplicates
from vlm.models import MovieIdentity, SeriesIdentity, VideoFile
class TestSeriesCompletenessAnalysis:
+11 -10
View File
@@ -4,18 +4,19 @@ Tests universal correctness properties using Hypothesis with minimum 100 iterati
Each test validates a specific property from the design document.
"""
import pytest
from pathlib import Path
from datetime import datetime, timezone
from hypothesis import given, strategies as st, settings
from vlm.models import (
SeriesIdentity, SeasonCompleteness, MovieIdentity, VideoFile, DuplicateGroup
)
from vlm.analysis import analyze_series_completeness, detect_duplicates, compare_quality
from vlm.reports import (
generate_completeness_report, generate_duplicate_report, generate_summary_report
)
from pathlib import Path
from hypothesis import given, settings
from hypothesis import strategies as st
from vlm.analysis import analyze_series_completeness, detect_duplicates
from vlm.models import DuplicateGroup, MovieIdentity, SeasonCompleteness, SeriesIdentity, VideoFile
from vlm.reports import (
generate_completeness_report,
generate_duplicate_report,
generate_summary_report,
)
# Custom strategies for generating test data
-1
View File
@@ -1,7 +1,6 @@
"""Tests for the parse CLI command."""
import json
from pathlib import Path
from click.testing import CliRunner
+1 -4
View File
@@ -3,13 +3,10 @@
This module tests the CLI interface for quarantine operations.
"""
import json
from pathlib import Path
from click.testing import CliRunner
import pytest
from click.testing import CliRunner
from vlm.cli import main
from vlm.config import Config
@pytest.fixture
+2 -3
View File
@@ -4,10 +4,9 @@ Tests the report commands to ensure they properly wire to the Report Generator.
"""
import json
import csv
from pathlib import Path
from datetime import datetime, timezone
from click.testing import CliRunner
from vlm.cli import main
+1 -1
View File
@@ -1,8 +1,8 @@
"""Tests for CLI rollback command."""
import json
from pathlib import Path
from datetime import datetime, timezone
from pathlib import Path
import pytest
from click.testing import CliRunner
-1
View File
@@ -1,6 +1,5 @@
"""Tests for CLI scan command options."""
from pathlib import Path
from unittest.mock import patch
from click.testing import CliRunner
+1 -3
View File
@@ -1,13 +1,11 @@
"""Tests for CLI state commands."""
import json
import tempfile
from pathlib import Path
import pytest
from click.testing import CliRunner
from vlm.cli import main, default_config_path
from vlm.cli import default_config_path, main
@pytest.fixture
+6 -4
View File
@@ -1,9 +1,11 @@
"""Unit tests for Configuration Manager."""
from pathlib import Path
import pytest
import yaml
from pathlib import Path
from vlm.config import Config, load_config, create_default_config, validate_config
from vlm.config import Config, create_default_config, load_config, validate_config
class TestConfig:
@@ -311,8 +313,8 @@ class TestCreateDefaultConfig:
"""Test that create_default_config creates parent directories."""
config_file = tmp_path / "subdir" / "config.yaml"
config = create_default_config(config_file)
create_default_config(config_file)
assert config_file.exists()
assert config_file.parent.exists()
+1 -4
View File
@@ -1,12 +1,9 @@
"""Integration tests for duplicate resolution with embedded video metadata."""
import json
from pathlib import Path
import pytest
from vlm.io import load_identities_json, identities_to_analysis_input
from vlm.analysis import detect_duplicates
from vlm.io import identities_to_analysis_input, load_identities_json
class TestDuplicateQualityWithMetadata:
+3 -2
View File
@@ -1,10 +1,11 @@
"""Unit tests for duplicate resolution strategies."""
import pytest
from pathlib import Path
from vlm.models import MovieIdentity
import pytest
from vlm.duplicate_resolve import DuplicateResolutionError, choose_keep_index
from vlm.models import MovieIdentity
def _mi(title: str = "Test", year: int | None = 2020) -> MovieIdentity:
+1 -2
View File
@@ -5,7 +5,6 @@ Tests execution mode handling, dry-run simulation, and actual file operations.
import logging
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4
import pytest
@@ -573,7 +572,7 @@ class TestRollbackLogSaving:
data = json.load(f)
# Verify timestamps are in ISO format
from datetime import datetime, timezone
from datetime import datetime
executed_at = datetime.fromisoformat(data["executed_at"])
assert executed_at is not None
+1 -1
View File
@@ -16,7 +16,7 @@ from vlm.io import (
save_execution_plan,
save_identities_json,
)
from vlm.models import ExecutionPlan, FileOperation, VideoFile
from vlm.models import ExecutionPlan, FileOperation
class TestVideoFileFromRecord:
+3 -4
View File
@@ -1,17 +1,16 @@
"""Unit tests for logging configuration."""
import logging
import tempfile
from pathlib import Path
import pytest
from vlm.logging_config import (
setup_logging,
get_logger,
log_operation,
MAX_LOG_SIZE,
default_log_dir,
get_logger,
log_operation,
setup_logging,
)
+5 -6
View File
@@ -1,10 +1,13 @@
"""Tests for the identity parser module."""
import pytest
from hypothesis import assume, given, settings
from hypothesis import strategies as st
from vlm.parser import (
group_episodes,
normalize_title,
parse_movie,
parse_series,
normalize_title,
remove_quality_tags,
remove_release_groups,
)
@@ -506,10 +509,6 @@ class TestEpisodeGrouping:
# Property-Based Tests
# ============================================================================
from hypothesis import given, strategies as st, assume, settings
from vlm.parser import group_episodes
import logging
# Custom strategies for generating test data
@st.composite
-1
View File
@@ -1,7 +1,6 @@
"""Security tests for path generation and execution boundaries."""
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4
from vlm.config import Config
+1 -4
View File
@@ -1,11 +1,9 @@
"""Tests for plan review helpers."""
import csv
from pathlib import Path
from vlm.models import ExecutionPlan, FileOperation
from vlm.plan_render import render_review_verdict
from vlm.plan_review import (
REVIEW_APPLIED_AT_KEY,
build_duplicate_path_maps,
build_identity_lookup,
check_review_requirements,
@@ -15,7 +13,6 @@ from vlm.plan_review import (
review_plan,
save_review_csv,
)
from vlm.plan_render import render_review_verdict
from vlm.plan_structure_preview import build_structure_preview_lines
from vlm.planner import apply_review_to_plan, load_plan, save_plan
from vlm.utils import utc_now
+10 -7
View File
@@ -1,10 +1,11 @@
"""Unit tests for plan generator."""
import json
import pytest
from datetime import datetime, timezone
from pathlib import Path
import pytest
from vlm.config import Config
from vlm.models import (
ExecutionPlan,
@@ -1011,7 +1012,7 @@ def test_save_plan_to_json(config, tmp_path):
def test_load_plan_from_json(config, tmp_path):
"""Test loading execution plan from JSON file."""
from vlm.planner import save_plan, load_plan
from vlm.planner import load_plan, save_plan
# Create and save a plan
video_file = VideoFile(
@@ -1058,7 +1059,7 @@ def test_load_plan_from_json(config, tmp_path):
def test_save_and_load_plan_with_conflicts(config, tmp_path):
"""Test saving and loading plan with conflict information."""
from vlm.planner import save_plan, load_plan
from vlm.planner import load_plan, save_plan
# Set up config with tmp_path as library root
config.library_root = tmp_path
@@ -1102,7 +1103,7 @@ def test_save_and_load_plan_with_conflicts(config, tmp_path):
def test_save_and_load_plan_with_no_op_operations(config, tmp_path):
"""Test saving and loading plan with no-op operations."""
from vlm.planner import save_plan, load_plan
from vlm.planner import load_plan, save_plan
# Create files that will generate no-op operations
files_and_identities = [
@@ -1189,7 +1190,7 @@ def test_save_plan_json_is_human_readable(config, tmp_path):
def test_save_plan_with_multiple_operations(config, tmp_path):
"""Test saving plan with multiple operations of different types."""
from vlm.planner import save_plan, load_plan
from vlm.planner import load_plan, save_plan
files_and_identities = [
# Movie (move)
@@ -1265,8 +1266,9 @@ def test_load_plan_file_not_found(tmp_path):
def test_load_plan_invalid_json(tmp_path):
"""Test loading plan from invalid JSON raises JSONDecodeError."""
from vlm.planner import load_plan
import json
from vlm.planner import load_plan
invalid_json_path = tmp_path / "invalid.json"
with open(invalid_json_path, 'w') as f:
@@ -1278,8 +1280,9 @@ def test_load_plan_invalid_json(tmp_path):
def test_plan_json_includes_all_required_fields(config, tmp_path):
"""Test that saved JSON includes plan_id, created_at, operations, and summary."""
from vlm.planner import save_plan
import json
from vlm.planner import save_plan
video_file = VideoFile(
path=Path("/mnt/nas/videos/movie/Movie.2020.mkv"),
+5 -4
View File
@@ -1,13 +1,14 @@
"""Tests for quarantine manager."""
import json
import pytest
from datetime import datetime
from pathlib import Path
from datetime import datetime, timezone
from vlm.quarantine import QuarantineManager
import pytest
from vlm.config import Config
from vlm.models import QuarantineEntry, QuarantineManifest
from vlm.models import QuarantineManifest
from vlm.quarantine import QuarantineManager
class TestQuarantineManager:
+10 -9
View File
@@ -7,19 +7,20 @@ import csv
import json
import os
import time
import pytest
from io import StringIO
from pathlib import Path
from datetime import datetime, timezone
from vlm.models import SeasonCompleteness, DuplicateGroup, VideoFile, MovieIdentity, SeriesIdentity
from pathlib import Path
import pytest
from vlm.models import DuplicateGroup, MovieIdentity, SeasonCompleteness, SeriesIdentity, VideoFile
from vlm.reports import (
generate_inventory_report,
generate_completeness_report,
generate_duplicate_report,
generate_summary_report,
_format_duration,
_format_episode_list,
_format_size,
_format_duration
generate_completeness_report,
generate_duplicate_report,
generate_inventory_report,
generate_summary_report,
)
+8 -3
View File
@@ -4,11 +4,16 @@ Tests the complete workflow from analysis to report generation.
"""
import json
from pathlib import Path
from datetime import datetime, timezone
from vlm.models import SeriesIdentity, VideoFile, MovieIdentity
from pathlib import Path
from vlm.analysis import analyze_series_completeness, detect_duplicates
from vlm.reports import generate_completeness_report, generate_duplicate_report, generate_summary_report
from vlm.models import MovieIdentity, SeriesIdentity, VideoFile
from vlm.reports import (
generate_completeness_report,
generate_duplicate_report,
generate_summary_report,
)
class TestReportsIntegration:
+8 -11
View File
@@ -1,15 +1,14 @@
"""Unit tests for the inventory scanner module."""
import os
import threading
import tempfile
import time
import csv
import json
import os
import subprocess
import threading
import time
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import patch, MagicMock
import subprocess
import json
from unittest.mock import MagicMock, patch
import pytest
@@ -17,10 +16,10 @@ from vlm.config import Config
from vlm.models import VideoFile
from vlm.scanner import (
categorize_file,
scan_library,
extract_metadata,
save_inventory_csv,
load_inventory_csv,
save_inventory_csv,
scan_library,
)
@@ -932,7 +931,6 @@ class TestInventoryReports:
assert output_file.exists()
# Read and verify content
import csv
with open(output_file, 'r', encoding='utf-8') as f:
# Skip comment lines
lines = [line for line in f if not line.startswith('#')]
@@ -1179,7 +1177,6 @@ class TestInventoryReports:
save_inventory_json(video_files, json_file, library_root)
# Read CSV data
import csv
with open(csv_file, 'r', encoding='utf-8') as f:
lines = [line for line in f if not line.startswith('#')]
reader = csv.DictReader(lines)
+4 -7
View File
@@ -2,16 +2,13 @@
import json
import os
import pytest
from datetime import datetime, timezone
from pathlib import Path
from vlm.state import (
load_state,
save_state,
StateManager,
VALID_STATUSES
)
import pytest
from vlm.models import FileState, StateStore
from vlm.state import VALID_STATUSES, StateManager, load_state, save_state
class TestLoadSaveState:
-3
View File
@@ -1,10 +1,7 @@
"""Tests for utility functions."""
import tempfile
from pathlib import Path
import pytest
from vlm.utils import canonical_path, canonical_path_str, format_size, utc_now