UNPKG

mcard-js

Version:

MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers

329 lines 10.7 kB
/** * CLM Introspection - Recursive CLM Hierarchy Analysis * * This module provides introspection capabilities for CLM (Cubical Logic Model) * hierarchies, enabling formal inspection of recursive CLM composition. * * ## Recursive CLM Composition * * CLMs can compose recursively, creating hierarchies of specifications: * * ``` * Root CLM (Chapter) * ├── Sub-CLM A (referenced PCard) * │ ├── Leaf CLM X * │ └── Leaf CLM Y * └── Sub-CLM B (referenced PCard) * └── Leaf CLM Z * ``` * * This module enables: * - Traversal of CLM dependency graphs * - Detection of circular dependencies * - Extraction of CLM metadata at any level * - Composition path tracing * * ## SMC Structure Preservation * * The introspection respects the Symmetric Monoidal Category structure: * - Composition paths preserve associativity * - Parallel compositions are correctly identified * - Identity CLMs are recognized * * @see CLM_MCard_REPL_Implementation.md §8: Recursive CLM Composition * @see PTR_MCard_CLM_Recent_Developments_Jan2026.md §4.2: Recursive Structure */ import { parse } from 'yaml'; /** * Type classification for CLM nodes */ export var CLMType; (function (CLMType) { CLMType["CHAPTER"] = "chapter"; CLMType["PCARD"] = "pcard"; CLMType["COMPOSED"] = "composed"; CLMType["PARALLEL"] = "parallel"; CLMType["REFERENCE"] = "reference"; CLMType["UNKNOWN"] = "unknown"; })(CLMType || (CLMType = {})); /** * Type of composition between CLMs */ export var CompositionType; (function (CompositionType) { CompositionType["SEQUENTIAL"] = "sequential"; CompositionType["PARALLEL"] = "parallel"; CompositionType["NONE"] = "none"; // Leaf node })(CompositionType || (CompositionType = {})); /** * Recursive CLM Hierarchy Introspector * * Analyzes CLM composition graphs, extracting structure and metadata * from arbitrarily nested CLM hierarchies. * * ## Usage * * ```typescript * const introspector = new CLMIntrospector(collection); * const result = await introspector.introspect("root_pcard_hash"); * * // Access hierarchy * console.log(result.root.chapter); * for (const child of result.root.children) { * console.log(` - ${child.hash}: ${child.clmType}`); * } * * // Check for cycles * if (result.cycles.length > 0) { * console.log(`Circular dependencies detected: ${result.cycles}`); * } * ``` */ export class CLMIntrospector { collection; maxDepth; visited = new Set(); nodes = new Map(); cycles = []; constructor(collection, maxDepth = 100) { this.collection = collection; this.maxDepth = maxDepth; } /** * Introspect a CLM hierarchy starting from the given root */ async introspect(pcardHash) { this.visited = new Set(); this.nodes = new Map(); this.cycles = []; let root = await this.buildTree(pcardHash, 0, []); if (!root) { root = { hash: pcardHash, clmType: CLMType.UNKNOWN, compositionType: CompositionType.NONE, children: [], depth: 0, runtime: 'unknown' }; } // Calculate statistics const maxDepth = Math.max(...Array.from(this.nodes.values()).map(n => n.depth), 0); const runtimes = this.countRuntimes(); const { seq, par, leaf } = this.countCompositions(); return { root, nodes: this.nodes, maxDepth, nodeCount: this.nodes.size, cycles: this.cycles, runtimes, sequentialCount: seq, parallelCount: par, leafCount: leaf }; } /** * Recursively build the CLM tree from a PCard hash */ async buildTree(pcardHash, depth, path, parentHash) { // Check for cycles if (path.includes(pcardHash)) { const cycleStart = path.indexOf(pcardHash); const cycle = [...path.slice(cycleStart), pcardHash]; this.cycles.push(cycle); console.warn(`Circular dependency detected: ${cycle.join(' -> ')}`); return null; } // Check max depth if (depth > this.maxDepth) { console.warn(`Max depth ${this.maxDepth} exceeded at ${pcardHash}`); return null; } // Check if already processed if (this.nodes.has(pcardHash)) { return this.nodes.get(pcardHash); } // Get PCard content const clmData = await this.getCLMData(pcardHash); if (!clmData) { return null; } // Create node const node = this.createNode(pcardHash, clmData, depth, parentHash); this.nodes.set(pcardHash, node); // Process children based on composition type const newPath = [...path, pcardHash]; if (node.compositionType === CompositionType.SEQUENTIAL) { // Sequential composition: process steps const steps = clmData?.clm?.abstract?.steps || []; for (const stepHash of steps) { const child = await this.buildTree(stepHash, depth + 1, newPath, pcardHash); if (child) { node.children.push(child); } } } else if (node.compositionType === CompositionType.PARALLEL) { // Parallel composition: process components const left = clmData?.clm?.abstract?.left; const right = clmData?.clm?.abstract?.right; if (left) { const child = await this.buildTree(left, depth + 1, newPath, pcardHash); if (child) { node.children.push(child); } } if (right) { const child = await this.buildTree(right, depth + 1, newPath, pcardHash); if (child) { node.children.push(child); } } } return node; } /** * Get CLM data from a PCard hash */ async getCLMData(pcardHash) { if (!this.collection) { return null; } try { const pcard = await this.collection.get(pcardHash); if (!pcard) { return null; } const content = typeof pcard.content === 'string' ? pcard.content : new TextDecoder().decode(pcard.content); return parse(content); } catch (e) { console.error(`Failed to get CLM data for ${pcardHash}: ${e}`); return null; } } /** * Create a CLMNode from parsed CLM data */ createNode(pcardHash, clmData, depth, parentHash) { // Determine CLM type const clmType = this.classifyCLM(clmData); // Extract CLM dimensions const clmSection = clmData.clm || {}; const abstract = clmSection.abstract || clmData.abstract; const concrete = clmSection.concrete || clmData.concrete; const balanced = clmSection.balanced || clmData.balanced; // Extract chapter const chapter = clmData.chapter; // Determine composition type let compositionType = CompositionType.NONE; const abstractType = abstract?.type || ''; if (abstractType === 'sequential_composition') { compositionType = CompositionType.SEQUENTIAL; } else if (abstractType === 'tensor_product') { compositionType = CompositionType.PARALLEL; } // Extract runtime const runtime = concrete?.runtime || clmSection.concrete?.runtime || 'unknown'; return { hash: pcardHash, clmType, abstract, concrete, balanced, chapter, compositionType, children: [], parentHash, depth, runtime }; } /** * Classify the CLM by its structure */ classifyCLM(clmData) { if (clmData.chapter) { return CLMType.CHAPTER; } const abstract = clmData.clm?.abstract || clmData.abstract || {}; const abstractType = typeof abstract === 'object' ? abstract.type || '' : ''; if (abstractType === 'sequential_composition') { return CLMType.COMPOSED; } else if (abstractType === 'tensor_product') { return CLMType.PARALLEL; } else if (clmData.clm || clmData.abstract) { return CLMType.PCARD; } return CLMType.UNKNOWN; } /** * Count nodes by runtime */ countRuntimes() { const runtimes = new Map(); for (const node of this.nodes.values()) { const rt = node.runtime; runtimes.set(rt, (runtimes.get(rt) || 0) + 1); } return runtimes; } /** * Count sequential, parallel, and leaf compositions */ countCompositions() { let seq = 0; let par = 0; let leaf = 0; for (const node of this.nodes.values()) { if (node.compositionType === CompositionType.SEQUENTIAL) { seq++; } else if (node.compositionType === CompositionType.PARALLEL) { par++; } else { leaf++; } } return { seq, par, leaf }; } /** * Generate a tree visualization of the CLM hierarchy */ toTreeString(result) { const lines = []; this.treeToString(result.root, lines, '', true); return lines.join('\n'); } /** * Recursive helper for tree visualization */ treeToString(node, lines, prefix, isLast) { const connector = isLast ? '└── ' : '├── '; // Build node label const labelParts = [node.hash.substring(0, 8)]; if (node.chapter?.title) { labelParts.push(`(${node.chapter.title})`); } labelParts.push(`[${node.clmType}]`); if (node.runtime !== 'unknown') { labelParts.push(`@${node.runtime}`); } lines.push(prefix + connector + labelParts.join(' ')); // Process children const newPrefix = prefix + (isLast ? ' ' : '│ '); node.children.forEach((child, i) => { this.treeToString(child, lines, newPrefix, i === node.children.length - 1); }); } } //# sourceMappingURL=CLMIntrospection.js.map