This is page 2 of 2. Use http://codebase.md/deus-h/claudeus-plane-mcp?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .cursorignore ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── Dockerfile ├── docs │ ├── smithery-docs.md │ └── transform-to-proper-standards.md ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── notes.txt ├── package.json ├── plane-instances-example.json ├── plane-instances-test-example.json ├── pnpm-lock.yaml ├── readme.md ├── SECURITY.md ├── smithery.yaml ├── src │ ├── api │ │ ├── base-client.ts │ │ ├── client.ts │ │ ├── issues │ │ │ ├── client.ts │ │ │ └── types.ts │ │ ├── projects.ts │ │ └── types │ │ ├── config.ts │ │ └── project.ts │ ├── config │ │ └── plane-config.ts │ ├── dummy-data │ │ ├── json.d.ts │ │ ├── projects.d.ts │ │ └── projects.json │ ├── index.ts │ ├── inspector-wrapper.ts │ ├── mcp │ │ ├── server.ts │ │ └── tools.ts │ ├── prompts │ │ └── projects │ │ ├── definitions.ts │ │ ├── handlers.ts │ │ └── index.ts │ ├── security │ │ └── SecurityManager.ts │ ├── test │ │ ├── integration │ │ │ └── projects.test.ts │ │ ├── mcp-test-harness.ts │ │ ├── setup.ts │ │ └── unit │ │ └── tools │ │ └── projects │ │ └── list.test.ts │ ├── tools │ │ ├── index.ts │ │ ├── issues │ │ │ ├── create.ts │ │ │ ├── get.ts │ │ │ ├── list.ts │ │ │ └── update.ts │ │ └── projects │ │ ├── __tests__ │ │ │ ├── create.test.ts │ │ │ ├── delete.test.ts │ │ │ ├── handlers.test.ts │ │ │ └── update.test.ts │ │ ├── create.ts │ │ ├── delete.ts │ │ ├── handlers.ts │ │ ├── index.ts │ │ ├── list.ts │ │ └── update.ts │ └── types │ ├── api.ts │ ├── index.ts │ ├── issue.ts │ ├── mcp.d.ts │ ├── mcp.ts │ ├── project.ts │ ├── prompt.ts │ └── security.ts ├── tsconfig.json └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /src/prompts/projects/handlers.ts: -------------------------------------------------------------------------------- ```typescript import { PromptHandler, PromptContext, PromptResponse } from '../../types/prompt.js'; import { PlaneApiClient, NotificationOptions, ToolExecutionOptions } from '../../api/client.js'; import { PlaneInstance } from '../../config/plane-config.js'; interface Project { name: string; total_members: number; total_cycles: number; total_modules: number; [key: string]: any; } interface ProjectMetrics { name: string; members: number; cycles: number; modules: number; complexity: number; } interface ProjectStructureAnalysis { name: string; missingFeatures: string[]; cycleGap: number; moduleGap: number; memberGap: number; } interface ProjectRecommendation { project: string; recommendations: string[]; } export const analyzeWorkspaceHealthHandler: PromptHandler = async (args: Record<string, unknown>, context: PromptContext): Promise<PromptResponse> => { try { const planeInstance: PlaneInstance = { name: 'default', baseUrl: process.env.PLANE_BASE_URL || '', apiKey: process.env.PLANE_API_KEY || '' }; const client = new PlaneApiClient(planeInstance, context); const workspace_slug = args.workspace_slug as string | undefined; const include_archived = args.include_archived as boolean | undefined; // Get projects using the tool const toolResult = await client.executeTool('claudeus_plane_projects__list', { workspace_slug, include_archived }); if (!toolResult?.content?.[0]?.text) { throw new Error('Invalid tool result format'); } const projects = JSON.parse(toolResult.content[0].text) as Project[]; // Analyze project health metrics const totalProjects = projects.length; const metrics = { memberDistribution: projects.map((p: Project) => ({ name: p.name, memberCount: p.total_members })), cycleUsage: projects.map((p: Project) => ({ name: p.name, cycleCount: p.total_cycles })), moduleUsage: projects.map((p: Project) => ({ name: p.name, moduleCount: p.total_modules })) }; // Generate insights const insights: string[] = []; // Member distribution insights const avgMembers = metrics.memberDistribution.reduce((acc: number, p) => acc + p.memberCount, 0) / totalProjects; insights.push(`Average team size: ${avgMembers.toFixed(1)} members per project`); const underStaffed = metrics.memberDistribution.filter(p => p.memberCount < avgMembers * 0.5); if (underStaffed.length > 0) { insights.push(`Potentially understaffed projects: ${underStaffed.map(p => p.name).join(', ')}`); } // Feature usage insights const lowCycleUsage = metrics.cycleUsage.filter(p => p.cycleCount === 0); if (lowCycleUsage.length > 0) { insights.push(`Projects not using cycles: ${lowCycleUsage.map(p => p.name).join(', ')}`); } const lowModuleUsage = metrics.moduleUsage.filter(p => p.moduleCount === 0); if (lowModuleUsage.length > 0) { insights.push(`Projects not using modules: ${lowModuleUsage.map(p => p.name).join(', ')}`); } const response: PromptResponse = { messages: [{ role: 'assistant', content: { type: 'text', text: '# Workspace Health Analysis\n\n' + `Analyzed ${totalProjects} projects${workspace_slug ? ` in workspace ${workspace_slug}` : ''}\n\n` + '## Key Insights\n\n' + insights.map(i => `- ${i}`).join('\n') + '\n\n' + '## Recommendations\n\n' + '1. Consider redistributing team members for more balanced project staffing\n' + '2. Encourage use of cycles for better project planning\n' + '3. Leverage modules for improved project organization' } }], metadata: { metrics, insights } }; return response; } catch (error) { console.error('Failed to analyze workspace health:', error); return { messages: [{ role: 'assistant', content: { type: 'text', text: `Error analyzing workspace health: ${error instanceof Error ? error.message : String(error)}` } }], metadata: { error: error instanceof Error ? error.message : String(error) } }; } }; export const suggestResourceAllocationHandler: PromptHandler = async (args: Record<string, unknown>, context: PromptContext): Promise<PromptResponse> => { try { const planeInstance: PlaneInstance = { name: 'default', baseUrl: process.env.PLANE_BASE_URL || '', apiKey: process.env.PLANE_API_KEY || '' }; const client = new PlaneApiClient(planeInstance, context); const workspace_slug = args.workspace_slug as string | undefined; const focus_area = (args.focus_area as string) || 'members'; // Get projects using the tool const toolResult = await client.executeTool('claudeus_plane_projects__list', { workspace_slug }); if (!toolResult?.content?.[0]?.text) { throw new Error('Invalid tool result format'); } const projects = JSON.parse(toolResult.content[0].text) as Project[]; // Analyze current allocation const allocation: ProjectMetrics[] = projects.map(p => ({ name: p.name, members: p.total_members, cycles: p.total_cycles, modules: p.total_modules, complexity: (p.total_cycles * 0.4) + (p.total_modules * 0.6) // Weighted complexity score })); // Generate recommendations based on focus area const recommendations: string[] = []; switch (focus_area) { case 'members': { const avgMembers = allocation.reduce((acc: number, p) => acc + p.members, 0) / projects.length; allocation.forEach(p => { const recommendedMembers = Math.ceil(p.complexity / avgMembers * p.members); if (recommendedMembers !== p.members) { recommendations.push(`${p.name}: ${p.members} → ${recommendedMembers} members (based on complexity)`); } }); break; } case 'cycles': { const avgCycles = allocation.reduce((acc: number, p) => acc + p.cycles, 0) / projects.length; allocation.forEach(p => { if (p.cycles < avgCycles * 0.5) { recommendations.push(`${p.name}: Consider increasing cycle usage (current: ${p.cycles}, avg: ${avgCycles.toFixed(1)})`); } }); break; } case 'modules': { const avgModules = allocation.reduce((acc: number, p) => acc + p.modules, 0) / projects.length; allocation.forEach(p => { if (p.modules < avgModules * 0.5) { recommendations.push(`${p.name}: Consider increasing module usage (current: ${p.modules}, avg: ${avgModules.toFixed(1)})`); } }); break; } } const response: PromptResponse = { messages: [{ role: 'assistant', content: { type: 'text', text: '# Resource Allocation Recommendations\n\n' + `Focus Area: ${focus_area}\n\n` + '## Recommendations\n\n' + recommendations.map(r => `- ${r}`).join('\n') + '\n\n' + '## Additional Notes\n\n' + '- Recommendations are based on project complexity and current resource usage\n' + '- Consider team expertise and project priorities when implementing changes' } }], metadata: { allocation, recommendations } }; return response; } catch (error) { console.error('Failed to analyze resource allocation:', error); return { messages: [{ role: 'assistant', content: { type: 'text', text: `Error analyzing resource allocation: ${error instanceof Error ? error.message : String(error)}` } }], metadata: { error: error instanceof Error ? error.message : String(error) } }; } }; export const recommendProjectStructureHandler: PromptHandler = async (args: Record<string, unknown>, context: PromptContext): Promise<PromptResponse> => { try { const planeInstance: PlaneInstance = { name: 'default', baseUrl: process.env.PLANE_BASE_URL || '', apiKey: process.env.PLANE_API_KEY || '' }; const client = new PlaneApiClient(planeInstance, context); const workspace_slug = args.workspace_slug as string | undefined; const template_project = args.template_project as string | undefined; // Get projects using the tool const toolResult = await client.executeTool('claudeus_plane_projects__list', { workspace_slug }); if (!toolResult?.content?.[0]?.text) { throw new Error('Invalid tool result format'); } const projects = JSON.parse(toolResult.content[0].text) as Project[]; // Define best practices const bestPractices = { minCycles: 1, minModules: 2, minMembers: 2, recommendedFeatures: ['cycles', 'modules', 'project_lead'] as const }; // If template project specified, use its metrics as best practices if (template_project) { const templateData = projects.find((p: Project) => p.name === template_project); if (templateData) { bestPractices.minCycles = templateData.total_cycles; bestPractices.minModules = templateData.total_modules; bestPractices.minMembers = templateData.total_members; } } // Analyze each project const structureAnalysis: ProjectStructureAnalysis[] = projects.map((p: Project) => ({ name: p.name, missingFeatures: bestPractices.recommendedFeatures.filter((f) => !p[f]), cycleGap: Math.max(0, bestPractices.minCycles - p.total_cycles), moduleGap: Math.max(0, bestPractices.minModules - p.total_modules), memberGap: Math.max(0, bestPractices.minMembers - p.total_members) })); // Generate recommendations const recommendations: ProjectRecommendation[] = structureAnalysis .filter((p) => p.missingFeatures.length > 0 || p.cycleGap > 0 || p.moduleGap > 0 || p.memberGap > 0) .map((p) => ({ project: p.name, recommendations: [ ...p.missingFeatures.map((f) => `Enable ${f} feature`), p.cycleGap > 0 ? `Add ${p.cycleGap} more cycle(s)` : null, p.moduleGap > 0 ? `Add ${p.moduleGap} more module(s)` : null, p.memberGap > 0 ? `Add ${p.memberGap} more team member(s)` : null ].filter((rec): rec is string => rec !== null) })); const response: PromptResponse = { messages: [{ role: 'assistant', content: { type: 'text', text: '# Project Structure Recommendations\n\n' + (template_project ? `Using "${template_project}" as template\n\n` : 'Using best practices as template\n\n') + '## Project-Specific Recommendations\n\n' + recommendations.map((r) => `### ${r.project}\n` + r.recommendations.map((rec) => `- ${rec}`).join('\n') ).join('\n\n') + '\n\n' + '## General Guidelines\n\n' + '- Maintain consistent project structure across workspace\n' + '- Regularly review and update project organization\n' + '- Document project structure decisions' } }], metadata: { bestPractices, structureAnalysis, recommendations } }; return response; } catch (error) { console.error('Failed to analyze project structure:', error); return { messages: [{ role: 'assistant', content: { type: 'text', text: `Error analyzing project structure: ${error instanceof Error ? error.message : String(error)}` } }], metadata: { error: error instanceof Error ? error.message : String(error) } }; } }; export async function handleProjectPrompts( promptId: string, args: Record<string, unknown>, client: PlaneApiClient ): Promise<PromptResponse> { try { switch (promptId) { case 'analyze_workspace_health': { const result = await client.listProjects(); return { messages: [{ role: 'assistant', content: { type: 'text', text: JSON.stringify(result, null, 2) } }] }; } case 'suggest_resource_allocation': { const result = await client.listProjects(); return { messages: [{ role: 'assistant', content: { type: 'text', text: JSON.stringify(result, null, 2) } }] }; } case 'recommend_project_structure': { const result = await client.listProjects(); return { messages: [{ role: 'assistant', content: { type: 'text', text: JSON.stringify(result, null, 2) } }] }; } default: throw new Error(`Unknown prompt: ${promptId}`); } } catch (error) { return { messages: [{ role: 'assistant', content: { type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` } }] }; } } ```