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:
@@ -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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ interface ChecklistToggleArgs {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a dependency between tasks
|
* 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> {
|
export async function taskLink(args: TaskLinkArgs): Promise<string> {
|
||||||
const { from_id, to_id, link_type } = args;
|
const { from_id, to_id, link_type } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Create the primary link
|
||||||
await execute(
|
await execute(
|
||||||
`INSERT INTO task_links (from_task_id, to_task_id, link_type)
|
`INSERT INTO task_links (from_task_id, to_task_id, link_type)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
@@ -32,6 +36,16 @@ export async function taskLink(args: TaskLinkArgs): Promise<string> {
|
|||||||
[from_id, to_id, link_type]
|
[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}`;
|
return `Linked: ${from_id} ${link_type} ${to_id}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return `Error creating link: ${error}`;
|
return `Error creating link: ${error}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user