claimTask.ts•4.73 kB
import {
isClaimable,
TrellisObject,
TrellisObjectStatus,
TrellisObjectType,
} from "../../models";
import { Repository } from "../../repositories";
import { checkPrerequisitesComplete } from "../../utils/checkPrerequisitesComplete";
import { checkHierarchicalPrerequisitesComplete } from "../../utils/checkHierarchicalPrerequisitesComplete";
import { filterUnavailableObjects } from "../../utils/filterUnavailableObjects";
import { sortTrellisObjects } from "../../utils/sortTrellisObjects";
import { updateParentHierarchy } from "../../utils/updateParentHierarchy";
export async function claimTask(
repository: Repository,
scope?: string,
taskId?: string,
force: boolean = false,
): Promise<{ content: Array<{ type: string; text: string }> }> {
try {
let claimedTask: TrellisObject;
if (taskId) {
claimedTask = await claimSpecificTask(taskId, force, repository);
} else {
claimedTask = await findNextAvailableTask(scope, repository);
}
const updatedTask = await updateTaskStatus(claimedTask, repository);
return {
content: [
{
type: "text",
text: `Successfully claimed task: ${JSON.stringify(
{
...updatedTask,
affectedFiles: Object.fromEntries(updatedTask.affectedFiles),
},
null,
2,
)}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error claiming task: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
async function claimSpecificTask(
taskId: string,
force: boolean,
repository: Repository,
): Promise<TrellisObject> {
const object = await repository.getObjectById(taskId);
if (!object) {
throw new Error(`Task with ID "${taskId}" not found`);
}
await validateTaskForClaiming(object, taskId, force, repository);
return object;
}
async function findNextAvailableTask(
scope: string | undefined,
repository: Repository,
): Promise<TrellisObject> {
const objects = await repository.getObjects(
false, // includeClosed
scope,
TrellisObjectType.TASK,
);
// Filter to get only available tasks
const availableTasks = await filterUnavailableObjects(objects, repository);
if (availableTasks.length === 0) {
throw new Error("No available tasks to claim");
}
// Sort by priority and return the top one
const sortedTasks = sortTrellisObjects(availableTasks);
return sortedTasks[0];
}
async function updateTaskStatus(
task: TrellisObject,
repository: Repository,
): Promise<TrellisObject> {
const updatedTask = {
...task,
status: TrellisObjectStatus.IN_PROGRESS,
};
await repository.saveObject(updatedTask);
// Update parent hierarchy to in-progress (don't let errors fail the task claim)
try {
await updateParentHierarchy(task.parent, repository);
} catch (error) {
// Log but don't propagate parent hierarchy update errors
console.warn("Failed to update parent hierarchy:", error);
}
// Re-read the object from repository to ensure proper Map reconstruction
// This is needed because the in-memory object may not have the Map properly reconstructed
const savedTask = await repository.getObjectById(task.id);
if (!savedTask) {
throw new Error(`Failed to retrieve saved task with ID "${task.id}"`);
}
return savedTask;
}
async function validateTaskForClaiming(
task: TrellisObject,
taskId: string,
force: boolean,
repository: Repository,
): Promise<void> {
// Validate it's a task
if (task.type !== TrellisObjectType.TASK) {
throw new Error(
`Object with ID "${taskId}" is not a task (type: ${task.type})`,
);
}
if (!force) {
// Validate status is draft or open
if (!isClaimable(task)) {
throw new Error(
`Task "${taskId}" cannot be claimed (status: ${task.status}). Task must be in draft or open status.`,
);
}
// Validate all prerequisites are complete (including hierarchical)
const prerequisitesComplete = await checkHierarchicalPrerequisitesComplete(
task,
repository,
);
if (!prerequisitesComplete) {
// Determine if it's the task's own prerequisites or a parent's
const ownPrerequisitesComplete = await checkPrerequisitesComplete(
task,
repository,
);
if (ownPrerequisitesComplete) {
throw new Error(
`Task "${taskId}" cannot be claimed. Parent hierarchy has incomplete prerequisites.`,
);
} else {
throw new Error(
`Task "${taskId}" cannot be claimed. Not all prerequisites are complete.`,
);
}
}
}
}