Files
FabledScribe/src/scribe/mcp/tools/recent.py
T
bvandeusen b49efdcb11
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 30s
CI & Build / Python tests (push) Failing after 31s
CI & Build / Build & push image (push) Has been skipped
refactor(scribe): retire calendar/events + person/place/list entities (backend)
Narrow Scribe to a Claude-Code work system-of-record (milestone #194,
decision note #1759). Wholesale removal per rule #22 — backend + schema half.

Calendar/events + CalDAV: delete models/event, services/{events,caldav,
caldav_sync}, routes/events, mcp/tools/events; strip event branches from
backup (bump v3->v4), dashboard (upcoming_events), trash, recent, and the
mcp server read-only allowlist + instructions.

Typed entities (person/place/list): delete mcp/tools/entities; drop the
notes.metadata (entity_meta) column from model/service/routes and the
knowledge browse service. note_type STAYS — it also marks 'process' notes.

Scheduler: event_scheduler -> recurrence_scheduler, keeping only the
recurring-task spawn job (drops event reminders + CalDAV sync).

Schema: migration 0069 drops the events table + notes.metadata column +
orphan caldav settings rows (faithful downgrade recreates them).

KEEP: recurrence.py (task recurrence), notifications task reminders, graph
view, and every work surface. Frontend + plugin/docs true-up follow next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPtbSzA4JLMAKgFZ8VTg7Q
2026-07-19 13:29:14 -04:00

75 lines
2.7 KiB
Python

"""get_recent — cross-type recent-activity tool.
Returns the most-recently-touched notes, tasks, and projects for the user,
ordered by updated_at descending. Useful for Claude to bootstrap context at
the start of a conversation ("what was I working on?").
Aggregation is Python-side after two small per-table queries — simpler than
a UNION ALL with type-discriminating columns, and fine for personal-scale data.
"""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from scribe.mcp._context import current_user_id
from scribe.models import async_session
from scribe.models.note import Note
from scribe.models.project import Project
async def get_recent(days: int = 7, limit: int = 25) -> dict:
"""Return recently-touched items across notes, tasks, and projects.
Args:
days: Look-back window in days (1-90).
limit: Maximum number of items returned (1-100).
Returns:
{"items": [{"id", "type", "title", "updated_at"}], "total": int}
Sorted by updated_at descending.
Scope note: this spans ALL projects and takes no project filter. When a
project is in scope, prefer list_tasks(project_id=...) /
list_notes(project_id=...) so you don't surface other projects' activity.
"""
uid = current_user_id()
days = max(1, min(days, 90))
limit = max(1, min(limit, 100))
since = datetime.now(timezone.utc) - timedelta(days=days)
items: list[dict] = []
async with async_session() as session:
notes = (await session.execute(
select(Note).where(Note.user_id == uid, Note.updated_at >= since,
Note.deleted_at.is_(None))
.order_by(Note.updated_at.desc()).limit(limit)
)).scalars().all()
for n in notes:
items.append({
"id": n.id,
"type": "task" if n.is_task else "note",
"title": n.title,
"updated_at": n.updated_at.isoformat(),
})
projects = (await session.execute(
select(Project).where(Project.user_id == uid,
Project.updated_at >= since,
Project.deleted_at.is_(None))
.order_by(Project.updated_at.desc()).limit(limit)
)).scalars().all()
for p in projects:
items.append({
"id": p.id,
"type": "project",
"title": p.title,
"updated_at": p.updated_at.isoformat(),
})
items.sort(key=lambda r: r["updated_at"], reverse=True)
items = items[:limit]
return {"items": items, "total": len(items)}
def register(mcp) -> None:
mcp.tool(name="get_recent")(get_recent)