get_plan_structure
Retrieve the complete hierarchical structure of a plan, including phases, tasks, and milestones, to understand project organization and dependencies.
Instructions
Get the complete hierarchical structure of a plan
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| plan_id | Yes | Plan ID | |
| include_details | No | Include full node details |
Implementation Reference
- src/tools.js:755-781 (handler)Main execution handler for get_plan_structure tool. Fetches plan details and nodes, determines if nodes are already hierarchical or builds hierarchy using helper, then formats and returns the structure.if (name === "get_plan_structure") { const { plan_id, include_details = false } = args; const plan = await apiClient.plans.getPlan(plan_id); const nodes = await apiClient.nodes.getNodes(plan_id); // The API already returns a tree structure, not a flat list // If it's already hierarchical, use it directly let structure; if (Array.isArray(nodes) && nodes.length > 0 && nodes[0].children !== undefined) { // Already hierarchical - use directly structure = nodes; } else { // Flat list - build hierarchy structure = buildNodeHierarchy(nodes, include_details); } return formatResponse({ plan: { id: plan.id, title: plan.title, status: plan.status, description: plan.description }, structure }); }
- src/tools.js:401-416 (registration)Tool registration in the list_tools response, including name, description, and input schema.{ name: "get_plan_structure", description: "Get the complete hierarchical structure of a plan", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, include_details: { type: "boolean", description: "Include full node details", default: false } }, required: ["plan_id"] } },
- src/tools.js:404-416 (schema)Input schema defining parameters for the get_plan_structure tool: plan_id (required), include_details (optional boolean).inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, include_details: { type: "boolean", description: "Include full node details", default: false } }, required: ["plan_id"] } },
- src/tools.js:834-936 (helper)Helper function that constructs a hierarchical tree structure from a flat list of nodes, handling parent-child relationships, sorting by order_index, and optionally including full details.function buildNodeHierarchy(nodes, includeDetails = false) { if (!nodes || nodes.length === 0) { return []; } // Debug logging to understand the structure if (process.env.NODE_ENV === 'development') { console.error('Building hierarchy for nodes:', nodes.length); if (nodes[0]) { console.error('Sample node:', { id: nodes[0].id, parent_id: nodes[0].parent_id, node_type: nodes[0].node_type }); } } const nodeMap = new Map(); const rootNodes = []; // First pass: create all nodes in the map nodes.forEach(node => { const nodeData = includeDetails ? { ...node } : { id: node.id, title: node.title, node_type: node.node_type, status: node.status, parent_id: node.parent_id, order_index: node.order_index }; // Initialize with empty children array nodeMap.set(node.id, { ...nodeData, children: [] }); }); // Second pass: build parent-child relationships nodes.forEach(node => { const currentNode = nodeMap.get(node.id); if (node.parent_id) { const parent = nodeMap.get(node.parent_id); if (parent) { // Add as child to parent parent.children.push(currentNode); } else { // Parent not found, treat as root if (process.env.NODE_ENV === 'development') { console.error(`Parent ${node.parent_id} not found for node ${node.id}`); } rootNodes.push(currentNode); } } else { // No parent_id means it's a root node rootNodes.push(currentNode); } }); // Special case: if we have a single root node of type 'root', return its children if (rootNodes.length === 1 && rootNodes[0].node_type === 'root') { // Return the root node itself with its children const rootNode = rootNodes[0]; // Sort children by order_index const sortNodes = (nodeArray) => { nodeArray.sort((a, b) => { const orderA = a.order_index ?? 999; const orderB = b.order_index ?? 999; return orderA - orderB; }); nodeArray.forEach(node => { if (node.children && node.children.length > 0) { sortNodes(node.children); } }); }; sortNodes(rootNode.children); return [rootNode]; // Return root with its properly sorted children } // Sort all root nodes and their children const sortNodes = (nodeArray) => { nodeArray.sort((a, b) => { const orderA = a.order_index ?? 999; const orderB = b.order_index ?? 999; return orderA - orderB; }); nodeArray.forEach(node => { if (node.children && node.children.length > 0) { sortNodes(node.children); } }); }; sortNodes(rootNodes); return rootNodes; }