name: notion-cleanup
description: Audit a Notion database for stale rows, missing-but-required fields, mistyped columns (text where numbers belong), duplicates, and orphaned select options. Produces a triage report with one-click fixes. NEVER mutates without explicit confirm per fix category. Use when the user says "audit this database", "clean up the tracker", "what's wrong with my Notion DB", or wants to prep a DB before a quarterly review.
/notion-cleanup
Read-only audit + opt-in batch fixes. Default behaviour is reporting; the user must approve each fix category before any write happens.
- Database URL or ID (required).
- Stale threshold (optional, default 90 days). Rows with
last_edited_time older than this are flagged as stale. - Required-field list (optional). User can specify "rows must have Cost and Status filled". If omitted, infer from typical patterns (Status filled, Title non-empty, primary numeric field non-empty).
Steps
- Read.
notion-fetch the DB schema + use the row-enumeration workaround (notion-search with single-letter queries "a" "e" "o" "s", union results, fetch each by ID for full properties). Cap at 500 rows for performance; for larger DBs, scope to a filter first. - Run checks. For each row:
- Stale?
last_edited_time < (now - threshold). - Required blanks? Any required property is empty.
- Mistyped values? A text property contains values that look like they should be a number / date / URL (heuristic: 95%+ of values match a single non-text pattern).
- Duplicate titles? Two or more rows share the same title (case-insensitive).
- Status mismatch? A row marked "Active" but no edit in 90+ days.
- Orphan select options? Options defined in schema but no row uses them.
- Workstream mismatches? Multi-select rows where one tag contradicts another (e.g. "Cancelled" status + "Active campaign" workstream).
- Triage report. Group findings by category, with row counts + sample row URLs (first 3 per category). Sort categories by impact (mistyped column > required blanks > duplicates > stale > orphan options).
- Confirm gate per category. For each category, ask: "Fix these N rows? (y / skip / show all)". Do not bundle approvals.
- Apply fixes per approval:
- Stale "Active" rows → propose flipping to "Paused" or "Cancelled". Show each, never blanket-update.
- Mistyped column → propose
ALTER COLUMN ... SET <type> (with the conversion preview from /notion-tracker rules). - Duplicates → propose merging or marking the older row "Archived". Never delete.
- Required blanks → list them; never auto-fill values, just surface for human entry.
- Orphan options → propose
ALTER COLUMN to drop them; warn this is one-way.
- Confirm. Print summary of fixes applied + categories deferred.
Decision rules
- Read-only by default. This skill's value is in the report; mutations are opt-in per category.
- Never blanket-update. Even if user says "fix everything", iterate per category, show the proposed change set, get explicit y on each.
- Never delete rows. Worst-case mutation is changing Status to "Archived". Deletion is a manual user action.
- Type conversion is irreversible if values get lost. Always preview the lossy values before the ALTER.
Output
- Triage report in conversation.
- Per-category fix summary after each approval.
- Optional: append findings to
Admin/<tracker-name>-cleanup-log.md in the vault so next audit can compare.
Anti-patterns
- Don't audit then immediately fix without showing the report. The report IS the deliverable for most runs.
- Don't propose "AI-fixing" blank required fields by inference. That's fabrication. List them and stop.
- Don't audit cross-database relations (rollups, relations) in v1 — too brittle, scope expansion.
Hard-won gotchas
- Stale threshold should match the DB's natural cadence. Marketing campaign DBs may be stale at 7 days; OPEX DBs at 90+ days. Default 90 but state it loudly and ask if it should differ.
- `last_edited_time` reflects ANY edit including bulk schema changes. If user just renamed a property, all rows look "fresh" — caveat the staleness check.