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 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-08 23:45:15 +02:00
parent a03e9e065a
commit f193581805
2 changed files with 44 additions and 0 deletions

View File

@@ -195,6 +195,36 @@ export async function taskShow(id: string): Promise<string> {
}
}
// 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;
}

View File

@@ -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<string> {
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<string> {
[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}`;