mcp-ai-agent-guidelines
Version:
A comprehensive Model Context Protocol server providing advanced tools, resources, and prompts for implementing AI agent best practices
124 lines • 4.67 kB
JavaScript
/**
* Constitution parser - extracts structured data from CONSTITUTION.md
*
* @module strategies/speckit/constitution-parser
*/
// Pattern for numbered principles: ### 1. Title
const PRINCIPLE_PATTERN = /^### (\d+)\.\s+(.+)$/gm;
// Pattern for constraints: ### C1: Title
const CONSTRAINT_PATTERN = /^### (C\d+):\s+(.+)$/gm;
// Pattern for architecture rules: ### AR1: Title
const ARCH_RULE_PATTERN = /^### (AR\d+):\s+(.+)$/gm;
// Pattern for design principles: ### DP1: Title
const DESIGN_PRINCIPLE_PATTERN = /^### (DP\d+):\s+(.+)$/gm;
// Pattern to find next heading boundary (level 2 or 3)
const NEXT_HEADING_PATTERN = /^##(?:#)?\s+/gm;
/**
* Parse a CONSTITUTION.md document into structured data
*
* @param content - The markdown content of CONSTITUTION.md
* @returns Structured constitution data
*/
export function parseConstitution(content) {
return {
principles: extractItems(content, PRINCIPLE_PATTERN, "principle"),
constraints: extractItems(content, CONSTRAINT_PATTERN, "constraint"),
architectureRules: extractItems(content, ARCH_RULE_PATTERN, "architecture-rule"),
designPrinciples: extractItems(content, DESIGN_PRINCIPLE_PATTERN, "design-principle"),
metadata: extractMetadata(content),
};
}
/**
* Generic extraction function for any item type
*
* @param content - The markdown content
* @param pattern - The regex pattern to match items
* @param type - The type identifier for the items
* @returns Array of extracted items
*/
function extractItems(content, pattern, type) {
const items = [];
const matches = Array.from(content.matchAll(pattern));
for (let i = 0; i < matches.length; i++) {
const match = matches[i];
const [, id, title] = match;
const startIndex = match.index ?? 0;
// Find the end index: either the next heading or end of content
const endIndex = findNextHeadingIndex(content, startIndex + 1);
const description = extractSectionContent(content, startIndex, endIndex);
items.push({
id,
title,
description,
type,
});
}
return items;
}
/**
* Find the index of the next heading (level 2 or 3) after the given start position
*
* @param content - The full markdown content
* @param startIndex - Position to start searching from
* @returns Index of next heading, or content.length if none found
*/
function findNextHeadingIndex(content, startIndex) {
// Reset the regex to start from the beginning
const headingPattern = new RegExp(NEXT_HEADING_PATTERN.source, "gm");
// Start searching from startIndex
headingPattern.lastIndex = startIndex;
const nextHeading = headingPattern.exec(content);
return nextHeading?.index ?? content.length;
}
/**
* Extract content from a section between start and end indices
*
* @param content - The full markdown content
* @param startIndex - Start position in content
* @param endIndex - End position in content (exclusive)
* @returns Extracted and trimmed section content
*/
function extractSectionContent(content, startIndex, endIndex) {
// Extract the section content
const section = content.substring(startIndex, endIndex);
// Find the end of the first line (the heading)
const firstNewline = section.indexOf("\n");
if (firstNewline === -1) {
return "";
}
// Get everything after the heading
let sectionContent = section.substring(firstNewline + 1).trim();
// Remove trailing separator lines (---) if present
sectionContent = sectionContent.replace(/\n---\s*$/, "");
return sectionContent;
}
/**
* Extract metadata from document front matter or headers
*
* @param content - The markdown content
* @returns Metadata object or undefined
*/
function extractMetadata(content) {
const metadata = {};
// Extract title from first H1 heading
const titleMatch = content.match(/^#\s+(.+)$/m);
if (titleMatch) {
metadata.title = titleMatch[1];
}
// Extract "applies to" information from blockquote at the start
const blockquoteMatch = content.match(/^>\s+(.+)$/m);
if (blockquoteMatch) {
const blockquoteText = blockquoteMatch[1];
// If the blockquote mentions a version, store the full text as appliesTo
const versionPattern = /\bv\d+(?:\.\d+)*\b/i;
if (versionPattern.test(blockquoteText)) {
metadata.appliesTo = blockquoteText.trim();
}
}
// Return undefined if no metadata was found
if (Object.keys(metadata).length === 0) {
return undefined;
}
return metadata;
}
//# sourceMappingURL=constitution-parser.js.map