Skip to main content
Glama
validators.ts6.09 kB
/** * Spec Kit Validators * * Validation rules for checking specification quality and completeness. * Implements GitHub Spec Kit's /analyze command patterns. */ export interface ValidationIssue { rule: string; message: string; location?: string; suggestion?: string; } export interface ValidationReport { errors: ValidationIssue[]; warnings: ValidationIssue[]; passed: boolean; } /** * Check if spec has all required sections */ export function checkCompleteness(specContent: string): ValidationIssue[] { const issues: ValidationIssue[] = []; const requiredSections = [ { name: 'Goals', pattern: /##\s+Goals?/i }, { name: 'Acceptance Criteria', pattern: /##\s+(Acceptance\s+Criteria|Success\s+Criteria)/i }, { name: 'Requirements', pattern: /##\s+(Requirements?|Features?)/i }, ]; for (const section of requiredSections) { if (!section.pattern.test(specContent)) { issues.push({ rule: 'completeness', message: `Missing required section: ${section.name}`, suggestion: `Add a "## ${section.name}" section to your spec.md` }); } } return issues; } /** * Check if features have testable acceptance criteria */ export function checkAcceptanceCriteria(specContent: string): ValidationIssue[] { const issues: ValidationIssue[] = []; // Look for acceptance criteria section const acceptanceMatch = specContent.match(/##\s+(Acceptance\s+Criteria|Success\s+Criteria)([\s\S]*?)(?=##|$)/i); if (!acceptanceMatch) { return [{ rule: 'acceptance_criteria', message: 'No acceptance criteria section found', suggestion: 'Add "## Acceptance Criteria" section with testable criteria' }]; } const acceptanceContent = acceptanceMatch[2]; // Check for when/then patterns or bullet points const hasWhenThen = /when\s+.*\s+then/i.test(acceptanceContent); const hasBullets = /^[\s]*[-*]\s+/m.test(acceptanceContent); const hasChecklist = /^[\s]*-\s+\[[ x]\]/m.test(acceptanceContent); if (!hasWhenThen && !hasBullets && !hasChecklist) { issues.push({ rule: 'acceptance_criteria', message: 'Acceptance criteria section exists but has no testable criteria', location: 'Acceptance Criteria section', suggestion: 'Add specific criteria using "when/then" format or bullet points' }); } return issues; } /** * Check for unresolved clarification markers */ export function checkClarifications(specContent: string): ValidationIssue[] { const issues: ValidationIssue[] = []; // Find all [NEEDS CLARIFICATION] markers const clarificationPattern = /\[NEEDS CLARIFICATION:?\s*([^\]]*)\]/gi; const matches = Array.from(specContent.matchAll(clarificationPattern)); for (const match of matches) { const clarificationId = match[1]?.trim() || 'unknown'; issues.push({ rule: 'clarifications', message: `Unresolved clarification: ${clarificationId}`, location: `Line containing: ${match[0]}`, suggestion: `Use clarify_resolve to address ${clarificationId || 'this clarification'}` }); } return issues; } /** * Check for premature implementation details (HOW in WHAT sections) */ export function checkPrematureImplementation(specContent: string): ValidationIssue[] { const issues: ValidationIssue[] = []; // Extract sections that should focus on WHAT (Goals, Requirements, Features) const whatSections = specContent.match(/##\s+(Goals?|Requirements?|Features?)([\s\S]*?)(?=##|$)/gi); if (!whatSections) { return issues; } for (const section of whatSections) { const sectionName = section.match(/##\s+([\w\s]+)/)?.[1] || 'Unknown section'; // Check for code blocks (implementation details) if (/```[\s\S]*?```/.test(section)) { issues.push({ rule: 'premature_implementation', message: `Code block found in ${sectionName} section`, location: sectionName, suggestion: 'Move implementation details to plan.md or remove them. Specs should focus on WHAT, not HOW.' }); } // Check for technology/library mentions (common indicator of HOW) const techKeywords = /\b(React|Vue|Angular|Express|Django|Flask|PostgreSQL|MongoDB|Redis|Docker|Kubernetes)\b/g; const techMatches = section.match(techKeywords); if (techMatches && techMatches.length > 2) { issues.push({ rule: 'premature_implementation', message: `Multiple technology references in ${sectionName} section: ${techMatches.slice(0, 3).join(', ')}`, location: sectionName, suggestion: 'Specs should describe user needs, not implementation technologies. Move tech stack to plan.md.' }); } // Check for file paths or directory structures if (/src\/|lib\/|components\/|routes\/|models\//i.test(section)) { issues.push({ rule: 'premature_implementation', message: `File/directory paths found in ${sectionName} section`, location: sectionName, suggestion: 'Remove file structure details. Focus on what users need, not how code is organized.' }); } } return issues; } /** * Run all validation checks and generate a report */ export function validateSpec( specContent: string, checks: { completeness?: boolean; acceptanceCriteria?: boolean; clarifications?: boolean; prematureImplementation?: boolean; } = {} ): ValidationReport { const errors: ValidationIssue[] = []; const warnings: ValidationIssue[] = []; // Default: run all checks const runAll = Object.values(checks).every(v => v === undefined); if (runAll || checks.completeness) { errors.push(...checkCompleteness(specContent)); } if (runAll || checks.acceptanceCriteria) { warnings.push(...checkAcceptanceCriteria(specContent)); } if (runAll || checks.clarifications) { warnings.push(...checkClarifications(specContent)); } if (runAll || checks.prematureImplementation) { warnings.push(...checkPrematureImplementation(specContent)); } return { errors, warnings, passed: errors.length === 0 }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/flight505/MCP_DinCoder'

If you have feedback or need assistance with the MCP directory API, please join our Discord server