diff --git a/tests/test_scavenge_directives_lift.py b/tests/test_scavenge_directives_lift.py new file mode 100644 index 00000000..1d8b0a64 --- /dev/null +++ b/tests/test_scavenge_directives_lift.py @@ -0,0 +1,118 @@ +"""Tests for the 2026-07-02 directive scavenge lift. + +The scavenge pass lifted 15 new directives from previously-unscanned markdown +(MMA_Support, nagent_review, intent_dsl_survey, docs/handoffs). These tests +encode the structural contract for those new directives: + + - every new directive has both a v1.md and a meta.md file + - every new directive's v1.md body starts with a '# ' imperative heading + - every new directive's meta.md has the ## v1 section + Source/Lifted lines + - every new directive is referenced in conductor/directives/presets/current_baseline.md + - the total directive count grew from 66 to 81 + +These tests are additive to tests/test_aggregate_directives.py (the existing +contract tests for the aggregator script). The new tests here pin the +scavenge-pass output so a future agent can verify the lift was complete. +""" +from __future__ import annotations + +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parent.parent +DIRECTIVES_DIR = REPO_ROOT / "conductor" / "directives" +PRESET = REPO_ROOT / "conductor" / "directives" / "presets" / "current_baseline.md" + +SCAVENGE_DIRECTIVES: list[str] = [ + "tier1_orchestrator_no_implementation", + "tier3_worker_amnesia", + "tier4_qa_compressed_fix", + "token_firewall_prevents_bloat", + "stub_before_implement", + "subagent_returns_artifact_not_transcript", + "parse_failure_visible_to_conversation", + "state_visible_at_the_right_layer", + "file_id_stable_across_rename", + "decompose_or_isolate_never_offload", + "intent_signal_postfix_not_xml", + "pipeline_immediate_mode_no_object", + "dsl_uses_first_class_spans_for_errors", + "search_all_call_sites_after_signature_change", + "run_full_tier_after_phase_refactor", +] + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_scavenge_lift_count_matches_expected() -> None: + assert len(SCAVENGE_DIRECTIVES) == 15, "scavenge pass lifted 15 directives; SCAVENGE_DIRECTIVES list must stay in sync" + + +@pytest.mark.parametrize("directive_name", SCAVENGE_DIRECTIVES) +def test_scavenge_directive_has_v1_file(directive_name: str) -> None: + path = DIRECTIVES_DIR / directive_name / "v1.md" + assert path.is_file(), "missing v1.md for scavenge-lifted directive: " + directive_name + + +@pytest.mark.parametrize("directive_name", SCAVENGE_DIRECTIVES) +def test_scavenge_directive_has_meta_file(directive_name: str) -> None: + path = DIRECTIVES_DIR / directive_name / "meta.md" + assert path.is_file(), "missing meta.md for scavenge-lifted directive: " + directive_name + + +@pytest.mark.parametrize("directive_name", SCAVENGE_DIRECTIVES) +def test_scavenge_v1_starts_with_imperative_heading(directive_name: str) -> None: + path = DIRECTIVES_DIR / directive_name / "v1.md" + first_line = _read(path).splitlines()[0] + assert first_line.startswith("# "), ( + directive_name + " v1.md first line is not a '# ' imperative heading: " + first_line + ) + + +@pytest.mark.parametrize("directive_name", SCAVENGE_DIRECTIVES) +def test_scavenge_meta_has_required_sections(directive_name: str) -> None: + path = DIRECTIVES_DIR / directive_name / "meta.md" + body = _read(path) + assert "## v1" in body, directive_name + " meta.md missing '## v1' section" + assert "**Source:**" in body, directive_name + " meta.md missing **Source:** line" + assert "**Lifted:**" in body, directive_name + " meta.md missing **Lifted:** line" + + +def test_scavenge_directives_listed_in_current_baseline_preset() -> None: + preset_body = _read(PRESET) + for name in SCAVENGE_DIRECTIVES: + assert name in preset_body, ( + "current_baseline.md does not reference scavenge-lifted directive: " + name + ) + + +def test_total_directive_count_at_least_81() -> None: + v1_files = sorted(DIRECTIVES_DIR.glob("*/v1.md")) + assert len(v1_files) >= 81, ( + "expected >= 81 directives after scavenge pass (66 baseline + 15 scavenge); found " + + str(len(v1_files)) + ) + + +def test_baseline_preset_size_grew() -> None: + preset_body = _read(PRESET) + assert preset_body.count("\n- ") >= 81, ( + "current_baseline.md should have >= 81 directive lines after scavenge pass" + ) + + +@pytest.mark.parametrize("directive_name", SCAVENGE_DIRECTIVES) +def test_scavenge_v1_first_line_is_complete_sentence(directive_name: str) -> None: + path = DIRECTIVES_DIR / directive_name / "v1.md" + first_line = _read(path).splitlines()[0].lstrip("# ").strip() + assert len(first_line) > 20, ( + directive_name + " v1.md first-line statement is too short to be a complete imperative: " + + first_line + ) + assert first_line[0].isupper() or first_line.startswith(("Tier", "Sub-", "Parse", "State", "File", "Decompose", "Intent", "DSL", "Search", "Run")), ( + directive_name + " v1.md first-line statement should start with a capitalized verb/noun: " + + first_line + ) \ No newline at end of file