from pathlib import Path import json import pytest from unittest.mock import patch, MagicMock from scripts.audit.generate_chronology import ( extract_slug_date, extract_summary, classify_status, walk_track_folders, format_markdown, ) from scripts.audit.chronology_quality_gate import run_quality_gate def test_slug_date_extraction() -> None: result: str = extract_slug_date("gencpp_python_bindings_20260308") assert result == "2026-03-08" def test_slug_date_extraction_handles_missing_date() -> None: result = extract_slug_date("my_folder") assert result is None def test_summary_extraction_rejects_priority_line(tmp_path: Path) -> None: spec_content: str = "# Title\n\n**Priority:** A (foundational)\n\nReal description of the work.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description of the work." def test_summary_extraction_rejects_date_line(tmp_path: Path) -> None: spec_content: str = "# Title\n\n**Date:** 2026-06-20\n\nReal description.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description." def test_summary_extraction_rejects_initialized_line(tmp_path: Path) -> None: spec_content: str = "# Title\n\n**Initialized:** 2026-06-13\n\nReal description.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description." def test_summary_extraction_rejects_track_line(tmp_path: Path) -> None: spec_content: str = "# Title\n\n**Track:** Some track\n\nReal description.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description." def test_summary_extraction_rejects_parent_umbrella_line(tmp_path: Path) -> None: spec_content: str = "# Title\n\n**Parent umbrella:** result_migration_20260616\n\nReal description.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description." def test_summary_extraction_rejects_confidence_line(tmp_path: Path) -> None: spec_content: str = "# Title\n\n**Confidence:** high\n\nReal description.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description." def test_summary_extraction_prefers_metadata_description_prose(tmp_path: Path) -> None: metadata: dict = {"description": "A real prose description of the track."} (tmp_path / "metadata.json").write_text(json.dumps(metadata), encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "A real prose description of the track." def test_summary_extraction_rejects_metadata_description_field_text(tmp_path: Path) -> None: metadata: dict = {"description": "**Priority:** A (foundational)"} (tmp_path / "metadata.json").write_text(json.dumps(metadata), encoding="utf-8") spec_content: str = "# Title\n\nReal description from spec.\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) assert result == "Real description from spec." def test_summary_extraction_truncates_to_25_words(tmp_path: Path) -> None: long_line: str = " ".join(["word"] * 50) spec_content: str = f"# Title\n\n{long_line}\n" (tmp_path / "spec.md").write_text(spec_content, encoding="utf-8") result: str = extract_summary(tmp_path) expected: str = " ".join(["word"] * 25) + "\u2026" assert result == expected # --- Classifier tests (the new git-history classifier) --- def test_classify_status_completion_report_override(tmp_path: Path) -> None: """TRACK_COMPLETION report in docs/reports/ overrides everything.""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) (reports_dir / "TRACK_COMPLETION_my_track_20260701.md").write_text("report", encoding="utf-8") result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, ) assert result[0] == "Completed" assert result[1] == "high" assert "completion report" in result[2] def test_classify_status_abort_report_override(tmp_path: Path) -> None: """TRACK_ABORTED report -> Abandoned.""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) (reports_dir / "TRACK_ABORTED_my_track_20260701.md").write_text("report", encoding="utf-8") result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, has_abort_report=True, ) assert result[0] == "Abandoned" assert result[1] == "high" assert "abort report" in result[2] def test_classify_status_superseded_state_toml_overrides_abort(tmp_path: Path) -> None: """state.toml status=superseded wins over abort report.""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) (reports_dir / "TRACK_ABORTED_my_track_20260701.md").write_text("report", encoding="utf-8") result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, has_abort_report=True, state_status="superseded", ) assert result[0] == "Superseded" assert result[1] == "high" def test_classify_status_work_commits_completed(tmp_path: Path) -> None: """>=3 work commits -> Completed.""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) with patch("scripts.audit.generate_chronology._git_log") as mock_log: mock_log.return_value = "abc1234 feat: add thing\ndef5678 fix: fix thing\nghi9012 refactor: refactor thing\n" result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, ) assert result[0] == "Completed" assert result[1] == "medium" assert "work commits" in result[2] def test_classify_status_metadata_commits_not_countd_as_work(tmp_path: Path) -> None: """conductor(plan): commits don't count as work commits.""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) with patch("scripts.audit.generate_chronology._git_log") as mock_log: mock_log.return_value = "abc1234 conductor(plan): mark task\ndef5678 conductor(state): update\nghi9012 conductor(track): init\n" result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, ) assert result[0] == "Active" def test_classify_status_1_2_work_commits_in_progress(tmp_path: Path) -> None: """1-2 work commits + in tracks/ -> In Progress.""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) with patch("scripts.audit.generate_chronology._git_log") as mock_log: mock_log.return_value = "abc1234 feat: add thing\ndef5678 fix: fix thing\n" result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, ) assert result[0] == "In Progress" def test_classify_status_archive_no_override_completed_low(tmp_path: Path) -> None: """archive/ + no completion report -> Completed (low confidence).""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) with patch("scripts.audit.generate_chronology._git_log") as mock_log: mock_log.return_value = "abc1234 feat: thing\ndef5678 fix: thing\nghi9012 refactor: thing\n" result = classify_status( folder_link="conductor/archive/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, ) # >=3 work commits -> Completed (medium), even in archive assert result[0] == "Completed" def test_classify_status_fallback_needs_review(tmp_path: Path) -> None: """Inconclusive -> Needs Review (path is neither tracks/ nor archive/).""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) with patch("scripts.audit.generate_chronology._git_log") as mock_log: mock_log.return_value = "" result = classify_status( folder_link="some/other/path/my_track", current="", track_id="my_track", repo_root=tmp_path, reports_dir=reports_dir, ) assert result[0] == "Needs Review" def test_classify_status_returns_3_tuple(tmp_path: Path) -> None: """Classifier must return (status, confidence, reason).""" reports_dir = tmp_path / "docs/reports" reports_dir.mkdir(parents=True) with patch("scripts.audit.generate_chronology._git_log") as mock_log: mock_log.return_value = "" result = classify_status( folder_link="conductor/tracks/my_track_20260701", current="active", track_id="my_track_20260701", repo_root=tmp_path, reports_dir=reports_dir, ) assert len(result) == 3 assert isinstance(result[0], str) assert isinstance(result[1], str) assert isinstance(result[2], str)