From fd1e83a378c7bd42f14089f58665c2d786266018 Mon Sep 17 00:00:00 2001 From: sjat Date: Sun, 14 Jun 2026 21:25:03 +0200 Subject: [PATCH] fix(kaizen): scope still_exists to repo paths; test age nudge; tidy --today - Add REPO_DIRS constant; still_exists now only checks tokens that start with a known repo top-level dir, ignoring plugin names (caddy-dns/gandi), make command fragments (tf-init/plan), and role-relative paths. - Add test_still_exists_ignores_non_repo_tokens (was failing before fix). - Add test_nudge_line_overdue_on_age to close coverage gap on age threshold. - Add load_signals docstring. - Replace manual --today date parsing with datetime.date.fromisoformat type converter so malformed dates give a clean argparse error. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/friction-scan.py | 18 ++++++++++-------- tests/test_friction_scan.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/scripts/friction-scan.py b/scripts/friction-scan.py index fcdd993..aa062c8 100755 --- a/scripts/friction-scan.py +++ b/scripts/friction-scan.py @@ -21,6 +21,10 @@ NUDGE_MIN_OPEN = 8 NUDGE_MAX_AGE_DAYS = 21 NUDGE_MIN_RECURRENCE = 3 +# Top-level repo dirs — only tokens under these are treated as repo-root paths +# for still_exists (avoids false negatives on plugin names, make commands, etc.). +REPO_DIRS = ("roles/", "scripts/", "docs/", "playbooks/", "inventories/", "tests/", "terraform/", ".claude/") + TAG_RE = re.compile(r"`\[(friction|gotcha|recurring|unused)\]`") DATE_RE = re.compile(r"(\d{4})-(\d{2})-(\d{2})") ORDINAL_RE = re.compile(r"(\d+)(?:st|nd|rd|th)\s+(?:occurrence|reinforcement|time)", re.I) @@ -106,7 +110,8 @@ def parse_signal(raw, today): first_seen = None age_days = None paths = parse_paths(raw) - still_exists = all(os.path.exists(os.path.join(REPO_ROOT, p)) for p in paths) if paths else True + repo_paths = [p for p in paths if p.startswith(REPO_DIRS)] + still_exists = all(os.path.exists(os.path.join(REPO_ROOT, p)) for p in repo_paths) if repo_paths else True return { "tag": tag_m.group(1) if tag_m else None, "first_seen": first_seen, @@ -119,6 +124,7 @@ def parse_signal(raw, today): def load_signals(path, today): + """Read a FRICTION.md file and return its Open signals as parsed dicts.""" with open(path, encoding="utf-8") as fh: text = fh.read() return [parse_signal(s, today) for s in split_signals(extract_open_section(text))] @@ -137,16 +143,12 @@ def nudge_line(signals): def main(): parser = argparse.ArgumentParser(description="Parse FRICTION.md Open signals for /kaizen.") parser.add_argument("--nudge", action="store_true", help="print a one-line overdue summary") - parser.add_argument("--today", help="override today's date (YYYY-MM-DD) for testing") + parser.add_argument("--today", type=datetime.date.fromisoformat, + help="override today's date (YYYY-MM-DD) for testing") parser.add_argument("--file", default=FRICTION, help="path to FRICTION.md") args = parser.parse_args() - if args.today: - y, m, d = args.today.split("-") - today = datetime.date(int(y), int(m), int(d)) - else: - today = datetime.date.today() - + today = args.today or datetime.date.today() signals = load_signals(args.file, today) if args.nudge: print(nudge_line(signals)) diff --git a/tests/test_friction_scan.py b/tests/test_friction_scan.py index 66984cf..5cceb31 100644 --- a/tests/test_friction_scan.py +++ b/tests/test_friction_scan.py @@ -110,6 +110,16 @@ def test_nudge_line_overdue_on_count(): 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)