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) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-14 21:25:03 +02:00
parent b185ac4765
commit fd1e83a378
2 changed files with 20 additions and 8 deletions

View file

@ -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))

View file

@ -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)