Skip to main content
Glama

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
NameRequiredDescriptionDefault
project_uuidYesUUID of the project to summarize

Implementation Reference

  • 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);
      }
    );
  • 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(" "),
      };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/soycanopa/SupaThings-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server