Skip to main content
Glama
referenceUtils.ts11.8 kB
import * as yaml from 'yaml'; import * as fs from 'fs'; import * as path from 'path'; import { ReferenceInfo, DefinitionInfo, CircularReference } from '../types.js'; /** * Information about external reference files */ export interface ExternalReferenceInfo { key: string; path: string; description?: string; } /** * Cache for loaded external documents */ const externalDocumentCache = new Map<string, yaml.Document>(); /** * Finds all LAML references in document */ export function findAllReferences(document: yaml.Document): ReferenceInfo[] { const references: ReferenceInfo[] = []; yaml.visit(document, { Scalar(_, node) { if (typeof node.value === 'string') { // Check for direct LAML references (strings starting with *) // Skip escaped references (starting with \*) if (node.value.startsWith('\\*')) { // Skip escaped asterisks } else if (node.value.startsWith('*')) { references.push({ path: node.value, line: node.range?.[0] }); } else { // Search for LAML references within multiline strings // Find all potential matches first, then filter out escaped ones const allMatches = node.value.match(/\\?\*[a-zA-Z][a-zA-Z0-9]*(?:\.[a-zA-Z][a-zA-Z0-9]*)*/g); if (allMatches) { for (const match of allMatches) { // Skip escaped references (starting with \*) if (!match.startsWith('\\*')) { references.push({ path: match, line: node.range?.[0] }); } } } } } } }); return references; } /** * Finds all property definitions in document */ export function findAllDefinitions(document: yaml.Document): DefinitionInfo[] { const definitions: DefinitionInfo[] = []; if (!document.contents || !yaml.isMap(document.contents)) { return definitions; } // Traverse document to find all property definitions const visitMap = (map: yaml.YAMLMap, currentPath: string[] = []) => { for (const item of map.items) { if (yaml.isScalar(item.key) && typeof item.key.value === 'string') { const keyName = item.key.value; const fullPath = [...currentPath, keyName].join('.'); definitions.push({ path: fullPath, line: item.key.range?.[0] }); // Recursively check nested maps if (yaml.isMap(item.value)) { visitMap(item.value, [...currentPath, keyName]); } } } }; visitMap(document.contents); return definitions; } /** * Checks if reference format is valid */ export function isValidReference(path: string): boolean { if (!path.startsWith('*')) return false; const dotPath = path.slice(1); // Check for external reference format: $refs.externalKey.path if (dotPath.startsWith('$refs.')) { const externalPath = dotPath.slice(6); // Remove '$refs.' // Must have at least: externalKey.section return /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/.test(externalPath); } // Check for internal reference format: section.property return /^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/.test(dotPath); } /** * Checks if reference target exists in document */ export function referenceExists(document: yaml.Document, refPath: string): boolean { const path = refPath.slice(1).split('.'); if (!document.contents || !yaml.isMap(document.contents)) { return false; } let current: unknown = document.contents; for (const segment of path) { if (!yaml.isMap(current)) { return false; } const item = current.items.find(item => yaml.isScalar(item.key) && item.key.value === segment ); if (!item) { return false; } current = item.value; } return true; } /** * Finds circular references in document */ export function findCircularReferences(document: yaml.Document, references: ReferenceInfo[]): CircularReference[] { const circularRefs: CircularReference[] = []; // Build a dependency graph: what each property references const dependencyGraph = new Map<string, string>(); // Extract all property->reference mappings for (const ref of references) { const refTarget = ref.path.slice(1); // Remove '*' const sourceProperty = findPropertyContainingReference(document, ref.path); if (sourceProperty && referenceExists(document, ref.path)) { dependencyGraph.set(sourceProperty, refTarget); } } // Detect cycles using DFS const visited = new Set<string>(); const recursionStack = new Set<string>(); const detectCycle = (node: string, path: string[]): boolean => { if (recursionStack.has(node)) { // Found a cycle - create the cycle path const cycleStart = path.indexOf(node); const cycle = path.slice(cycleStart).concat([node]); circularRefs.push({ cycle }); return true; } if (visited.has(node)) { return false; } visited.add(node); recursionStack.add(node); const target = dependencyGraph.get(node); if (target) { if (detectCycle(target, [...path, node])) { return true; } } recursionStack.delete(node); return false; }; // Check for cycles starting from each node for (const node of dependencyGraph.keys()) { if (!visited.has(node)) { detectCycle(node, []); } } return circularRefs; } /** * Finds which property contains the given reference */ function findPropertyContainingReference(document: yaml.Document, referencePath: string): string | null { // Find which property contains this reference if (!document.contents || !yaml.isMap(document.contents)) { return null; } const findInMap = (map: yaml.YAMLMap, currentPath: string[] = []): string | null => { for (const item of map.items) { if (yaml.isScalar(item.key) && typeof item.key.value === 'string') { const keyName = item.key.value; const fullPath = [...currentPath, keyName].join('.'); if (yaml.isScalar(item.value) && typeof item.value.value === 'string' && item.value.value === referencePath) { return fullPath; } if (yaml.isMap(item.value)) { const found = findInMap(item.value, [...currentPath, keyName]); if (found) return found; } } } return null; }; return findInMap(document.contents); } /** * Extracts $refs section from document */ export function extractRefsSection(document: yaml.Document): ExternalReferenceInfo[] { const refs: ExternalReferenceInfo[] = []; if (!document.contents || !yaml.isMap(document.contents)) { return refs; } const refsItem = document.contents.items.find(item => yaml.isScalar(item.key) && item.key.value === '$refs' ); if (!refsItem || !yaml.isMap(refsItem.value)) { return refs; } const refsMap = refsItem.value; for (const item of refsMap.items) { if (yaml.isScalar(item.key) && typeof item.key.value === 'string') { const key = item.key.value; if (yaml.isMap(item.value)) { const refMap = item.value; const pathItem = refMap.items.find(i => yaml.isScalar(i.key) && i.key.value === 'path' ); const descItem = refMap.items.find(i => yaml.isScalar(i.key) && i.key.value === 'description' ); if (pathItem && yaml.isScalar(pathItem.value) && typeof pathItem.value.value === 'string') { refs.push({ key, path: pathItem.value.value, description: descItem && yaml.isScalar(descItem.value) && typeof descItem.value.value === 'string' ? descItem.value.value : undefined }); } } } } return refs; } /** * Loads external LAML document from file path */ export function loadExternalDocument(filePath: string, baseDir?: string): yaml.Document | null { let resolvedPath: string; if (baseDir) { // Apply same baseDir logic as in validateReferences.ts let adjustedBaseDir = baseDir; if (baseDir.endsWith('.cursor/rules') || baseDir.includes('.cursor/rules/')) { // Navigate up to project root (assuming .cursor/rules structure) const cursorIndex = baseDir.lastIndexOf('.cursor'); adjustedBaseDir = cursorIndex > 0 ? baseDir.substring(0, cursorIndex) : baseDir; } resolvedPath = path.resolve(adjustedBaseDir, filePath); } else { resolvedPath = path.resolve(filePath); } // Check cache first if (externalDocumentCache.has(resolvedPath)) { return externalDocumentCache.get(resolvedPath)!; } try { if (!fs.existsSync(resolvedPath)) { return null; } const content = fs.readFileSync(resolvedPath, 'utf-8'); // Extract YAML from markdown code blocks if necessary let yamlContent = content; // Try full yaml block first (with closing ```) let yamlBlockMatch = content.match(/```ya?ml\s*\n([\s\S]*?)\n```/); if (yamlBlockMatch) { yamlContent = yamlBlockMatch[1]; } else { // Try yaml block without closing ``` (for .mdc files) yamlBlockMatch = content.match(/```ya?ml\s*\n([\s\S]*)/); if (yamlBlockMatch) { yamlContent = yamlBlockMatch[1]; } } const document = yaml.parseDocument(yamlContent); externalDocumentCache.set(resolvedPath, document); return document; } catch (error) { return null; } } /** * Checks if reference is an external reference (starts with *$refs.) */ export function isExternalReference(refPath: string, externalRefs: ExternalReferenceInfo[]): { isExternal: boolean; refKey?: string; localPath?: string } { if (!refPath.startsWith('*$refs.')) { return { isExternal: false }; } const path = refPath.slice(7); // Remove '*$refs.' const segments = path.split('.'); if (segments.length < 2) { return { isExternal: false }; } const refKey = segments[0]; const localPath = segments.slice(1).join('.'); // Check if the external key exists in $refs const hasKey = externalRefs.some(ref => ref.key === refKey); if (hasKey) { return { isExternal: true, refKey, localPath }; } return { isExternal: false }; } /** * Validates external reference */ export function validateExternalReference( refPath: string, externalRefs: ExternalReferenceInfo[], baseDir?: string ): { isValid: boolean; error?: string } { const { isExternal, refKey, localPath } = isExternalReference(refPath, externalRefs); if (!isExternal || !refKey || !localPath) { return { isValid: false, error: 'Not a valid external reference' }; } const externalRef = externalRefs.find(ref => ref.key === refKey); if (!externalRef) { return { isValid: false, error: `External reference key '${refKey}' not found in $refs section` }; } const externalDoc = loadExternalDocument(externalRef.path, baseDir); if (!externalDoc) { return { isValid: false, error: `Cannot load external file: ${externalRef.path}` }; } const localRefPath = '*' + localPath; if (!referenceExists(externalDoc, localRefPath)) { return { isValid: false, error: `Reference '${localPath}' not found in external file ${externalRef.path}` }; } return { isValid: true }; } /** * Unescapes asterisk characters in value (converts \* to *) */ export function unescapeAsterisks(value: string): string { return value.replace(/\\\*/g, '*'); } /** * Checks if value contains escaped asterisks */ export function hasEscapedAsterisks(value: string): boolean { return value.includes('\\*'); }

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/EgorKluch/mcp-laml'

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