life-manager/codebase/features/projects/backend/projects-tools.provider.ts
2026-03-01 01:35:26 -08:00

241 lines
8.4 KiB
TypeScript

import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
import { ToolRegistryService } from '@features/assistant/backend/skills/tool-registry.service';
import { SprintsService } from './sprints.service';
import { TasksService } from '@features/tasks/backend/tasks.service';
@Injectable()
export class ProjectsToolsProvider implements OnApplicationBootstrap {
constructor(
private readonly toolRegistry: ToolRegistryService,
private readonly sprintsService: SprintsService,
private readonly tasksService: TasksService,
) {}
onApplicationBootstrap(): void {
this.toolRegistry.register({
name: 'create_sprint',
description: 'Create a new sprint in a project with start/end dates and optional goal',
parameters: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'Project UUID' },
name: { type: 'string', description: 'Sprint name' },
startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
goal: { type: 'string', description: 'Sprint goal (optional)' },
},
required: ['projectId', 'name', 'startDate', 'endDate'],
},
handler: async (params) => {
try {
const sprint = await this.sprintsService.createSprint({
projectId: params.projectId as string,
name: params.name as string,
startDate: params.startDate as string,
endDate: params.endDate as string,
goal: params.goal as string | undefined,
});
return {
ok: true,
output: `Created sprint "${sprint.name}" (id: ${sprint.id}), status: ${sprint.status}`,
};
} catch (error) {
return {
ok: false,
error: `Failed to create sprint: ${error instanceof Error ? error.message : error}`,
};
}
},
always: false,
});
this.toolRegistry.register({
name: 'list_sprints',
description: 'List sprints, optionally filtered by domain or status',
parameters: {
type: 'object',
properties: {
domainId: { type: 'string', description: 'Filter by domain UUID' },
status: {
type: 'string',
description: 'Filter by status: planned, active, completed, cancelled',
},
limit: { type: 'integer', description: 'Max results (default 10)' },
},
},
handler: async (params) => {
try {
const result = await this.sprintsService.findAll({
domainId: params.domainId as string | undefined,
status: params.status as string | undefined,
limit: (params.limit as number) ?? 10,
page: 1,
} as Record<string, unknown>);
if (result.data.length === 0) {
return { ok: true, output: 'No sprints found matching the filters.' };
}
const summary = result.data
.map(
(s) =>
`- [${s.status}] ${s.name} (${s.startDate}${s.endDate})${s.goal ? ` — Goal: ${s.goal}` : ''}`,
)
.join('\n');
return {
ok: true,
output: `Found ${result.meta.total} sprints (showing ${result.data.length}):\n${summary}`,
};
} catch (error) {
return {
ok: false,
error: `Failed to list sprints: ${error instanceof Error ? error.message : error}`,
};
}
},
always: false,
});
this.toolRegistry.register({
name: 'get_sprint_tasks',
description: 'Get a sprint with all its tasks grouped by status',
parameters: {
type: 'object',
properties: {
sprintId: { type: 'string', description: 'Sprint UUID' },
},
required: ['sprintId'],
},
handler: async (params) => {
try {
const sprint = await this.sprintsService.findOneWithTasks(params.sprintId as string);
const grouped: Record<string, string[]> = {};
for (const t of sprint.tasks) {
if (!grouped[t.status]) grouped[t.status] = [];
grouped[t.status].push(`[${t.priority}] ${t.title}`);
}
const sections = Object.entries(grouped)
.map(
([status, tasks]) =>
`${status.toUpperCase()} (${tasks.length}):\n${tasks.map((t) => ` - ${t}`).join('\n')}`,
)
.join('\n\n');
const header = [
`Sprint: ${sprint.name} [${sprint.status}]`,
`Period: ${sprint.startDate}${sprint.endDate}`,
sprint.goal ? `Goal: ${sprint.goal}` : null,
`Total tasks: ${sprint.tasks.length}`,
]
.filter(Boolean)
.join('\n');
return {
ok: true,
output:
sprint.tasks.length > 0
? `${header}\n\n${sections}`
: `${header}\n\nNo tasks assigned to this sprint.`,
};
} catch (error) {
return {
ok: false,
error: `Failed to get sprint tasks: ${error instanceof Error ? error.message : error}`,
};
}
},
always: false,
});
this.toolRegistry.register({
name: 'update_sprint',
description: "Update a sprint's status, goal, or retrospective",
parameters: {
type: 'object',
properties: {
sprintId: { type: 'string', description: 'Sprint UUID' },
status: {
type: 'string',
description: 'New status: planned, active, completed, cancelled',
},
goal: { type: 'string', description: 'Updated sprint goal' },
retrospective: { type: 'string', description: 'Sprint retrospective notes' },
name: { type: 'string', description: 'Rename the sprint' },
},
required: ['sprintId'],
},
handler: async (params) => {
try {
const dto: Record<string, unknown> = {};
if (params.status) dto['status'] = params.status;
if (params.goal) dto['goal'] = params.goal;
if (params.retrospective) dto['retrospective'] = params.retrospective;
if (params.name) dto['name'] = params.name;
const sprint = await this.sprintsService.updateSprint(
params.sprintId as string,
dto,
);
return {
ok: true,
output: `Updated sprint "${sprint.name}" — status: ${sprint.status}`,
};
} catch (error) {
return {
ok: false,
error: `Failed to update sprint: ${error instanceof Error ? error.message : error}`,
};
}
},
always: false,
});
this.toolRegistry.register({
name: 'assign_task_to_sprint',
description: 'Assign an existing task to a sprint',
parameters: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task UUID' },
sprintId: { type: 'string', description: 'Sprint UUID' },
},
required: ['taskId', 'sprintId'],
},
handler: async (params) => {
try {
const task = await this.tasksService.updateTask(params.taskId as string, {
sprintId: params.sprintId as string,
});
return { ok: true, output: `Assigned task "${task.title}" to sprint` };
} catch (error) {
return {
ok: false,
error: `Failed to assign task: ${error instanceof Error ? error.message : error}`,
};
}
},
always: false,
});
this.toolRegistry.register({
name: 'remove_task_from_sprint',
description: 'Remove a task from its sprint (unassign)',
parameters: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task UUID' },
},
required: ['taskId'],
},
handler: async (params) => {
try {
const task = await this.tasksService.updateTask(params.taskId as string, {
sprintId: null,
});
return { ok: true, output: `Removed task "${task.title}" from sprint` };
} catch (error) {
return {
ok: false,
error: `Failed to remove task from sprint: ${error instanceof Error ? error.message : error}`,
};
}
},
always: false,
});
}
}