UNPKG

mcp-ai-agent-guidelines

Version:

A comprehensive Model Context Protocol server providing advanced tools, resources, and prompts for implementing AI agent best practices

236 lines 7.5 kB
/** * Output Selector - Intelligent strategy selection based on context * * Analyzes context signals to recommend the most appropriate output strategy. * Provides automatic selection with confidence scoring and reasoning. * * @module gateway/output-selector */ import { OutputApproach } from "../strategies/output-strategy.js"; /** * Keywords that signal Spec-Kit is the appropriate output format. * * Spec-Kit is recommended when: * - Multiple Spec-Kit signals are present (≥2) * - Constitution or constraints are referenced * - GitHub workflow or spec-driven development context is detected */ const SPECKIT_SIGNALS = [ "spec", "specification", "spec.md", "plan", "plan.md", "tasks", "tasks.md", "task list", "progress", "progress.md", "github workflow", "speckit", "constitution", "constraints", "acceptance criteria", ]; /** * Extract keywords from context text. * * Simple extraction that splits on whitespace and punctuation, * filters out common stop words, and normalizes to lowercase. * Preserves file extensions like .md in filenames. * * @param context - Input text to analyze * @returns Array of extracted keywords */ export function extractKeywords(context) { // Normalize and split on whitespace and specific punctuation // Preserve dots in filenames like spec.md const words = context .toLowerCase() .split(/[\s,;!?()[\]{}'"]+/) .filter(Boolean); // Basic stop word filtering const stopWords = new Set([ "a", "an", "the", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "can", "in", "on", "at", "to", "for", "of", "with", "by", "from", "and", "or", "but", "not", ]); return words.filter((word) => !stopWords.has(word) && word.length > 1); } /** * Check if a signal matches against keywords. * Handles both single-word and multi-word signals. * * @param signal - Signal to match (e.g., "spec", "github workflow") * @param keywords - Array of normalized keywords * @returns True if signal matches */ function matchesSignal(signal, keywords) { const signalWords = signal.split(/\s+/); if (signalWords.length > 1) { // Multi-word signal: check if exact match exists first if (keywords.includes(signal)) { return true; } // Otherwise, all words must be present in keywords return signalWords.every((word) => keywords.includes(word)); } // Single-word signal: check exact match or word boundary return keywords.some((keyword) => { // Exact match if (keyword === signal) { return true; } // Match if signal appears as complete word or with separators // This prevents "specification" from matching "spec" // but allows "spec.md" or "create-spec" to match "spec" const pattern = new RegExp(`(?:^|[^a-z])${signal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^a-z]|$)`); return pattern.test(keyword); }); } /** * Select the appropriate output approach based on context signals. * * Decision logic: * 1. If constitution is referenced → SPECKIT (high confidence) * 2. If ≥2 Spec-Kit signals detected → SPECKIT * 3. Otherwise → CHAT (default) * * @param signals - Context signals for strategy selection * @returns Recommended output approach */ export function selectApproach(signals) { // Constitution reference is a strong signal for Spec-Kit if (signals.hasConstitution) { return OutputApproach.SPECKIT; } // Check for Spec-Kit signals - use exact match or word boundary match // Note: keywords are expected to already be normalized by extractKeywords() const speckitScore = SPECKIT_SIGNALS.filter((signal) => { return matchesSignal(signal, signals.keywords); }).length; // Require multiple signals to avoid false positives from generic keywords if (speckitScore >= 2) { return OutputApproach.SPECKIT; } // Default to CHAT approach for general use return OutputApproach.CHAT; } /** * Calculate confidence score for a recommendation. * * Confidence scoring: * - Constitution present: 95% * - 3+ Spec-Kit signals: 90% * - 2 Spec-Kit signals: 75% * - Default (CHAT): 60% * * @param signals - Context signals * @param approach - Selected approach * @returns Confidence score (0-100) */ export function calculateConfidence(signals, approach) { if (approach === OutputApproach.SPECKIT) { if (signals.hasConstitution) { return 95; } // Note: keywords are expected to already be normalized by extractKeywords() const speckitScore = SPECKIT_SIGNALS.filter((signal) => { return matchesSignal(signal, signals.keywords); }).length; if (speckitScore >= 3) { return 90; } if (speckitScore === 2) { return 75; } } // Default confidence for CHAT approach return 60; } /** * Generate human-readable reasoning for the recommendation. * * @param signals - Context signals * @param approach - Selected approach * @returns Reasoning explanation */ export function generateReasoning(signals, approach) { if (approach === OutputApproach.SPECKIT) { if (signals.hasConstitution) { return "Constitution or constraint document detected. Spec-Kit format provides structured specifications with constitution support."; } // Note: keywords are expected to already be normalized by extractKeywords() const matchedSignals = SPECKIT_SIGNALS.filter((signal) => { return matchesSignal(signal, signals.keywords); }); return `Multiple Spec-Kit signals detected (${matchedSignals.length}): ${matchedSignals.slice(0, 3).join(", ")}. Spec-Kit format recommended for spec-driven development workflow.`; } return "Default markdown format suitable for general-purpose output."; } /** * Recommend an output approach based on context analysis. * * This is the main entry point for strategy selection. It: * 1. Extracts keywords from context * 2. Detects constitution references * 3. Selects appropriate approach * 4. Calculates confidence and generates reasoning * * @param context - Input context text to analyze * @returns Recommendation with approach, confidence, and reasoning * * @example * ```typescript * const result = recommendApproach('Create a spec.md and plan.md for the project'); * // Returns: { approach: OutputApproach.SPECKIT, confidence: 75, reasoning: "..." } * ``` */ export function recommendApproach(context) { const keywords = extractKeywords(context); const signals = { keywords }; // Detect constitution reference (case-insensitive) if (context.toLowerCase().includes("constitution")) { signals.hasConstitution = true; } const approach = selectApproach(signals); return { approach, confidence: calculateConfidence(signals, approach), reasoning: generateReasoning(signals, approach), }; } //# sourceMappingURL=output-selector.js.map