squabble-mcp
Version:
Engineer-driven development with critical-thinking PM collaboration - MCP server for Claude
96 lines ⢠5.24 kB
JavaScript
import { z } from 'zod';
import path from 'path';
import { PMSessionManager } from '../pm/session-manager.js';
import { FileEventBroker } from '../streaming/file-event-broker.js';
const consultPMSchema = z.object({
message: z.string().describe('Your message or question for the PM'),
context: z.object({
currentTask: z.string().optional().describe('Current task you are working on'),
codeSnippet: z.string().optional().describe('Relevant code to discuss'),
specificQuestions: z.array(z.string()).optional().describe('Specific questions for PM')
}).optional(),
continueSession: z.boolean().optional().default(true).describe('Continue existing PM session if available')
});
/**
* Tool for consulting with the PM on requirements, approach, or any questions
* This maintains a continuous dialogue with the PM using --resume functionality
*/
export function registerConsultPM(server, workspaceManager, pmSessionManager) {
const eventBroker = FileEventBroker.getInstance(workspaceManager);
server.addTool({
name: 'consult_pm',
description: 'Consult with the PM about requirements, approach, or any questions. Maintains conversation context. MANDATORY first step. Research deeply with WebSearch BEFORE consulting. Use @User to escalate questions to human.',
parameters: consultPMSchema,
execute: async (args) => {
const { message, context, continueSession } = args;
try {
// Check if workspace is initialized
workspaceManager.checkInitialized();
// Build the full prompt with context
let fullPrompt = message;
if (context) {
const contextParts = [];
if (context.currentTask) {
contextParts.push(`Current Task: ${context.currentTask}`);
}
if (context.codeSnippet) {
contextParts.push(`Code Context:\n\`\`\`\n${context.codeSnippet}\n\`\`\``);
}
if (context.specificQuestions && context.specificQuestions.length > 0) {
contextParts.push(`Specific Questions:\n${context.specificQuestions.map((q, i) => `${i + 1}. ${q}`).join('\n')}`);
}
if (contextParts.length > 0) {
fullPrompt = `${contextParts.join('\n\n')}\n\n${message}`;
}
}
// Get current session if we should continue
let resumeSessionId;
if (continueSession) {
const currentSession = await pmSessionManager.getCurrentSession();
resumeSessionId = currentSession?.currentSessionId;
}
// Start streaming PM session
const sessionId = await eventBroker.startPMSession(fullPrompt, PMSessionManager.createPMSystemPromptWithCustom(workspaceManager.getWorkspaceRoot()), resumeSessionId, {
engineerId: 'current-engineer', // TODO: Get actual engineer ID
taskId: context?.currentTask
});
// Collect PM response from streaming session
let response = '';
const responsePromise = new Promise((resolve) => {
const messageHandler = (event) => {
if (event.sessionId === sessionId) {
if (event.type === 'pm_message' && event.message) {
response += event.message;
}
else if (event.type === 'session_end') {
eventBroker.off('pm-event', messageHandler);
resolve(response);
}
}
};
eventBroker.on('pm-event', messageHandler);
});
// Wait for the response to complete (no timeout)
response = await responsePromise;
// Save conversation context
await workspaceManager.saveContext('last-pm-consultation', {
timestamp: new Date(),
message,
context,
response,
sessionId
});
const sessionInfo = resumeSessionId
? 'Continuing previous PM session. Context maintained.'
: 'Started new PM session. Use continueSession=true to maintain context.';
const activityLogPath = path.join(workspaceManager.getWorkspaceRoot(), 'pm-activity.log');
return `PM Response:\n\n${response}\n\n---\nSession ID: ${sessionId}\n${sessionInfo}\n\nš PM activity log: ${activityLogPath}`;
}
catch (error) {
console.error('Failed to consult PM:', error);
return `Error: ${error instanceof Error ? error.message : 'Failed to consult PM'}. Ensure Claude CLI is installed and workspace is initialized.`;
}
}
});
}
//# sourceMappingURL=consult-pm.js.map