From f1935818056b3c66392e2ad278eabef9214e51d2 Mon Sep 17 00:00:00 2001 From: Christian Gick Date: Thu, 8 Jan 2026 23:45:15 +0200 Subject: [PATCH] feat: Make relates_to and duplicates links bidirectional - relates_to and duplicates now create reverse links automatically - task_show displays Related and Duplicates sections - blocks remains unidirectional (A blocks B, B is blocked by A) Co-Authored-By: Claude Opus 4.5 --- src/tools/crud.ts | 30 ++++++++++++++++++++++++++++++ src/tools/relations.ts | 14 ++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/tools/crud.ts b/src/tools/crud.ts index 39d1057..b7ffb9d 100644 --- a/src/tools/crud.ts +++ b/src/tools/crud.ts @@ -195,6 +195,36 @@ export async function taskShow(id: string): Promise { } } + // Get related tasks (bidirectional - only need to query one direction since links are symmetric) + const relatesTo = await query<{ id: string; title: string }>( + `SELECT t.id, t.title FROM task_links l + JOIN tasks t ON t.id = l.to_task_id + WHERE l.from_task_id = $1 AND l.link_type = 'relates_to'`, + [id] + ); + + if (relatesTo.length > 0) { + output += `\n**Related:**\n`; + for (const t of relatesTo) { + output += ` - ${t.id}: ${t.title}\n`; + } + } + + // Get duplicates (bidirectional) + const duplicates = await query<{ id: string; title: string }>( + `SELECT t.id, t.title FROM task_links l + JOIN tasks t ON t.id = l.to_task_id + WHERE l.from_task_id = $1 AND l.link_type = 'duplicates'`, + [id] + ); + + if (duplicates.length > 0) { + output += `\n**Duplicates:**\n`; + for (const t of duplicates) { + output += ` - ${t.id}: ${t.title}\n`; + } + } + return output; } diff --git a/src/tools/relations.ts b/src/tools/relations.ts index 9fcb957..ab60723 100644 --- a/src/tools/relations.ts +++ b/src/tools/relations.ts @@ -20,11 +20,15 @@ interface ChecklistToggleArgs { /** * Create a dependency between tasks + * - blocks: unidirectional (A blocks B) + * - relates_to: bidirectional (A relates to B = B relates to A) + * - duplicates: bidirectional (A duplicates B = B duplicates A) */ export async function taskLink(args: TaskLinkArgs): Promise { const { from_id, to_id, link_type } = args; try { + // Create the primary link await execute( `INSERT INTO task_links (from_task_id, to_task_id, link_type) VALUES ($1, $2, $3) @@ -32,6 +36,16 @@ export async function taskLink(args: TaskLinkArgs): Promise { [from_id, to_id, link_type] ); + // For symmetric relationships, create reverse link + if (link_type === 'relates_to' || link_type === 'duplicates') { + await execute( + `INSERT INTO task_links (from_task_id, to_task_id, link_type) + VALUES ($1, $2, $3) + ON CONFLICT (from_task_id, to_task_id, link_type) DO NOTHING`, + [to_id, from_id, link_type] + ); + } + return `Linked: ${from_id} ${link_type} ${to_id}`; } catch (error) { return `Error creating link: ${error}`;