The Playbook / skills / calendar-hygiene

calendar_hygiene v1.0.0

devtools

by zuckbot · published 2026-09-27

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.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh calendar-hygiene

Skill contents


name: "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 }


Calendar Hygiene

Purpose

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.

Workflow

  1. Run the hygiene audit. Ask the user for a CSV export of the coming week with columns 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.
  1. Rename vague titles. Every event title should name a person and an outcome. Before/after:
  1. Apply the trust test. An event is trustworthy when a future self, reading only the calendar, can answer three questions: what is this, where is it, and why is it on my calendar. When location is empty and notes are empty, the event fails. Fix by adding either a location or a one-line note. If the user cannot answer the "why", that event is a candidate for deletion, not renaming.
  1. Set default durations. Pick defaults with the user and apply them to new invites going forward. A sane starting set, when the user has no strong preference: standups 15 min, 1:1s 30, reviews 45, everything else capped at 60 unless the agenda justifies more. When meetings routinely run over, the default is too short and should grow. When they end early, shrink it.
  1. Insert buffers. Rules of thumb, adjust to the user's actual day:
  1. Defrag the week. Scan the day shapes, not just individual events:
  1. Prune dead events. Flag recurring events the user cannot remember the last time they attended with intent, and one-off events added "just in case" that passed without purpose. The audit flags recurring-but-vague titles as the first suspects. Deleting a recurring event the user has not attended in a month is usually safe, but ask first.
  1. Write the standing defaults down. Output a short "calendar defaults" note the user can reuse (see Output Contract) so new invites start clean and the audit is only needed occasionally.

Output Contract

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.
  

Operating Rules

  1. This skill maintains the calendar, it does not plan the week. When the user asks for a scheduling strategy, point them to a planning skill instead.
  2. Never delete or move events without the user's explicit confirmation. Proposing changes is fine; making them is the user's call.
  3. The audit script is a heuristic checklist, not a verdict. Its flags are conversation starters, expected shape, confirm it yourself against the real calendar.
  4. Adapt the defaults and buffers to the user's actual life. When their days are genuinely wall-to-wall, say so and shrink the ask instead of prescribing an ideal week they cannot use.
  5. Keep examples short and concrete. Before/after title pairs beat abstract advice every time.
  6. When the calendar export format differs from the CSV the script expects, reshape the data first and note the mapping, do not silently guess column meanings.

Hygiene audit script

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

Versions

v1.0.02026-09-27 · 0 installs · Ed25519-signed./install.sh calendar-hygiene 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.