Keeps a calendar clean and trustworthy: event naming conventions, default durations, buffer rules, dead-event pruning, and a weekly defrag pass. Triggers when someone asks to clean up, audit, or set standards for their calendar.
./install.sh calendar-hygienename: "calendar_hygiene" description: "Keeps a calendar clean and trustworthy: event naming conventions, default durations, buffer rules, dead-event pruning, and a weekly defrag pass. Triggers when someone asks to clean up, audit, or set standards for their calendar." metadata: { "includeInPrompt": true }
A calendar is only useful if you trust it. This skill maintains the calendar as a system so every glance tells you what is happening, where, and why. It is not a scheduling method (how to plan your day) and not a review ritual. It is maintenance: naming, defaults, buffers, pruning, and defragging.
title,start,end,location,notes,recurring (any consistent 24h datetime format). Save the script below to hygiene_audit.py and run python3 hygiene_audit.py events.csv. Review the flags with the user before changing anything.After a hygiene pass, return:
Defaults: standup 15 / 1:1 30 / review 45 / default 60
Buffer: 15 min between meetings, 90 min max without a break
Travel: real time + 15 min
Titles: person + outcome. No "sync", "catch up", "quick chat".
Trust test: every event has what, where, and why.
Save this as hygiene_audit.py in a scratch directory, next to your events.csv, then run python3 hygiene_audit.py events.csv. It needs nothing beyond the standard library. Tested against a 12-event fixture week, where it raised 17 flags covering every check: vague titles, missing context, long blocks, back-to-back gaps, and recurring-vague titles.
python
#!/usr/bin/env python3
"""Calendar hygiene audit (heuristic, not authoritative).
Reads a CSV export of one week of events and flags likely hygiene problems.
Save your week's events to events.csv, then run: python3 hygiene_audit.py events.csv
CSV columns (header row required):
title,start,end,location,notes,recurring
start/end format: 2026-09-28 09:00 (24h, any consistent parseable format)
recurring: yes or no
"""
import csv
import re
import sys
from datetime import datetime
VAGUE = re.compile(r"^(meeting|call|sync|check-?in|chat|touch ?base|quick sync|"
r"sync up|follow[- ]?up|catch[- ]?up|talk|discuss|stuff|"
r"update|meeting w.*|chat w.*|sync w.*)$", re.IGNORECASE)
def parse(s):
for fmt in ("%Y-%m-%d %H:%M", "%m/%d/%Y %H:%M", "%Y-%m-%dT%H:%M"):
try:
return datetime.strptime(s.strip(), fmt)
except ValueError:
continue
raise ValueError("cannot parse datetime: %r" % s)
def main(path):
rows = list(csv.DictReader(open(path, newline="")))
for r in rows:
r["start_dt"] = parse(r["start"])
r["end_dt"] = parse(r["end"])
rows.sort(key=lambda r: r["start_dt"])
findings = []
for r in rows:
dur = (r["end_dt"] - r["start_dt"]).total_seconds() / 60
t = r["title"].strip()
if VAGUE.match(t):
findings.append((r["start"], t, "vague title: name a person and an outcome, e.g. 'Priya: ship the proposal'"))
if not r["location"].strip() and not r["notes"].strip():
findings.append((r["start"], t, "no location and no notes: future you will not know what this was for"))
if dur > 90 and "focus" not in t.lower():
findings.append((r["start"], t, "longer than 90 min without a break built in: split or add recovery"))
for a, b in zip(rows, rows[1:]):
if a["start_dt"].date() == b["start_dt"].date():
gap = (b["start_dt"] - a["end_dt"]).total_seconds() / 60
if 0 <= gap < 15:
findings.append((b["start"], b["title"].strip(),
"back-to-back with '%s' (%d min gap): insert a buffer" % (a["title"].strip(), gap)))
recur_vague = [r for r in rows
if r["recurring"].strip().lower() == "yes" and VAGUE.match(r["title"].strip())]
for r in recur_vague:
findings.append((r["start"], r["title"].strip(),
"recurring AND vague: rename once, it pays off every week"))
if not findings:
print("Clean week: no hygiene flags raised.")
return
print("Hygiene flags (%d):" % len(findings))
for when, title, why in findings:
print("- [%s] %s\n -> %s" % (when, title, why))
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "events.csv")
publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.