caseyg avatar

todoist-triage

Triage and clean up Todoist with duplicate detection, stale task cleanup, and ADHD-friendly coaching

提供方 caseyg|开源

Todoist Triage

Interactive triage and cleanup for your Todoist task list, designed with Executive Function (EF) and ADHD support in mind. Syncs data to a local SQLite database for efficient analysis, detects issues, suggests improvements, and coaches you toward better task management habits.

Philosophy: EF and ADHD-Friendly Design

This skill acts as an Executive Function coach, not just a task cleaner. It's built around principles that help people who struggle with:

  • Task initiation: Identifies "quick wins" (2-5 min tasks) to build momentum
  • Task sizing: Breaks down overwhelming tasks into concrete next actions
  • Clarity: Ensures tasks are SMART (Specific, Measurable, Attainable, Relevant, Timebound)
  • Decision fatigue: Batches similar decisions, offers smart defaults
  • Motivation: Celebrates progress, suggests dopamine-friendly task ordering
  • Overwhelm: Never shows everything at once; works in focused chunks

Core Principles:

  1. One thing at a time - Each question focuses on one decision
  2. Momentum first - Start sessions by identifying quick wins
  3. Break it down - Large vague tasks become concrete action steps
  4. Raise the bar gently - Suggest improvements without judgment
  5. Learn patterns - Remember what works for you specifically

Trigger Phrases

  • /todoist-triage
  • "triage my todoist"
  • "clean up my todoist"
  • "find duplicate tasks in todoist"
  • "organize my todoist inbox"
  • "help me get unstuck" (EF-focused mode)
  • "what can I do right now" (quick wins mode)

Prerequisites

  • Todoist MCP: Must be configured for write operations
  • 1Password CLI: API token stored in item "Todoist", field "API Token"
  • Python 3: With thefuzz library for fuzzy matching (uv pip install thefuzz)

Overview

This skill operates in 5 phases:

  1. SYNC - Fetch data from Todoist Sync API into local SQLite
  2. ANALYZE - Detect issues: duplicates, stale tasks, sizing problems, SMART gaps
  3. TRIAGE - Interactive decision-making with EF-friendly coaching
  4. CONFIRM - Review summary of all planned changes
  5. APPLY - Execute changes via MCP tools, update memory

File Locations

skills/todoist-triage/
├── SKILL.md                   # This file
├── assets/
│   └── TRIAGE_MEMORY.md       # Template for user preferences
└── data/                      # Runtime data (gitignored)
    ├── todoist.db             # SQLite database
    └── sync_state.json        # Sync token and timestamps

Phase 1: SYNC

Step 1.1: Initialize Database

Create the SQLite database if it doesn't exist:

DB_PATH="skills/todoist-triage/data/todoist.db"
mkdir -p "skills/todoist-triage/data"

sqlite3 "$DB_PATH" << 'EOF'
CREATE TABLE IF NOT EXISTS sync_state (
    id INTEGER PRIMARY KEY CHECK (id = 1),
    sync_token TEXT NOT NULL DEFAULT '*',
    last_full_sync DATETIME,
    last_incremental_sync DATETIME
);
INSERT OR IGNORE INTO sync_state (id, sync_token) VALUES (1, '*');

