summarize-project
Generate concise summaries of Things 3 projects by analyzing task structure and content to provide quick overviews of project scope and status.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_uuid | Yes | UUID of the project to summarize |
Implementation Reference
- src/index.ts:1612-1623 (handler)Registration of the 'summarize-project' tool in src/index.ts.
"summarize-project", { project_uuid: z.string().describe("UUID of the project to summarize"), }, async ({ project_uuid }) => { const summary = await withDatabase((db) => summarizeProject(getAllTasks(db), project_uuid) ); return buildTextResponse(`Summarized project ${summary.project.title}`, summary); } ); - src/project-summary.ts:112-222 (handler)The core implementation of the project summarization logic in src/project-summary.ts.
export function summarizeProject(tasks: TaskLike[], projectUuid: string) { const today = todayDateOnly(); const structure = buildProjectStructure(tasks, projectUuid); const headingDistribution = headingStatsFor(structure); const sortedByLoad = [...headingDistribution].sort( (a, b) => b.todoCount - a.todoCount || a.headingTitle.localeCompare(b.headingTitle) ); const busiestHeading = sortedByLoad.find((entry) => entry.todoCount > 0) ?? null; const emptiestHeadings = headingDistribution.filter((entry) => entry.todoCount === 0); const completedTodos = structure.todos.filter((todo) => todo.status === "completed").length; const canceledTodos = structure.todos.filter((todo) => todo.status === "canceled").length; const incompleteTodos = structure.todos.filter((todo) => todo.status === "incomplete").length; const overdue = overdueTasks(structure.todos, today); const upcoming = upcomingTasks(structure.todos, today); const planningPriority = inferPlanningPriority({ overdueCount: overdue.length, withoutHeadingCount: structure.todosWithoutHeading.length, emptyHeadingCount: emptiestHeadings.length, incompleteCount: incompleteTodos, }); const observations: string[] = []; if (busiestHeading) { observations.push( `El heading con más carga actual es ${busiestHeading.headingTitle} (${busiestHeading.todoCount} tareas, ${busiestHeading.incomplete} incompletas).` ); } if (emptiestHeadings.length > 0) { observations.push( `Hay ${emptiestHeadings.length} headings sin tareas: ${emptiestHeadings.map((entry) => entry.headingTitle).join(", ")}.` ); } if (structure.todosWithoutHeading.length > 0) { observations.push(`Hay ${structure.todosWithoutHeading.length} tareas sin heading asignado.`); } if (overdue.length > 0) { observations.push(`Hay ${overdue.length} tareas vencidas que requieren atención.`); } if (upcoming.length > 0) { observations.push(`Hay ${upcoming.length} tareas con fecha próxima o activa para revisar.`); } if (observations.length === 0) { observations.push("La estructura del proyecto se ve balanceada y sin huecos obvios."); } const nextActions: string[] = []; if (overdue.length > 0) { nextActions.push("Atender o reprogramar primero las tareas vencidas."); } if (structure.todosWithoutHeading.length > 0) { nextActions.push("Ubicar las tareas sin heading para mantener la estructura consistente."); } if (emptiestHeadings.length > 0) { nextActions.push("Revisar si los headings vacíos siguen siendo útiles o si conviene poblarlos con próximas tareas."); } if (upcoming.length > 0) { nextActions.push("Confirmar las próximas tareas activas para que reflejen la prioridad real del proyecto."); } if (!nextActions.length) { nextActions.push("Usar la estructura actual del proyecto como base para seguir agregando tareas nuevas."); } const planningSignals = { planningPriority, hasOverdueWork: overdue.length > 0, hasUnassignedTasks: structure.todosWithoutHeading.length > 0, hasEmptyHeadings: emptiestHeadings.length > 0, isStructureBalanced: overdue.length === 0 && structure.todosWithoutHeading.length === 0 && emptiestHeadings.length === 0, }; return { project: structure.compact.project, counts: { headings: structure.headings.length, todos: structure.todos.length, incompleteTodos, completedTodos, canceledTodos, todosWithoutHeading: structure.todosWithoutHeading.length, overdueTodos: overdue.length, upcomingTodos: upcoming.length, }, headingDistribution, busiestHeading, emptyHeadings: emptiestHeadings, overdue, upcoming, planningSignals, observations, nextActions, summaryText: [ `Proyecto: ${structure.project.title}.`, `Tiene ${structure.headings.length} headings y ${structure.todos.length} tareas.`, structure.todosWithoutHeading.length > 0 ? `${structure.todosWithoutHeading.length} tareas siguen sin heading.` : "No hay tareas sin heading.", overdue.length > 0 ? `Hay ${overdue.length} tareas vencidas.` : "No hay tareas vencidas.", busiestHeading ? `La mayor concentración de tareas está en ${busiestHeading.headingTitle}.` : "Todavía no hay un heading dominante.", `Prioridad de planificación: ${planningPriority}.`, ].join(" "), }; }