UNPKG

mcp-ai-agent-guidelines

Version:

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

387 lines (357 loc) 12.2 kB
/** * ADRStrategy - Architecture Decision Record output format * * Renders domain results as Architecture Decision Records (ADRs) following * the Michael Nygard format. This is the industry standard for documenting * architectural decisions with status, context, decision, and consequences. * * @module strategies/adr-strategy * @see {@link https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions Michael Nygard ADR format} * @see {@link https://github.com/Anselmoo/mcp-ai-agent-guidelines/blob/development/plan-v0.13.x/specs/SPEC-001-output-strategy-layer.md SPEC-001} §4.3 */ import { OutputApproach } from "./output-strategy.js"; /** * ADRStrategy implements the Architecture Decision Record output format. * * Supports rendering: * - ADRSessionState: Design workflow decisions * - PromptResult: Prompt-based architectural decisions * * ADR Format (Michael Nygard): * - Status: Proposed/Accepted/Deprecated/Superseded * - Context: Background and problem statement * - Decision: The architectural decision made * - Consequences: Split into Positive, Negative, and Neutral * * @implements {OutputStrategy<ADRSessionState | PromptResult>} */ export class ADRStrategy { /** The output approach this strategy implements */ approach = OutputApproach.ADR; /** * Render a domain result to ADR format artifacts. * * @param result - The domain result to render (ADRSessionState or PromptResult) * @param options - Optional rendering options * @returns Output artifacts with primary ADR document * @throws {Error} If result type is not supported */ render(result, options) { if (this.isSessionState(result)) { return this.renderSessionState(result, options); } if (this.isPromptResult(result)) { return this.renderPromptResult(result, options); } throw new Error("Unsupported domain result type for ADRStrategy"); } /** * Check if this strategy supports rendering a specific domain type. * * @param domainType - The domain type identifier * @returns True if this strategy can render the domain type */ supports(domainType) { return ["SessionState", "PromptResult"].includes(domainType); } /** * Render a ADRSessionState to ADR format. * * Extracts architectural decision information from design workflow state * and formats it according to Michael Nygard's ADR template. * * @param result - The session state to render * @param options - Optional rendering options * @returns Output artifacts with ADR document * @private */ renderSessionState(result, options) { const adrNumber = this.generateAdrNumber(); const title = this.extractTitle(result); const fileName = `ADR-${adrNumber}-${this.slugify(title)}.md`; const content = `# ADR-${adrNumber}: ${title} ## Status **Proposed** | _${new Date().toISOString().split("T")[0]}_ ## Context ${this.extractContext(result)} ## Decision ${this.extractDecision(result)} ## Consequences ### Positive ${this.extractPositiveConsequences(result)} ### Negative ${this.extractNegativeConsequences(result)} ### Neutral ${this.extractNeutralConsequences(result)} ## References ${this.extractReferences(result, options)} --- *ADR generated by design-assistant* `; return { primary: { name: fileName, content: content.trim(), format: "markdown", }, }; } /** * Render a PromptResult to ADR format. * * Converts prompt sections into ADR structure, mapping sections * to ADR template sections (Context, Decision, Consequences). * * @param result - The prompt result to render * @param options - Optional rendering options * @returns Output artifacts with ADR document * @private */ renderPromptResult(result, options) { const adrNumber = this.generateAdrNumber(); const title = this.extractTitleFromPrompt(result); const fileName = `ADR-${adrNumber}-${this.slugify(title)}.md`; // Extract sections by their typical names // Use exact matching to avoid false positives (e.g., "API Gateway Decision" matching "decision") const contextSection = result.sections.find((s) => s.title.toLowerCase() === "context"); const decisionSection = result.sections.find((s) => s.title.toLowerCase() === "decision" || s.title.toLowerCase() === "goal"); const consequencesSection = result.sections.find((s) => s.title.toLowerCase() === "consequences"); const content = `# ADR-${adrNumber}: ${title} ## Status **Proposed** | _${new Date().toISOString().split("T")[0]}_ ## Context ${contextSection?.body || "No context provided"} ## Decision ${decisionSection?.body || "Decision to be documented"} ## Consequences ### Positive ${consequencesSection?.body || "- To be determined"} ### Negative - To be determined ### Neutral - To be determined ${options?.includeMetadata ? `\n\n---\n*Technique: ${result.metadata.techniques.join(", ")} | Tokens: ~${result.metadata.tokenEstimate}*` : ""} --- *ADR generated by hierarchical-prompt-builder* `; return { primary: { name: fileName, content: content.trim(), format: "markdown", }, }; } /** * Generate a sequential ADR number. * * Uses timestamp-based generation to create unique ADR numbers. * Format: 4-digit number from last 4 digits of timestamp. * * @returns 4-digit ADR number as string * @private */ generateAdrNumber() { return String(Date.now()).slice(-4).padStart(4, "0"); } /** * Slugify a title for use in filenames. * * Converts title to lowercase, replaces non-alphanumeric characters * with hyphens, and truncates to 50 characters. * * @param title - The title to slugify * @returns Slugified title suitable for filenames * @private */ slugify(title) { return title .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 50) .replace(/^-+|-+$/g, ""); } /** * Extract title from ADRSessionState. * * Looks for title in metadata, config.goal, or uses default. * * @param result - The session state * @returns The extracted title * @private */ extractTitle(result) { // Check metadata title if (result.metadata?.title) { return result.metadata.title; } // Check config goal if (result.config?.goal) { return result.config.goal; } // Check context goal (with type guard) if (result.config?.context?.goal && typeof result.config.context.goal === "string") { return result.config.context.goal; } return "Untitled Decision"; } /** * Extract title from PromptResult. * * Uses first section title or metadata if available. * * @param result - The prompt result * @returns The extracted title * @private */ extractTitleFromPrompt(result) { return result.sections[0]?.title || "Architecture Decision"; } /** * Extract context from ADRSessionState. * * Builds context section from session context, config, and phase information. * * @param result - The session state * @returns Formatted context description * @private */ extractContext(result) { const parts = []; // Add goal if available if (result.config?.goal) { parts.push(result.config.goal); } // Add context description if (result.config?.context) { const contextStr = typeof result.config.context === "string" ? result.config.context : JSON.stringify(result.config.context, null, 2); parts.push(`\n**Context:**\n${contextStr}`); } // Add phase information if (result.phase) { parts.push(`\n**Current Phase:** ${result.phase}`); } return parts.length > 0 ? parts.join("\n\n") : "Context to be documented based on architectural needs."; } /** * Extract decision statement from ADRSessionState. * * Looks for decision in metadata or constructs from goal. * * @param result - The session state * @returns Decision statement * @private */ extractDecision(result) { if (result.metadata?.decision) { return result.metadata.decision; } if (result.config?.goal) { return `We will ${result.config.goal.toLowerCase()}`; } return "The architectural decision will be documented here."; } /** * Extract positive consequences from ADRSessionState. * * Formats positive consequences as markdown list. * * @param result - The session state * @returns Formatted positive consequences list * @private */ extractPositiveConsequences(result) { const consequences = result.metadata?.positiveConsequences || []; return consequences.length > 0 ? consequences.map((c) => `- ${c}`).join("\n") : "- To be determined"; } /** * Extract negative consequences from ADRSessionState. * * Formats negative consequences as markdown list. * * @param result - The session state * @returns Formatted negative consequences list * @private */ extractNegativeConsequences(result) { const consequences = result.metadata?.negativeConsequences || []; return consequences.length > 0 ? consequences.map((c) => `- ${c}`).join("\n") : "- To be determined"; } /** * Extract neutral consequences from ADRSessionState. * * Formats neutral consequences as markdown list. * * @param result - The session state * @returns Formatted neutral consequences list * @private */ extractNeutralConsequences(result) { const consequences = result.metadata?.neutralConsequences || []; return consequences.length > 0 ? consequences.map((c) => `- ${c}`).join("\n") : "- To be determined"; } /** * Extract references from ADRSessionState. * * Builds references section from artifacts and session metadata. * * @param result - The session state * @param _options - Optional rendering options (unused) * @returns Formatted references list * @private */ extractReferences(result, _options) { const references = []; // Add session reference references.push(`- Session ID: ${result.id}`); // Add phase reference if available if (result.phase) { references.push(`- Phase: ${result.phase}`); } // Add artifact references if (result.artifacts && Object.keys(result.artifacts).length > 0) { references.push(`- Related artifacts: ${Object.keys(result.artifacts).join(", ")}`); } return references.join("\n"); } /** * Type guard for ADRSessionState. * * @param result - The value to check * @returns True if result is an ADRSessionState * @private */ isSessionState(result) { return (typeof result === "object" && result !== null && "id" in result && typeof result.id === "string"); } /** * Type guard for PromptResult. * * @param result - The value to check * @returns True if result is a PromptResult * @private */ isPromptResult(result) { return (typeof result === "object" && result !== null && "sections" in result && "metadata" in result); } } //# sourceMappingURL=adr-strategy.js.map