Skip to main content
Glama

suggest-task-placement

Classify new tasks into existing project headings by analyzing task titles and project structure to maintain organization in Things 3.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_uuidYesUUID of the project whose existing headings should be reused
task_titlesYesTask titles to classify into the project's existing headings

Implementation Reference

  • The main logic for suggesting task placement based on semantic matching between task titles and existing headings/tasks in a project.
    export function suggestTaskPlacement(input: {
      projectTitle: string;
      headings: TaskLike[];
      todos: TaskLike[];
      taskTitles: string[];
    }) {
      const corpora = buildHeadingCorpus(input.headings, input.todos);
    
      const placements = input.taskTitles.map((taskTitle) => {
        const taskTokens = tokenize(taskTitle);
        const scored = corpora
          .map((heading) => {
            let score = 0;
            const matches: string[] = [];
    
            for (const token of taskTokens) {
              if (heading.tokens.has(token)) {
                score += 3;
                matches.push(token);
              }
            }
    
            const normalizedHeading = normalizeHeadingName(heading.headingTitle);
            if (taskTitle.toLowerCase().includes(normalizedHeading)) {
              score += 4;
              matches.push(heading.headingTitle);
            }
    
            if (heading.ownTodoCount > 0 && score > 0) {
              score += 1;
            }
    
            return {
              headingId: heading.headingId,
              headingTitle: heading.headingTitle,
              score,
              matches: [...new Set(matches)],
            };
          })
          .sort((a, b) => b.score - a.score || a.headingTitle.localeCompare(b.headingTitle));
    
        const best = scored[0];
        const second = scored[1];
        const ambiguous = Boolean(best && second && best.score > 0 && best.score === second.score);
        const confident = Boolean(best && best.score >= 3 && !ambiguous);
    
        return {
          taskTitle,
          suggestedHeadingId: confident ? best.headingId : null,
          suggestedHeadingTitle: confident ? best.headingTitle : null,
          confidence: confident ? (best.score >= 6 ? "high" : "medium") : "low",
          ambiguous,
          reason: confident
            ? `Coincide mejor con ${best.headingTitle}${best.matches.length ? ` por: ${best.matches.join(", ")}` : ""}.`
            : ambiguous
              ? "La tarea podría encajar en más de un heading existente."
              : "No hay una coincidencia semántica suficientemente clara con los headings existentes.",
          alternatives: scored
            .filter((entry) => entry.score > 0)
            .slice(0, 3)
            .map(({ headingId, headingTitle, score }) => ({ headingId, headingTitle, score })),
        };
      });
    
      return {
        projectTitle: input.projectTitle,
        headings: corpora.map(({ headingId, headingTitle, ownTodoCount }) => ({
          headingId,
          headingTitle,
          ownTodoCount,
        })),
        placements,
        summary: {
          totalTasks: placements.length,
          confidentlyPlaced: placements.filter((entry) => entry.suggestedHeadingId).length,
          needsReview: placements.filter((entry) => !entry.suggestedHeadingId).length,
        },
        guidance:
          "Usa estas sugerencias para reutilizar los headings existentes del proyecto. Si una tarea queda con confianza baja o ambigua, conviene confirmar con el usuario antes de moverla.",
      };
    }
  • src/index.ts:1588-1609 (registration)
    Registration of the suggest-task-placement tool, which orchestrates the call to suggestTaskPlacement in src/task-placement.ts.
      "suggest-task-placement",
      {
        project_uuid: z.string().describe("UUID of the project whose existing headings should be reused"),
        task_titles: z.array(z.string()).min(1).describe("Task titles to classify into the project's existing headings"),
      },
      async ({ project_uuid, task_titles }) => {
        const placement = await withDatabase((db) => {
          const structure = buildProjectStructure(getAllTasks(db), project_uuid);
          return suggestTaskPlacement({
            projectTitle: structure.project.title,
            headings: structure.headings,
            todos: structure.todos,
            taskTitles: task_titles,
          });
        });
    
        return buildTextResponse(
          `Suggested placement for ${placement.summary.totalTasks} tasks in ${placement.projectTitle}`,
          placement
        );
      }
    );

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