mcp-ai-agent-guidelines
Version:
A comprehensive Model Context Protocol server providing advanced tools, resources, and prompts for implementing AI agent best practices
413 lines • 16.8 kB
JavaScript
/**
* Spec Validator - validates specifications against constitutional constraints
*
* @module strategies/speckit/spec-validator
*/
/**
* Validates specification documents against a design "constitution"
* composed of principles, constraints, architecture rules, and design principles.
*
* Typical usage is to create an instance for a given {@link Constitution} and then
* call {@link SpecValidator#validate} for each {@link SpecContent} you want to
* check for alignment and coverage.
*
* @example
* ```ts
* import { SpecValidator } from "./spec-validator.js";
* import type { Constitution, SpecContent } from "./types.js";
*
* const constitution: Constitution = loadConstitutionSomehow();
* const validator = new SpecValidator(constitution);
*
* const spec: SpecContent = {
* title: "Payments Service ADR",
* overview: "Describes the architecture and design decisions for the payments service.",
* };
*
* const result = validator.validate(spec);
*
* if (!result.valid) {
* // Inspect result.issues and result.score to understand gaps and violations.
* }
* ```
*/
export class SpecValidator {
constitution;
constructor(constitution) {
this.constitution = constitution;
}
/**
* Validate a specification against the constitution
*
* @param spec - The specification content to validate
* @returns Validation result with score and issues
*/
validate(spec) {
const issues = [];
let checkedConstraints = 0;
let passedConstraints = 0;
// Validate against principles
for (const principle of this.constitution.principles ?? []) {
checkedConstraints++;
const issue = this.checkPrinciple(spec, principle);
if (issue) {
issues.push(issue);
}
else {
passedConstraints++;
}
}
// Validate against constraints
for (const constraint of this.constitution.constraints ?? []) {
checkedConstraints++;
const issue = this.checkConstraint(spec, constraint);
if (issue) {
issues.push(issue);
}
else {
passedConstraints++;
}
}
// Validate against architecture rules
for (const rule of this.constitution.architectureRules ?? []) {
checkedConstraints++;
const issue = this.checkArchitectureRule(spec, rule);
if (issue) {
issues.push(issue);
}
else {
passedConstraints++;
}
}
// Validate against design principles
for (const principle of this.constitution.designPrinciples ?? []) {
checkedConstraints++;
const issue = this.checkDesignPrinciple(spec, principle);
if (issue) {
issues.push(issue);
}
else {
passedConstraints++;
}
}
const score = checkedConstraints > 0
? Math.round((passedConstraints / checkedConstraints) * 100)
: 100;
return {
valid: issues.filter((i) => i.severity === "error").length === 0,
score,
issues,
checkedConstraints,
passedConstraints,
};
}
/**
* Check if spec aligns with a principle
*
* @param spec - The specification content
* @param principle - The principle to check
* @returns ValidationIssue if violation found, null otherwise
*/
checkPrinciple(spec, principle) {
// Reserved for future validation logic that may need to analyze content
// Currently placeholder checks are used; more sophisticated checks can be
// added based on specific principle types and requirements
const _content = spec.rawMarkdown || spec.overview || "";
// Example check for principle alignment
// TODO: Replace hardcoded ID checks with a validation rule registry
// that maps principle types/titles to validation functions
if (principle.id === "1" && !spec.title) {
return {
severity: "warning",
code: `P${principle.id}-VIOLATION`,
message: `Principle "${principle.title}" may not be fully addressed`,
constraint: {
id: principle.id,
type: "principle",
description: principle.description,
},
suggestion: "Ensure spec has a clear title",
};
}
return null; // No issue found
}
/**
* Check if spec violates a constraint
*
* @param spec - The specification content
* @param constraint - The constraint to check
* @returns ValidationIssue if violation found, null otherwise
*/
checkConstraint(spec, constraint) {
const content = spec.rawMarkdown || spec.overview || "";
// Check based on constraint severity
const severity = constraint.severity === "must" ? "error" : "warning";
// Example constraint checks
// TODO: Replace hardcoded ID checks with a validation rule registry
// TypeScript strict mode check (C1)
if (constraint.id === "C1" && content.toLowerCase().includes("any type")) {
return {
severity,
code: `${constraint.id}-VIOLATION`,
message: `Constraint "${constraint.title}" violated: avoid 'any' types`,
constraint: {
id: constraint.id,
type: "constraint",
description: constraint.description,
},
suggestion: "Use explicit TypeScript types instead of 'any'",
};
}
// ESM module system check (C2)
// Use regex to match actual require() function calls, not just the word "require"
const requirePattern = /\brequire\s*\(/;
if (constraint.id === "C2" &&
requirePattern.test(content) &&
!content.includes("// legacy")) {
return {
severity,
code: `${constraint.id}-VIOLATION`,
message: `Constraint "${constraint.title}" violated: avoid CommonJS require()`,
constraint: {
id: constraint.id,
type: "constraint",
description: constraint.description,
},
suggestion: "Use ESM imports with .js extensions",
};
}
return null; // No issue found
}
/**
* Check architecture compliance
*
* @param spec - The specification content
* @param rule - The architecture rule to check
* @returns ValidationIssue if violation found, null otherwise
*/
checkArchitectureRule(spec, rule) {
const content = spec.rawMarkdown || spec.overview || "";
// Example architecture rule check
// TODO: Replace hardcoded ID checks with a validation rule registry
// Layer dependency check (AR1)
if (rule.id === "AR1") {
// Use more precise pattern to detect invalid dependency flow
// Matches "domain → ... → gateway" pattern indicating wrong order
const invalidDependencyPattern = /domain\s*→.*→\s*gateway/i;
if (invalidDependencyPattern.test(content)) {
return {
severity: "error",
code: `${rule.id}-VIOLATION`,
message: `Architecture rule "${rule.title}" violated: invalid layer dependency`,
constraint: {
id: rule.id,
type: "architecture-rule",
description: rule.description,
},
suggestion: "Follow proper layer dependencies: MCPServer → Gateway → Domain",
};
}
}
return null; // No issue found
}
/**
* Check design principle compliance
*
* @param spec - The specification content
* @param principle - The design principle to check
* @returns ValidationIssue if violation found, null otherwise
*/
checkDesignPrinciple(spec, principle) {
const content = spec.rawMarkdown || spec.overview || "";
// Example design principle check
// Single responsibility check (DP1)
if (principle.id === "DP1") {
// Check if spec describes multiple distinct responsibilities
const responsibilityKeywords = ["and also", "in addition", "plus"];
const hasMultipleResponsibilities = responsibilityKeywords.some((kw) => content.toLowerCase().includes(kw));
if (hasMultipleResponsibilities && spec.objectives?.length) {
const distinctObjectives = spec.objectives.length > 3;
if (distinctObjectives) {
return {
severity: "info",
code: `${principle.id}-VIOLATION`,
message: `Design principle "${principle.title}": spec may have too many responsibilities`,
constraint: {
id: principle.id,
type: "design-principle",
description: principle.description,
},
suggestion: "Consider splitting into focused, single-purpose specs",
};
}
}
}
return null; // No issue found
}
/**
* Generate a comprehensive validation report
*
* @param spec - The specification content to validate
* @returns Comprehensive validation report with metrics and categorization
*/
generateReport(spec) {
const result = this.validate(spec);
return {
valid: result.valid,
score: result.score,
timestamp: new Date().toISOString(),
metrics: {
total: result.checkedConstraints,
passed: result.passedConstraints,
failed: result.issues.filter((i) => i.severity === "error").length,
warnings: result.issues.filter((i) => i.severity === "warning").length,
info: result.issues.filter((i) => i.severity === "info").length,
},
byType: this.categorizeResults(result),
issues: result.issues,
recommendations: this.generateRecommendations(result),
};
}
/**
* Categorize validation results by constraint type
*
* @param result - The validation result
* @returns Breakdown of results by constraint type
*/
categorizeResults(result) {
const byType = {
principles: { checked: 0, passed: 0 },
constraints: { checked: 0, passed: 0 },
architectureRules: { checked: 0, passed: 0 },
designPrinciples: { checked: 0, passed: 0 },
};
// Count checks by type
byType.principles.checked = this.constitution.principles?.length ?? 0;
byType.constraints.checked = this.constitution.constraints?.length ?? 0;
byType.architectureRules.checked =
this.constitution.architectureRules?.length ?? 0;
byType.designPrinciples.checked =
this.constitution.designPrinciples?.length ?? 0;
// Count passes by analyzing issues
const issuesByType = {
principle: 0,
constraint: 0,
"architecture-rule": 0,
"design-principle": 0,
};
for (const issue of result.issues) {
if (issue.constraint?.type) {
issuesByType[issue.constraint.type]++;
}
}
byType.principles.passed =
byType.principles.checked - issuesByType.principle;
byType.constraints.passed =
byType.constraints.checked - issuesByType.constraint;
byType.architectureRules.passed =
byType.architectureRules.checked - issuesByType["architecture-rule"];
byType.designPrinciples.passed =
byType.designPrinciples.checked - issuesByType["design-principle"];
return byType;
}
/**
* Generate recommendations based on validation results
*
* @param result - The validation result
* @returns List of recommendations for improvement
*/
generateRecommendations(result) {
const recommendations = [];
// Recommend addressing errors first
const errorCount = result.issues.filter((i) => i.severity === "error").length;
if (errorCount > 0) {
recommendations.push(`Address ${errorCount} critical error${errorCount > 1 ? "s" : ""} to improve spec validity`);
}
// Recommend addressing warnings
const warningCount = result.issues.filter((i) => i.severity === "warning").length;
if (warningCount > 0) {
recommendations.push(`Review ${warningCount} warning${warningCount > 1 ? "s" : ""} to ensure best practices`);
}
// Score-based recommendations
if (result.score < 70) {
recommendations.push("Validation score is below 70. Consider a thorough review of all constitutional requirements");
}
else if (result.score < 85) {
recommendations.push("Validation score is good but can be improved. Review remaining issues");
}
// Return undefined if no recommendations
return recommendations.length > 0 ? recommendations : undefined;
}
/**
* Format validation report as GitHub-flavored markdown
*
* @param report - The validation report to format
* @returns Markdown-formatted report
*/
formatReportAsMarkdown(report) {
const lines = [];
lines.push("# Validation Report\n");
lines.push(`**Generated**: ${report.timestamp}\n`);
lines.push(`**Status**: ${report.valid ? "✅ Valid" : "❌ Invalid"}\n`);
lines.push(`**Score**: ${report.score}/100\n\n`);
lines.push("## Summary\n\n");
lines.push("| Metric | Count |\n");
lines.push("|--------|-------|\n");
lines.push(`| Total Constraints | ${report.metrics.total} |\n`);
lines.push(`| Passed | ${report.metrics.passed} |\n`);
lines.push(`| Errors | ${report.metrics.failed} |\n`);
lines.push(`| Warnings | ${report.metrics.warnings} |\n`);
lines.push(`| Info | ${report.metrics.info} |\n\n`);
if (report.issues.length > 0) {
lines.push("## Issues\n\n");
for (const severity of ["error", "warning", "info"]) {
const issues = report.issues.filter((i) => i.severity === severity);
if (issues.length > 0) {
const icon = { error: "❌", warning: "⚠️", info: "ℹ️" }[severity];
lines.push(`### ${icon} ${severity.charAt(0).toUpperCase() + severity.slice(1)}s\n\n`);
for (const issue of issues) {
lines.push(`- **${issue.code}**: ${issue.message}\n`);
if (issue.constraint) {
lines.push(` - Constraint: ${issue.constraint.id} (${issue.constraint.type})\n`);
}
if (issue.suggestion) {
lines.push(` - Suggestion: ${issue.suggestion}\n`);
}
}
lines.push("\n");
}
}
}
if (report.recommendations && report.recommendations.length > 0) {
lines.push("## Recommendations\n\n");
for (const r of report.recommendations) {
lines.push(`- ${r}\n`);
}
}
return lines.join("");
}
}
/**
* Factory function to create a SpecValidator instance
*
* Convenience factory for instantiating a {@link SpecValidator}.
* Equivalent to `new SpecValidator(constitution)`.
*
* @param constitution - The constitution to validate against
* @returns A new SpecValidator instance
*
* @example
* ```ts
* import { createSpecValidator } from "./spec-validator.js";
* import { parseConstitution } from "./constitution-parser.js";
*
* const constitution = parseConstitution(constitutionMarkdown);
* const validator = createSpecValidator(constitution);
*
* const result = validator.validate(mySpec);
* ```
*/
export function createSpecValidator(constitution) {
return new SpecValidator(constitution);
}
//# sourceMappingURL=spec-validator.js.map