mcp-ai-agent-guidelines
Version:
A comprehensive Model Context Protocol server providing advanced tools, resources, and prompts for implementing AI agent best practices
541 lines (496 loc) • 15.5 kB
JavaScript
/**
* SDDStrategy - Spec-Driven Development output format
*
* Generates three interconnected documents that form a complete development specification:
* - spec.md: Requirements, constraints, acceptance criteria
* - plan.md: Implementation phases, timeline, dependencies
* - tasks.md: Task breakdown with Mermaid dependency graph
*
* @module strategies/sdd-strategy
* @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.4
*/
import { OutputApproach } from "./output-strategy.js";
/**
* SDDStrategy implements Spec-Driven Development output format.
*
* Supports rendering:
* - SessionState: Complete design session state into spec/plan/tasks
* - PromptResult: Prompt configuration into SDD format
*
* @implements {OutputStrategy<SessionState | PromptResult>}
*/
export class SDDStrategy {
/** The output approach this strategy implements */
approach = OutputApproach.SDD;
/**
* Render a domain result to SDD artifacts (spec.md, plan.md, tasks.md).
*
* @param result - The domain result to render (SessionState or PromptResult)
* @param options - Optional rendering options
* @returns Output artifacts with primary spec.md and secondary plan.md, tasks.md
* @throws {Error} If result type is not supported
*/
render(result, options) {
if (this.isSessionState(result)) {
return this.renderSession(result, options);
}
if (this.isPromptResult(result)) {
return this.renderPrompt(result, options);
}
throw new Error("Unsupported domain result type");
}
/**
* 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 SessionState to SDD artifacts.
*
* @param result - The session state to render
* @param options - Optional rendering options
* @returns Output artifacts with spec, plan, and tasks documents
* @private
*/
renderSession(result, _options) {
return {
primary: this.generateSpec(result),
secondary: [this.generatePlan(result), this.generateTasks(result)],
};
}
/**
* Render a PromptResult to SDD artifacts.
*
* @param result - The prompt result to render
* @param options - Optional rendering options
* @returns Output artifacts with spec, plan, and tasks documents
* @private
*/
renderPrompt(result, _options) {
return {
primary: this.generateSpecFromPrompt(result),
secondary: [
this.generatePlanFromPrompt(result),
this.generateTasksFromPrompt(result),
],
};
}
/**
* Generate spec.md from SessionState.
*
* @param result - The session state
* @returns Specification document
* @private
*/
generateSpec(result) {
const title = this.extractTitle(result);
const overview = this.extractOverview(result);
const functionalReqs = this.extractFunctionalRequirements(result);
const nonFunctionalReqs = this.extractNonFunctionalRequirements(result);
const constraints = this.extractConstraints(result);
const successCriteria = this.extractSuccessCriteria(result);
const content = `# Specification: ${title}
## Overview
${overview}
## Requirements
### Functional Requirements
${functionalReqs}
### Non-Functional Requirements
${nonFunctionalReqs}
## Constraints
${constraints}
## Success Criteria
${successCriteria}
`;
return { name: "spec.md", content, format: "markdown" };
}
/**
* Generate plan.md from SessionState.
*
* @param result - The session state
* @returns Implementation plan document
* @private
*/
generatePlan(result) {
const phases = this.extractPhases(result);
const timeline = this.extractTimeline(result);
const dependencies = this.extractDependencies(result);
const risks = this.extractRisks(result);
const content = `# Implementation Plan
## Phases
${phases}
## Timeline
${timeline}
## Dependencies
${dependencies}
## Risks
${risks}
`;
return { name: "plan.md", content, format: "markdown" };
}
/**
* Generate tasks.md from SessionState.
*
* @param result - The session state
* @returns Task breakdown document with Mermaid diagram
* @private
*/
generateTasks(result) {
const tasks = this.extractTasks(result);
const dependencyGraph = this.generateDependencyGraph(result);
const content = `# Tasks
## Task List
${tasks}
## Dependencies Graph
\`\`\`mermaid
${dependencyGraph}
\`\`\`
`;
return { name: "tasks.md", content, format: "markdown" };
}
/**
* Generate spec.md from PromptResult.
*
* @param result - The prompt result
* @returns Specification document
* @private
*/
generateSpecFromPrompt(result) {
const title = "Feature";
const sections = result.sections;
// Extract overview from first section or default
const overview = sections.length > 0 ? sections[0].body : "To be defined";
// Build requirements from sections
const requirements = sections
.filter((s) => s.title.toLowerCase().includes("requirement"))
.map((s, i) => `${i + 1}. ${s.body}`)
.join("\n");
const content = `# Specification: ${title}
## Overview
${overview}
## Requirements
${requirements || "To be defined"}
## Constraints
To be defined
## Success Criteria
To be defined
`;
return { name: "spec.md", content, format: "markdown" };
}
/**
* Generate plan.md from PromptResult.
*
* @param result - The prompt result
* @returns Implementation plan document
* @private
*/
generatePlanFromPrompt(_result) {
const content = `# Implementation Plan
## Phases
To be defined based on requirements analysis
## Timeline
To be estimated
## Dependencies
To be identified
## Risks
To be assessed
`;
return { name: "plan.md", content, format: "markdown" };
}
/**
* Generate tasks.md from PromptResult.
*
* @param result - The prompt result
* @returns Task breakdown document
* @private
*/
generateTasksFromPrompt(_result) {
const content = `# Tasks
## Task List
- [ ] Define requirements
- [ ] Create implementation plan
- [ ] Break down into tasks
## Dependencies Graph
\`\`\`mermaid
graph TD
A[Define Requirements] --> B[Create Plan]
B --> C[Break Down Tasks]
\`\`\`
`;
return { name: "tasks.md", content, format: "markdown" };
}
/**
* Extract title from SessionState.
*
* @param result - The session state
* @returns Title string
* @private
*/
extractTitle(result) {
if (result.config?.goal) {
return result.config.goal;
}
if (result.context?.title && typeof result.context.title === "string") {
return result.context.title;
}
return "Feature";
}
/**
* Extract overview from SessionState.
*
* @param result - The session state
* @returns Overview text
* @private
*/
extractOverview(result) {
if (result.context?.overview &&
typeof result.context.overview === "string") {
return result.context.overview;
}
if (result.config?.goal) {
return result.config.goal;
}
return "To be defined";
}
/**
* Extract functional requirements from SessionState.
*
* @param result - The session state
* @returns Formatted requirements list
* @private
*/
extractFunctionalRequirements(result) {
if (result.config?.requirements &&
Array.isArray(result.config.requirements)) {
return result.config.requirements
.map((req, i) => `${i + 1}. ${req}`)
.join("\n");
}
if (result.context?.requirements &&
Array.isArray(result.context.requirements)) {
return result.context.requirements
.map((req, i) => `${i + 1}. ${req}`)
.join("\n");
}
return "To be defined";
}
/**
* Extract non-functional requirements from SessionState.
*
* @param result - The session state
* @returns Formatted non-functional requirements
* @private
*/
extractNonFunctionalRequirements(result) {
if (result.context?.nonFunctionalRequirements &&
Array.isArray(result.context.nonFunctionalRequirements)) {
return result.context.nonFunctionalRequirements
.map((req, i) => `${i + 1}. ${req}`)
.join("\n");
}
return "To be defined";
}
/**
* Extract constraints from SessionState.
*
* @param result - The session state
* @returns Formatted constraints list
* @private
*/
extractConstraints(result) {
if (result.config?.constraints &&
Array.isArray(result.config.constraints)) {
return result.config.constraints
.map((c) => `- ${c}`)
.join("\n");
}
if (result.context?.constraints &&
Array.isArray(result.context.constraints)) {
return result.context.constraints
.map((c) => `- ${c}`)
.join("\n");
}
return "None specified";
}
/**
* Extract success criteria from SessionState.
*
* @param result - The session state
* @returns Formatted success criteria
* @private
*/
extractSuccessCriteria(result) {
if (result.context?.successCriteria &&
Array.isArray(result.context.successCriteria)) {
return result.context.successCriteria
.map((c) => `- [ ] ${c}`)
.join("\n");
}
return "To be defined";
}
/**
* Extract phases from SessionState.
*
* @param result - The session state
* @returns Formatted phases description
* @private
*/
extractPhases(result) {
if (result.phases) {
const phaseEntries = Object.entries(result.phases);
if (phaseEntries.length > 0) {
return phaseEntries
.map(([phaseId, phaseData], i) => {
return `### Phase ${i + 1}: ${phaseId}
${typeof phaseData === "string" ? phaseData : "In progress"}
`;
})
.join("\n");
}
}
if (result.history && result.history.length > 0) {
const uniquePhases = [...new Set(result.history.map((h) => h.to))].filter(Boolean);
return uniquePhases
.map((phase, i) => `### Phase ${i + 1}: ${phase}
Completed or in progress
`)
.join("\n");
}
return "To be defined based on requirements";
}
/**
* Extract timeline from SessionState.
*
* @param result - The session state
* @returns Formatted timeline
* @private
*/
extractTimeline(result) {
if (result.context?.timeline &&
typeof result.context.timeline === "string") {
return result.context.timeline;
}
if (result.history && result.history.length > 0) {
const transitions = result.history
.filter((h) => h.timestamp)
.map((h) => `- ${h.timestamp}: Transitioned to ${h.to}`)
.join("\n");
return transitions || "To be estimated";
}
return "To be estimated";
}
/**
* Extract dependencies from SessionState.
*
* @param result - The session state
* @returns Formatted dependencies
* @private
*/
extractDependencies(result) {
if (result.context?.dependencies &&
Array.isArray(result.context.dependencies)) {
return result.context.dependencies
.map((d) => `- ${d}`)
.join("\n");
}
return "To be identified";
}
/**
* Extract risks from SessionState.
*
* @param result - The session state
* @returns Formatted risks
* @private
*/
extractRisks(result) {
if (result.context?.risks && Array.isArray(result.context.risks)) {
return result.context.risks.map((r) => `- ${r}`).join("\n");
}
return "To be assessed";
}
/**
* Extract tasks from SessionState.
*
* @param result - The session state
* @returns Formatted task list
* @private
*/
extractTasks(result) {
if (result.context?.tasks && Array.isArray(result.context.tasks)) {
return result.context.tasks
.map((t) => {
const estimate = t.estimate ? ` (${t.estimate})` : "";
return `- [ ] ${t.title}${estimate}`;
})
.join("\n");
}
return "- [ ] Define requirements\n- [ ] Create implementation plan\n- [ ] Break down into tasks";
}
/**
* Generate Mermaid dependency graph from SessionState.
*
* @param result - The session state
* @returns Mermaid diagram syntax
* @private
*/
generateDependencyGraph(result) {
if (result.context?.tasks && Array.isArray(result.context.tasks)) {
const tasks = result.context.tasks;
const nodes = tasks
.map((t, i) => {
const id = t.id || `T${i + 1}`;
return ` ${id}[${t.title}]`;
})
.join("\n");
const edges = tasks
.flatMap((t, i) => {
const id = t.id || `T${i + 1}`;
if (t.dependencies && t.dependencies.length > 0) {
return t.dependencies.map((dep) => ` ${dep} --> ${id}`);
}
return [];
})
.join("\n");
if (edges) {
return `graph TD\n${nodes}\n${edges}`;
}
}
// Default simple graph
return `graph TD
A[Define Requirements] --> B[Create Plan]
B --> C[Break Down Tasks]`;
}
/**
* Type guard for SessionState.
*
* @param result - The value to check
* @returns True if result is a SessionState
* @private
*/
isSessionState(result) {
return (typeof result === "object" &&
result !== null &&
"id" in result &&
"phase" in result &&
"context" in result &&
"history" in result);
}
/**
* 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=sdd-strategy.js.map