import importlib.util import os _SPEC = importlib.util.spec_from_file_location( "friction_scan", os.path.join(os.path.dirname(__file__), "..", "scripts", "friction-scan.py"), ) fs = importlib.util.module_from_spec(_SPEC) _SPEC.loader.exec_module(fs) SAMPLE = """# FRICTION.md ## Open signals _(append new raw signals here)_ - `[gotcha]` **First thing** (2026-06-01): body line one. continuation line two. - `[friction]` **Second thing** (2026-06-10): only one line. --- ## Kaizen reviews — decisions ledger - `[gotcha]` **Should not be parsed** (2026-01-01): in the ledger. """ def test_extract_open_section_stops_at_next_heading(): section = fs.extract_open_section(SAMPLE) assert "First thing" in section assert "Second thing" in section assert "Should not be parsed" not in section def test_split_signals_finds_two_items_and_joins_continuations(): signals = fs.split_signals(fs.extract_open_section(SAMPLE)) assert len(signals) == 2 assert "continuation line two" in signals[0] assert signals[1].startswith("`[friction]`") import datetime TODAY = datetime.date(2026, 6, 15) def test_parse_signal_extracts_tag_and_date_and_age(): raw = fs.split_signals(fs.extract_open_section(SAMPLE))[0] sig = fs.parse_signal(raw, TODAY) assert sig["tag"] == "gotcha" assert sig["first_seen"] == "2026-06-01" assert sig["age_days"] == 14 assert "First thing" in sig["text"] def test_parse_signal_handles_missing_date(): sig = fs.parse_signal("`[unused]` **No date here** something", TODAY) assert sig["tag"] == "unused" assert sig["first_seen"] is None assert sig["age_days"] is None def test_recurrence_from_ordinal(): assert fs.parse_recurrence("blah 5th occurrence (06-05/06/06) blah") == 5 def test_recurrence_from_datelist_when_no_ordinal(): # three slash-separated date fragments -> recurrence 3 assert fs.parse_recurrence("recurred (06-05/06-09/06-10) again") == 3 def test_recurrence_defaults_to_one(): assert fs.parse_recurrence("a one-off gotcha") == 1 def test_parse_paths_picks_repo_paths_only(): paths = fs.parse_paths("see `scripts/repo-scan.py` and `latest` and `foo.yml`") assert "scripts/repo-scan.py" in paths assert "foo.yml" in paths assert "latest" not in paths def test_still_exists_false_for_missing_path(): sig = fs.parse_signal("`[unused]` **x** (2026-06-01): `scripts/nope-not-real.py`", TODAY) assert sig["still_exists"] is False def test_still_exists_true_for_real_path(): sig = fs.parse_signal("`[gotcha]` **x** (2026-06-01): `scripts/repo-scan.py`", TODAY) assert sig["still_exists"] is True def test_nudge_line_overdue_on_recurrence(): sigs = [{"age_days": 2, "recurrence_count": 5}] line = fs.nudge_line(sigs) assert "OVERDUE" in line assert "max recurrence 5x" in line def test_nudge_line_ok_when_quiet(): sigs = [{"age_days": 3, "recurrence_count": 1}, {"age_days": 1, "recurrence_count": 1}] line = fs.nudge_line(sigs) assert "ok" in line assert "OVERDUE" not in line def test_nudge_line_overdue_on_count(): sigs = [{"age_days": 1, "recurrence_count": 1} for _ in range(8)] assert "OVERDUE" in fs.nudge_line(sigs) def test_still_exists_ignores_non_repo_tokens(): sig = fs.parse_signal("`[gotcha]` **x** (2026-06-01): `caddy-dns/gandi` and `make tf-init/plan`", TODAY) assert sig["still_exists"] is True def test_nudge_line_overdue_on_age(): sigs = [{"age_days": 21, "recurrence_count": 1}] assert "OVERDUE" in fs.nudge_line(sigs) def test_load_signals_reads_real_friction_file(): path = os.path.join(os.path.dirname(__file__), "..", "docs", "FRICTION.md") sigs = fs.load_signals(path, TODAY) # May legitimately be empty right after a /kaizen pass consumes every open signal — # an empty Open-signals section is the goal state, not a failure. Assert the function # parses the real file into well-formed signals (validity holds vacuously when empty). assert isinstance(sigs, list) assert all(s["tag"] in {"friction", "gotcha", "recurring", "unused"} for s in sigs)