9eddb8497c
Direction change (operator, see plan task #755 work-log): the plugin must NOT depend on disabling a native Claude function to work. It earns its place by steering behavior, not by toggling autoMemoryEnabled. Memory doctrine (no dual-write): - using-scribe SKILL.md gains "Scribe holds these functions — don't keep a second copy": route rules/recall/planning to Scribe, don't also write them to native auto-memory, never instruct disabling a native function, and accept a "Scribe-shaped hole" if the plugin is removed (recover over time). - mcp/server.py _INSTRUCTIONS: drop the paragraph that told the model to create/refresh a "rules live in Scribe" pointer in CLAUDE.md / ~/.claude memory. That was an active dual-write instruction; the SessionStart hook is the bridge now. Replaced with the no-dual-write / no-settings-dependency doctrine. Supersedes plan #755 Phase 6 ("set autoMemoryEnabled:false"). Project-scope discipline (stop cross-project bleed): - using-scribe SKILL.md gains "Stay inside the active project's scope": pass project_id to every read, only reference/offer work on the in-scope project, ask before switching. - _INSTRUCTIONS scope bullet extended from reads to referencing/offering, and flags get_recent as cross-project. - get_recent docstring gains a scope note steering to scoped list_* when a project is active. plugin.json 0.1.4 -> 0.1.5 so clients' caches actually refresh (re-shipping under the same version does not bust the cache). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
"""get_recent — cross-type recent-activity tool.
|
|
|
|
Returns the most-recently-touched notes, tasks, projects, and events 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 three 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.event import Event
|
|
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, projects, events.
|
|
|
|
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(),
|
|
})
|
|
events = (await session.execute(
|
|
select(Event).where(Event.user_id == uid,
|
|
Event.updated_at >= since,
|
|
Event.deleted_at.is_(None))
|
|
.order_by(Event.updated_at.desc()).limit(limit)
|
|
)).scalars().all()
|
|
for e in events:
|
|
items.append({
|
|
"id": e.id,
|
|
"type": "event",
|
|
"title": e.title,
|
|
"updated_at": e.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)
|