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
JavaScript
/**
* 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