CREATE TABLE IF NOT EXISTS projects (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    parent_id TEXT,
    is_inbox BOOLEAN DEFAULT 0,
    is_archived BOOLEAN DEFAULT 0,
    synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS items (
    id TEXT PRIMARY KEY,
    content TEXT NOT NULL,
    description TEXT,
    project_id TEXT,
    section_id TEXT,
    parent_id TEXT,
    labels TEXT,
    priority INTEGER DEFAULT 1,
    due_date TEXT,
    is_completed BOOLEAN DEFAULT 0,
    added_at DATETIME,
    content_normalized TEXT,
    content_hash TEXT,
    synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS triage_findings (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_id TEXT NOT NULL,
    finding_type TEXT NOT NULL,
    item_id TEXT,
    related_item_id TEXT,
    similarity_score REAL,
    details TEXT,
    suggested_action TEXT,
    user_decision TEXT,
    decided_at DATETIME,
    applied_at DATETIME
);

CREATE INDEX IF NOT EXISTS idx_items_content_hash ON items(content_hash);
CREATE INDEX IF NOT EXISTS idx_items_added ON items(added_at);
CREATE INDEX IF NOT EXISTS idx_findings_session ON triage_findings(session_id);
EOF

Step 1.2: Fetch Todoist API Token

TODOIST_TOKEN=$(op item get "Todoist" --fields "API Token" --reveal)

Step 1.3: Call Todoist Sync API

SYNC_TOKEN=$(sqlite3 "$DB_PATH" "SELECT sync_token FROM sync_state WHERE id = 1;")

RESPONSE=$(curl -s -X POST "https://api.todoist.com/sync/v9/sync" \
  -H "Authorization: Bearer $TODOIST_TOKEN" \
  -d "sync_token=$SYNC_TOKEN" \
  -d 'resource_types=["all"]')

echo "$RESPONSE" > "skills/todoist-triage/data/sync_response.json"

Phase 2: ANALYZE

Detect Exact Duplicates

SELECT i1.id, i2.id, i1.content
FROM items i1
JOIN items i2 ON i1.content_hash = i2.content_hash AND i1.id < i2.id
WHERE i1.is_completed = 0 AND i2.is_completed = 0;

Detect Fuzzy Duplicates

Use Python with thefuzz for similarity detection (threshold: 80%):

from thefuzz import fuzz

for t1 in tasks:
    for t2 in tasks:
        score = fuzz.ratio(t1['content'].lower(), t2['content'].lower())
        if score >= 80:
            # Found fuzzy duplicate

Detect Stale Tasks

  • No due date + age > 60 days
  • Overdue > 14 days
  • In Inbox > 7 days
SELECT * FROM items
WHERE is_completed = 0
  AND due_date IS NULL
  AND julianday('now') - julianday(added_at) > 60;

Task Sizing Analysis

Identify tasks that need breakdown:

  • Vague verbs: plan, organize, figure out, deal with
  • Multiple "and"s in task name
  • Very long task names (>12 words)

Quick wins:

  • Specific verbs: call, email, buy, schedule
  • Short task names (<6 words)

Phase 3: TRIAGE (Interactive)

Present Summary

AskUserQuestion(
  questions=[{
    "question": "Analysis complete! Found:\n* Q quick wins\n* X duplicates\n* Z stale tasks\n\nWhat to tackle?",
    "header": "Summary",
    "options": [
      {"label": "Quick wins", "description": "Build momentum"},
      {"label": "Duplicates", "description": "Clean up duplicates"},
      {"label": "Stale tasks", "description": "Review old tasks"},
      {"label": "Full triage", "description": "All categories"}
    ]
  }]
)

Handle Duplicates

AskUserQuestion(
  questions=[{
    "question": "Found X tasks with identical content. How to handle?",
    "header": "Duplicates",
    "options": [
      {"label": "Keep newest", "description": "Delete older duplicates"},
      {"label": "Keep oldest", "description": "Delete newer duplicates"},
      {"label": "Review each", "description": "Manual decision"}
    ]
  }]
)

Quick Wins (Momentum Building)

AskUserQuestion(
  questions=[{
    "question": "Here are 5 tasks you could finish in 15 minutes:\n1. Call dentist\n2. Reply to email\n3. Order supplies\n\nKnock some out now?",
    "header": "Quick Wins",
    "options": [
      {"label": "Show all", "description": "See full list"},
      {"label": "Batch by type", "description": "Group calls, emails"},
      {"label": "Start with #1", "description": "Let's do it"},
      {"label": "Skip", "description": "Move to other triage"}
    ]
  }]
)

Large Task Breakdown

AskUserQuestion(
  questions=[{
    "question": "'Plan vacation to Japan'\n\nThis looks like a project. What's the VERY NEXT physical action?",
    "header": "Break Down",
    "options": [
      {"label": "Research flights", "description": "Search dates/prices"},
      {"label": "Pick dates", "description": "Check calendar"},
      {"label": "Ask travel buddy", "description": "Coordinate"},
      {"label": "Help me think", "description": "Walk through it"}
    ]
  }]
)

Phase 4: CONFIRM

AskUserQuestion(
  questions=[{
    "question": "Ready to apply:\n* Delete: 8 duplicates\n* Complete: 3 stale\n* Update: 6 with due dates\n\nProceed?",
    "header": "Confirm",
    "options": [
      {"label": "Apply all", "description": "Execute changes"},
      {"label": "Dry run", "description": "Show what would happen"},
      {"label": "Review again", "description": "Go back"},
      {"label": "Cancel", "description": "Discard all"}
    ]
  }]
)

Phase 5: APPLY

Execute via MCP

# Complete tasks
mcp__todoist__complete-tasks(ids: ["id1", "id2", ...])

# Update tasks
mcp__todoist__update-tasks(tasks: [
  {id: "x", dueString: "tomorrow"},
  {id: "y", projectId: "new_project"}
])

# Create projects
mcp__todoist__add-projects(projects: [{name: "New Project"}])

Update TRIAGE_MEMORY.md

Record session results and learned patterns:

### Session 20260113_143022

**Changes applied:**
- Deleted 8 duplicates
- Completed 3 stale tasks
- Updated 6 tasks with due dates

**Patterns learned:**
- Tasks mentioning 'Sarah' -> Work project
- Tasks starting with 'Call' -> Add @phone label

Configuration (TRIAGE_MEMORY.md)

Copy assets/TRIAGE_MEMORY.md to data/TRIAGE_MEMORY.md and customize:

stale_thresholds:
  no_due_date_days: 60
  overdue_days: 14
  inbox_days: 7

excluded_projects:
  - "Someday/Maybe"
  - "Reference"

duplicate_default: "keep_newest"
similarity_threshold: 80

Error Handling

ErrorCauseRecovery
1Password 401Session expiredRun op signin
Todoist API 401Token invalidCheck 1Password item
Todoist API 429Rate limitWait 60s, retry
MCP 403Project limit (300)Suggest sub-project

Tools Used

  • Bash: Database init, API calls
  • Python: Fuzzy matching, analysis
  • SQLite: Local data storage
  • AskUserQuestion: Interactive decisions
  • mcp__todoist__*: Task operations