Skip to main content
Glama

Task Trellis MCP

prerequisites.e2e.test.ts23 kB
import { McpTestClient, TestEnvironment } from "../utils"; describe("E2E Workflow - Prerequisites", () => { let testEnv: TestEnvironment; let client: McpTestClient; beforeEach(async () => { testEnv = new TestEnvironment(); testEnv.setup(); client = new McpTestClient(testEnv.projectRoot); await client.connect(); await client.callTool("activate", { mode: "local", projectRoot: testEnv.projectRoot, }); }, 30000); afterEach(async () => { await client?.disconnect(); testEnv?.cleanup(); }); describe("Simple Prerequisite Chains", () => { it("should handle linear dependency chain A -> B -> C", async () => { // Create tasks A, B, C with dependencies const taskA = await client.callTool("create_issue", { type: "task", title: "Task A - Foundation", status: "open", }); const taskAId = taskA.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const taskB = await client.callTool("create_issue", { type: "task", title: "Task B - Middle", status: "open", prerequisites: [taskAId], }); const taskBId = taskB.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const taskC = await client.callTool("create_issue", { type: "task", title: "Task C - Final", status: "open", prerequisites: [taskBId], }); const taskCId = taskC.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Try to claim C (should fail) let result = await client.callTool("claim_task", { taskId: taskCId }); expect(result.content[0].text).toContain("Not all prerequisites"); // Try to claim B (should fail) result = await client.callTool("claim_task", { taskId: taskBId }); expect(result.content[0].text).toContain("Not all prerequisites"); // Claim and complete A await client.callTool("claim_task", { taskId: taskAId }); await client.callTool("complete_task", { taskId: taskAId, summary: "Foundation complete", filesChanged: {}, }); // Now claim and complete B await client.callTool("claim_task", { taskId: taskBId }); await client.callTool("complete_task", { taskId: taskBId, summary: "Middle layer complete", filesChanged: {}, }); // Finally claim and complete C result = await client.callTool("claim_task", { taskId: taskCId }); expect(result.content[0].text).toContain("Successfully claimed"); await client.callTool("complete_task", { taskId: taskCId, summary: "Final task complete", filesChanged: {}, }); }); it("should handle parallel prerequisites", async () => { // Create two independent prerequisites const prereq1 = await client.callTool("create_issue", { type: "task", title: "Prerequisite 1", status: "open", }); const prereq1Id = prereq1.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const prereq2 = await client.callTool("create_issue", { type: "task", title: "Prerequisite 2", status: "open", }); const prereq2Id = prereq2.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create task depending on both const mainTask = await client.callTool("create_issue", { type: "task", title: "Task with Multiple Prerequisites", status: "open", prerequisites: [prereq1Id, prereq2Id], }); const mainTaskId = mainTask.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Try to claim main task (should fail) const failResult = await client.callTool("claim_task", { taskId: mainTaskId, }); expect(failResult.content[0].text).toContain("Not all prerequisites"); // Complete only prereq1 await client.callTool("claim_task", { taskId: prereq1Id }); await client.callTool("complete_task", { taskId: prereq1Id, summary: "First prerequisite done", filesChanged: {}, }); // Try to claim main task (should still fail) const failResult2 = await client.callTool("claim_task", { taskId: mainTaskId, }); expect(failResult2.content[0].text).toContain("Not all prerequisites"); // Complete prereq2 await client.callTool("claim_task", { taskId: prereq2Id }); await client.callTool("complete_task", { taskId: prereq2Id, summary: "Second prerequisite done", filesChanged: {}, }); // Now can claim main task const result = await client.callTool("claim_task", { taskId: mainTaskId, }); expect(result.content[0].text).toContain("Successfully claimed"); }); }); describe("Complex Dependency Graphs", () => { it("should handle diamond dependency pattern", async () => { // A // / \ // B C // \ / // D const taskA = await client.callTool("create_issue", { type: "task", title: "Task A - Root", status: "open", }); const taskAId = taskA.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const taskB = await client.callTool("create_issue", { type: "task", title: "Task B - Left Branch", status: "open", prerequisites: [taskAId], }); const taskBId = taskB.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const taskC = await client.callTool("create_issue", { type: "task", title: "Task C - Right Branch", status: "open", prerequisites: [taskAId], }); const taskCId = taskC.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const taskD = await client.callTool("create_issue", { type: "task", title: "Task D - Convergence", status: "open", prerequisites: [taskBId, taskCId], }); const taskDId = taskD.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Complete A await client.callTool("claim_task", { taskId: taskAId }); await client.callTool("complete_task", { taskId: taskAId, summary: "Root complete", filesChanged: {}, }); // B and C can now be claimed in parallel await client.callTool("claim_task", { taskId: taskBId }); await client.callTool("claim_task", { taskId: taskCId }); // Try to claim D (should fail) let result = await client.callTool("claim_task", { taskId: taskDId }); expect(result.content[0].text).toContain("Not all prerequisites"); // Complete B and C await client.callTool("complete_task", { taskId: taskBId, summary: "Left branch complete", filesChanged: {}, }); await client.callTool("complete_task", { taskId: taskCId, summary: "Right branch complete", filesChanged: {}, }); // Now D can be claimed result = await client.callTool("claim_task", { taskId: taskDId }); expect(result.content[0].text).toContain("Successfully claimed"); }); it("should handle cross-type dependencies", async () => { // Create project -> epic -> feature hierarchy const projectResult = await client.callTool("create_issue", { type: "project", title: "Cross-Type Project", status: "open", }); const projectId = projectResult.content[0].text.match( /ID: (P-[a-z0-9-]+)/, )![1] as string; const epicResult = await client.callTool("create_issue", { type: "epic", title: "Cross-Type Epic", parent: projectId, status: "open", }); const epicId = epicResult.content[0].text.match( /ID: (E-[a-z0-9-]+)/, )![1] as string; const featureResult = await client.callTool("create_issue", { type: "feature", title: "Cross-Type Feature", parent: epicId, status: "open", }); const featureId = featureResult.content[0].text.match( /ID: (F-[a-z0-9-]+)/, )![1] as string; // Create task1 under feature const task1Result = await client.callTool("create_issue", { type: "task", title: "Task 1 - Under Feature", parent: featureId, status: "open", }); const task1Id = task1Result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create standalone task2 depending on task1 const task2Result = await client.callTool("create_issue", { type: "task", title: "Task 2 - Standalone", status: "open", prerequisites: [task1Id], }); const task2Id = task2Result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Try to claim task2 (should fail) const crossTypeFailResult = await client.callTool("claim_task", { taskId: task2Id, }); expect(crossTypeFailResult.content[0].text).toContain( "Not all prerequisites", ); // Complete task1 await client.callTool("claim_task", { taskId: task1Id }); await client.callTool("complete_task", { taskId: task1Id, summary: "Feature task complete", filesChanged: {}, }); // Now task2 can be claimed const result = await client.callTool("claim_task", { taskId: task2Id }); expect(result.content[0].text).toContain("Successfully claimed"); }); }); describe("Circular Dependency Detection", () => { it("should handle self-referencing prerequisites gracefully", async () => { // Create task with self-reference (should be prevented at creation) const result = await client.callTool("create_issue", { type: "task", title: "Self-Referencing Task", status: "open", prerequisites: ["T-self-ref"], // Its own ID }); const taskId = result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // The task gets created but prerequisite won't exist // Try to claim (prerequisite doesn't exist so should succeed) const claimResult = await client.callTool("claim_task", { taskId }); expect(claimResult.content[0].text).toContain("Successfully claimed"); }); it("should handle mutual dependencies", async () => { // Create task A const taskA = await client.callTool("create_issue", { type: "task", title: "Task A - Mutual", status: "open", }); const taskAId = taskA.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create task B depending on A const taskB = await client.callTool("create_issue", { type: "task", title: "Task B - Mutual", status: "open", prerequisites: [taskAId], }); const taskBId = taskB.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Try to update A to depend on B (creating cycle) await client.callTool("update_issue", { id: taskAId, prerequisites: [taskBId], }); // Both tasks should now be unclaimable let result = await client.callTool("claim_task", { taskId: taskAId }); expect(result.content[0].text).toContain("Not all prerequisites"); result = await client.callTool("claim_task", { taskId: taskBId }); expect(result.content[0].text).toContain("Not all prerequisites"); // Force claim should work result = await client.callTool("claim_task", { taskId: taskAId, force: true, }); expect(result.content[0].text).toContain("Successfully claimed"); }); }); describe("Partial Prerequisites", () => { it("should handle wont-do prerequisites", async () => { // Create prerequisite that will be marked wont-do const prereqResult = await client.callTool("create_issue", { type: "task", title: "Prerequisite - Will Cancel", status: "open", }); const prereqId = prereqResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create dependent task const mainResult = await client.callTool("create_issue", { type: "task", title: "Task with Cancelled Prerequisite", status: "open", prerequisites: [prereqId], }); const mainId = mainResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Mark prerequisite as wont-do await client.callTool("update_issue", { id: prereqId, status: "wont-do", }); // Main task should now be claimable const result = await client.callTool("claim_task", { taskId: mainId }); expect(result.content[0].text).toContain("Successfully claimed"); }); it("should handle external prerequisites", async () => { // Create task with external (non-existent) prerequisite const result = await client.callTool("create_issue", { type: "task", title: "Task with External Dependency", status: "open", prerequisites: ["EXTERNAL-API-READY", "EXTERNAL-DOCS-COMPLETE"], }); const taskId = result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Should be claimable since external prerequisites are assumed complete const claimResult = await client.callTool("claim_task", { taskId }); expect(claimResult.content[0].text).toContain("Successfully claimed"); }); it("should handle mixed internal and external prerequisites", async () => { // Create internal prerequisite const internalResult = await client.callTool("create_issue", { type: "task", title: "Internal Prerequisite", status: "open", }); const internalId = internalResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create task with both internal and external prerequisites const mainResult = await client.callTool("create_issue", { type: "task", title: "Mixed Prerequisites Task", status: "open", prerequisites: [internalId, "EXTERNAL-APPROVAL"], }); const mainId = mainResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Should not be claimable (internal not complete) let result = await client.callTool("claim_task", { taskId: mainId }); expect(result.content[0].text).toContain("Not all prerequisites"); // Complete internal prerequisite await client.callTool("claim_task", { taskId: internalId }); await client.callTool("complete_task", { taskId: internalId, summary: "Internal work done", filesChanged: {}, }); // Now should be claimable result = await client.callTool("claim_task", { taskId: mainId }); expect(result.content[0].text).toContain("Successfully claimed"); }); }); describe("Prerequisites Edge Cases", () => { it("should handle empty prerequisites array", async () => { const result = await client.callTool("create_issue", { type: "task", title: "No Prerequisites Task", status: "open", prerequisites: [], }); const taskId = result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Should be immediately claimable const claimResult = await client.callTool("claim_task", { taskId }); expect(claimResult.content[0].text).toContain("Successfully claimed"); }); it("should handle prerequisites on different object types", async () => { // Create feature with prerequisites const task1Result = await client.callTool("create_issue", { type: "task", title: "Prerequisite Task for Feature", status: "open", }); const task1Id = task1Result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const featureResult = await client.callTool("create_issue", { type: "feature", title: "Feature with Prerequisites", status: "open", prerequisites: [task1Id], }); const featureId = featureResult.content[0].text.match( /ID: (F-[a-z0-9-]+)/, )![1] as string; // Create task depending on feature const task2Result = await client.callTool("create_issue", { type: "task", title: "Task Depending on Feature", status: "open", prerequisites: [featureId], }); const task2Id = task2Result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Complete task1 await client.callTool("claim_task", { taskId: task1Id }); await client.callTool("complete_task", { taskId: task1Id, summary: "Prerequisite for feature done", filesChanged: {}, }); // Mark feature as done await client.callTool("update_issue", { id: featureId, status: "done", }); // Now task2 should be claimable const result = await client.callTool("claim_task", { taskId: task2Id }); expect(result.content[0].text).toContain("Successfully claimed"); }); }); describe("Dynamic Prerequisite Updates", () => { it("should handle adding prerequisites to existing tasks", async () => { // Create tasks const task1Result = await client.callTool("create_issue", { type: "task", title: "Task 1 - Independent", status: "open", }); const task1Id = task1Result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; const task2Result = await client.callTool("create_issue", { type: "task", title: "Task 2 - Initially Independent", status: "open", prerequisites: [], }); const task2Id = task2Result.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Task 2 should be claimable initially let result = await client.callTool("claim_task", { taskId: task2Id }); expect(result.content[0].text).toContain("Successfully claimed"); // Reset task 2 to open await client.callTool("update_issue", { id: task2Id, status: "open", }); // Add prerequisite to task 2 await client.callTool("update_issue", { id: task2Id, prerequisites: [task1Id], }); // Now task 2 should not be claimable result = await client.callTool("claim_task", { taskId: task2Id }); expect(result.content[0].text).toContain("Not all prerequisites"); // Complete task 1 await client.callTool("claim_task", { taskId: task1Id }); await client.callTool("complete_task", { taskId: task1Id, summary: "Prerequisite completed", filesChanged: {}, }); // Now task 2 should be claimable result = await client.callTool("claim_task", { taskId: task2Id }); expect(result.content[0].text).toContain("Successfully claimed"); }); it("should handle removing prerequisites from existing tasks", async () => { // Create prerequisite task const prereqResult = await client.callTool("create_issue", { type: "task", title: "Prerequisite Task", status: "open", }); const prereqId = prereqResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create dependent task const dependentResult = await client.callTool("create_issue", { type: "task", title: "Dependent Task", status: "open", prerequisites: [prereqId], }); const dependentId = dependentResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Should not be claimable initially let result = await client.callTool("claim_task", { taskId: dependentId }); expect(result.content[0].text).toContain("Not all prerequisites"); // Remove prerequisite await client.callTool("update_issue", { id: dependentId, prerequisites: [], }); // Now should be claimable result = await client.callTool("claim_task", { taskId: dependentId }); expect(result.content[0].text).toContain("Successfully claimed"); }); }); describe("Priority with Prerequisites", () => { it("should claim highest priority task among available tasks", async () => { // Create prerequisite const prereqResult = await client.callTool("create_issue", { type: "task", title: "Completed Prerequisite", status: "done", }); const prereqId = prereqResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create high priority task with completed prerequisite await client.callTool("create_issue", { type: "task", title: "High Priority Available", priority: "high", status: "open", prerequisites: [prereqId], }); // Create medium priority task without prerequisites await client.callTool("create_issue", { type: "task", title: "Medium Priority Independent", priority: "medium", status: "open", prerequisites: [], }); // Auto-claim should get high priority task const result = await client.callTool("claim_task", {}); expect(result.content[0].text).toContain("High Priority Available"); }); it("should skip blocked high priority tasks and claim available lower priority", async () => { // Create incomplete prerequisite const prereqResult = await client.callTool("create_issue", { type: "task", title: "Incomplete Prerequisite", status: "open", priority: "low", }); const prereqId = prereqResult.content[0].text.match( /ID: (T-[a-z0-9-]+)/, )![1] as string; // Create high priority task with incomplete prerequisite await client.callTool("create_issue", { type: "task", title: "High Priority Blocked", priority: "high", status: "open", prerequisites: [prereqId], }); // Create medium priority task without prerequisites await client.callTool("create_issue", { type: "task", title: "Medium Priority Available", priority: "medium", status: "open", prerequisites: [], }); // Auto-claim should get medium priority task (high is blocked) const result = await client.callTool("claim_task", {}); expect(result.content[0].text).toContain("Medium Priority Available"); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/langadventurellc/task-trellis-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server