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:
- •One thing at a time - Each question focuses on one decision
- •Momentum first - Start sessions by identifying quick wins
- •Break it down - Large vague tasks become concrete action steps
- •Raise the bar gently - Suggest improvements without judgment
- •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
thefuzzlibrary for fuzzy matching (uv pip install thefuzz)
Overview
This skill operates in 5 phases:
- •SYNC - Fetch data from Todoist Sync API into local SQLite
- •ANALYZE - Detect issues: duplicates, stale tasks, sizing problems, SMART gaps
- •TRIAGE - Interactive decision-making with EF-friendly coaching
- •CONFIRM - Review summary of all planned changes
- •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
| Error | Cause | Recovery |
|---|---|---|
| 1Password 401 | Session expired | Run op signin |
| Todoist API 401 | Token invalid | Check 1Password item |
| Todoist API 429 | Rate limit | Wait 60s, retry |
| MCP 403 | Project 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