feat: add getTaskComment functionality and enhance pre-commit script

- Introduced a new function `getTaskComment` to retrieve a specific comment from a task, including relevant task details.
- Updated the task store to support direct file reads for tasks that match canonical UUIDs.
- Added a new server tool for fetching task comments, enhancing the API capabilities.
- Modified the pre-commit script to improve environment setup and ensure lint-staged runs correctly.
This commit is contained in:
iliya 2026-03-20 13:45:14 +02:00
parent ec547e0662
commit b41cf9fad2
6 changed files with 69 additions and 1 deletions

15
.husky/pre-commit Normal file → Executable file
View file

@ -1 +1,14 @@
pnpm exec lint-staged #!/usr/bin/env sh
cd "$(dirname "$0")/.." || exit 1
# Git/IDE hooks often get a minimal PATH — Node lives outside it.
export PATH="$HOME/.local/share/mise/shims:$HOME/.volta/bin:/opt/homebrew/bin:/usr/local/bin:$HOME/.linuxbrew/bin:$PATH"
if [ -z "${NODE_SKIP_NVM:-}" ] && [ -f "$HOME/.nvm/nvm.sh" ]; then
NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
export NVM_DIR
# shellcheck source=/dev/null
. "$HOME/.nvm/nvm.sh"
fi
exec ./node_modules/.bin/lint-staged

View file

@ -117,6 +117,17 @@ function resolveTaskRef(paths, taskRef, options = {}) {
} }
const includeDeleted = options.includeDeleted === true; const includeDeleted = options.includeDeleted === true;
// Fast path: if taskRef looks like a canonical UUID, try direct file read first
if (looksLikeCanonicalTaskId(normalizedRef)) {
const taskPath = getTaskPath(paths, normalizedRef);
const rawTask = readJson(taskPath, null);
if (rawTask && (includeDeleted || rawTask.status !== 'deleted')) {
return normalizedRef;
}
}
// Fallback: scan all tasks for displayId match or non-UUID refs
const tasks = listRawTasks(paths); const tasks = listRawTasks(paths);
const exact = tasks.find((task) => task.id === normalizedRef); const exact = tasks.find((task) => task.id === normalizedRef);
if (exact && (includeDeleted || exact.status !== 'deleted')) { if (exact && (includeDeleted || exact.status !== 'deleted')) {

View file

@ -165,6 +165,30 @@ function getTask(context, taskId) {
return taskStore.readTask(context.paths, taskId, { includeDeleted: true }); return taskStore.readTask(context.paths, taskId, { includeDeleted: true });
} }
function getTaskComment(context, taskId, commentId) {
const normalizedCommentId = String(commentId || '').trim();
if (!normalizedCommentId) {
throw new Error('Missing commentId');
}
const task = taskStore.readTask(context.paths, taskId, { includeDeleted: true });
const comments = Array.isArray(task.comments) ? task.comments : [];
const comment = comments.find((c) => c && c.id === normalizedCommentId);
if (!comment) {
throw new Error(`Comment ${normalizedCommentId} not found on task #${task.displayId || task.id}`);
}
return {
comment,
task: {
id: task.id,
displayId: task.displayId,
subject: task.subject,
status: task.status,
owner: task.owner,
commentCount: comments.length,
},
};
}
function listTasks(context) { function listTasks(context) {
return taskStore.listTasks(context.paths); return taskStore.listTasks(context.paths);
} }
@ -609,6 +633,7 @@ module.exports = {
completeTask, completeTask,
createTask, createTask,
getTask, getTask,
getTaskComment,
linkTask, linkTask,
listDeletedTasks, listDeletedTasks,
listTasks, listTasks,

View file

@ -7,6 +7,7 @@ declare module 'agent-teams-controller' {
export interface ControllerTaskApi { export interface ControllerTaskApi {
createTask(flags: Record<string, unknown>): unknown; createTask(flags: Record<string, unknown>): unknown;
getTask(taskId: string): unknown; getTask(taskId: string): unknown;
getTaskComment(taskId: string, commentId: string): { comment: Record<string, unknown>; task: { id: string; displayId: string; subject: string; status: string; owner: string | null; commentCount: number } };
listTasks(): unknown[]; listTasks(): unknown[];
listDeletedTasks(): unknown[]; listDeletedTasks(): unknown[];
resolveTaskId(taskRef: string): string; resolveTaskId(taskRef: string): string;

View file

@ -226,6 +226,23 @@ export function registerTaskTools(server: Pick<FastMCP, 'addTool'>) {
await Promise.resolve(jsonTextContent(getController(teamName, claudeDir).tasks.getTask(taskId))), await Promise.resolve(jsonTextContent(getController(teamName, claudeDir).tasks.getTask(taskId))),
}); });
server.addTool({
name: 'task_get_comment',
description:
'Get a single task comment by id. Returns the comment object and minimal task context (id, displayId, subject, status, owner).',
parameters: z.object({
...toolContextSchema,
taskId: z.string().min(1),
commentId: z.string().min(1),
}),
execute: async ({ teamName, claudeDir, taskId, commentId }) =>
await Promise.resolve(
jsonTextContent(
getController(teamName, claudeDir).tasks.getTaskComment(taskId, commentId)
)
),
});
server.addTool({ server.addTool({
name: 'task_list', name: 'task_list',
description: 'List tasks for a team', description: 'List tasks for a team',

View file

@ -58,6 +58,7 @@ describe('agent-teams-mcp tools', () => {
'task_create', 'task_create',
'task_create_from_message', 'task_create_from_message',
'task_get', 'task_get',
'task_get_comment',
'task_link', 'task_link',
'task_list', 'task_list',
'task_set_clarification', 'task_set_clarification',