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:
parent
b185ac4765
commit
fd1e83a378
2 changed files with 20 additions and 8 deletions
|
|
@ -21,6 +21,10 @@ NUDGE_MIN_OPEN = 8
|
||||||
NUDGE_MAX_AGE_DAYS = 21
|
NUDGE_MAX_AGE_DAYS = 21
|
||||||
NUDGE_MIN_RECURRENCE = 3
|
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)\]`")
|
TAG_RE = re.compile(r"`\[(friction|gotcha|recurring|unused)\]`")
|
||||||
DATE_RE = re.compile(r"(\d{4})-(\d{2})-(\d{2})")
|
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)
|
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
|
first_seen = None
|
||||||
age_days = None
|
age_days = None
|
||||||
paths = parse_paths(raw)
|
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 {
|
return {
|
||||||
"tag": tag_m.group(1) if tag_m else None,
|
"tag": tag_m.group(1) if tag_m else None,
|
||||||
"first_seen": first_seen,
|
"first_seen": first_seen,
|
||||||
|
|
@ -119,6 +124,7 @@ def parse_signal(raw, today):
|
||||||
|
|
||||||
|
|
||||||
def load_signals(path, 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:
|
with open(path, encoding="utf-8") as fh:
|
||||||
text = fh.read()
|
text = fh.read()
|
||||||
return [parse_signal(s, today) for s in split_signals(extract_open_section(text))]
|
return [parse_signal(s, today) for s in split_signals(extract_open_section(text))]
|
||||||
|
|
@ -137,16 +143,12 @@ def nudge_line(signals):
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Parse FRICTION.md Open signals for /kaizen.")
|
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("--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")
|
parser.add_argument("--file", default=FRICTION, help="path to FRICTION.md")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.today:
|
today = args.today or datetime.date.today()
|
||||||
y, m, d = args.today.split("-")
|
|
||||||
today = datetime.date(int(y), int(m), int(d))
|
|
||||||
else:
|
|
||||||
today = datetime.date.today()
|
|
||||||
|
|
||||||
signals = load_signals(args.file, today)
|
signals = load_signals(args.file, today)
|
||||||
if args.nudge:
|
if args.nudge:
|
||||||
print(nudge_line(signals))
|
print(nudge_line(signals))
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,16 @@ def test_nudge_line_overdue_on_count():
|
||||||
assert "OVERDUE" in fs.nudge_line(sigs)
|
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():
|
def test_load_signals_reads_real_friction_file():
|
||||||
path = os.path.join(os.path.dirname(__file__), "..", "docs", "FRICTION.md")
|
path = os.path.join(os.path.dirname(__file__), "..", "docs", "FRICTION.md")
|
||||||
sigs = fs.load_signals(path, TODAY)
|
sigs = fs.load_signals(path, TODAY)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue