refactor: consolidate skill docs, add anti-drift tests, and apply audit fixes

DLO-13: Restructure vlm-library-workflow skill as safety contract layer.
- Rewrite SKILL.md (69 lines): safety contract, execution threshold semantics,
  six-step high-risk loop, decision rules, phase skeleton
- Delete redundant references (cli-reference, workflow, command-recipes, dev-guide)
- Add triage.md (failure mapping + preflight) and dev-map.md (module→test mapping)
- Add tests/test_docs_consistency.py: 78 parametrized tests verifying documented
  vlm commands exist in CLI registry
- Add CSV path mismatch test to test_plan_review.py (4th safety gate path)
- Delete vlm-expert.skill (Gemini package, 7 months stale) and README Gemini section

DLO-2 audit fixes: rate limiter injection, symmetric quarantine categories,
review-plan safety gates, parser improvements, planner validation.

CLI modularization: commands/ directory with one module per command group.
This commit is contained in:
windyboy
2026-09-25 13:50:09 +08:00
parent c7a55190d7
commit dfa18ed405
35 changed files with 1116 additions and 621 deletions
+86
View File
@@ -63,6 +63,92 @@ def test_generate_plan_for_movie_with_year(config):
assert "Some Movie (2020)" in operation.reason
def test_generate_plan_attaches_sidecar_context(tmp_path):
"""Test move operations carry review-visible sidecar associations."""
config = Config(
library_root=tmp_path,
video_extensions=[".mp4", ".mkv", ".avi"],
movie_template="movie/{title} ({year})/",
movie_filename_template="{title} ({year}){ext}",
)
src_dir = tmp_path / "downloads"
src_dir.mkdir()
video = src_dir / "Movie (2020).mkv"
video.touch()
sub = src_dir / "Movie (2020).zh.srt"
sub.touch()
nfo = src_dir / "Movie (2020).nfo"
nfo.touch()
unrelated = src_dir / "Movie (2020).1.srt"
unrelated.touch()
video_file = VideoFile(
path=video,
filename=video.name,
size_bytes=1000000,
modified_timestamp=datetime.now(timezone.utc),
category="movie",
)
identity = MovieIdentity(
title="Movie",
year=2020,
confidence=0.9,
needs_review=False,
original_filename=video.name,
)
plan = generate_plan([(video_file, identity)], config)
operation = plan.operations[0]
assert operation.operation_type == "move"
sidecars = operation.review_context.get("sidecars")
assert sidecars is not None
assert [s["name"] for s in sidecars] == ["Movie (2020).nfo", "Movie (2020).zh.srt"]
dest_dir = operation.destination_path.parent
by_name = {s["name"]: s for s in sidecars}
assert by_name["Movie (2020).zh.srt"]["proposed_destination_path"] == str(dest_dir / "Movie (2020).zh.srt")
assert by_name["Movie (2020).nfo"]["proposed_destination_path"] == str(dest_dir / "Movie (2020).nfo")
# Numeric-suffix file must not be associated
assert all(s["name"] != "Movie (2020).1.srt" for s in sidecars)
def test_generate_plan_no_sidecars_for_noop(tmp_path):
"""Test no-op operations carry no sidecar context."""
config = Config(
library_root=tmp_path,
video_extensions=[".mp4", ".mkv", ".avi"],
movie_template="movie/{title} ({year})/",
movie_filename_template="{title} ({year}){ext}",
)
src_dir = tmp_path / "downloads"
src_dir.mkdir()
video = src_dir / "random_movie.mkv"
video.touch()
sub = src_dir / "random_movie.srt"
sub.touch()
video_file = VideoFile(
path=video,
filename=video.name,
size_bytes=1000000,
modified_timestamp=datetime.now(timezone.utc),
category="movie",
)
identity = MovieIdentity(
title="Random Movie",
year=None,
confidence=0.3,
needs_review=True,
original_filename=video.name,
)
plan = generate_plan([(video_file, identity)], config)
operation = plan.operations[0]
assert operation.operation_type == "no-op"
assert "sidecars" not in (operation.review_context or {})
def test_generate_plan_for_movie_without_year(config):
"""Test plan generation for a movie without year (needs review)."""
video_file = VideoFile(