@documentation-robotics/cli 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ async function extractExitCode(error) {
48
48
  }
49
49
  return 1;
50
50
  }
51
- const cliVersion = "0.1.11", program = new Command();
51
+ const cliVersion = "0.1.12", program = new Command();
52
52
  (process.argv.includes("--version") || process.argv.includes("-V")) && (process.argv = ["node", "dr", "version"]), program.name("dr").description("Documentation Robotics CLI - Architecture Model Management").option("-v, --verbose", "Enable verbose output").option("--debug", "Enable debug mode").exitOverride().showSuggestionAfterError().hook("preAction", async (thisCommand) => {
53
53
  const options = thisCommand.opts();
54
54
  setGlobalOptions({
@@ -2,7 +2,7 @@ import ansis from "ansis";
2
2
  import { getCliBundledSpecVersion } from "../utils/spec-version.js";
3
3
  import { isTelemetryBuiltIn, isTelemetryConfigured } from "../telemetry/detect.js";
4
4
  import { getSpecReferencePath, getModelPath } from "../utils/project-paths.js";
5
- const gitHash = "e821d8d", cliVersion = "0.1.11";
5
+ const gitHash = "d34903f", cliVersion = "0.1.12";
6
6
  async function versionCommand() {
7
7
  const specVersion = getCliBundledSpecVersion(), telemetryBuiltIn = isTelemetryBuiltIn(), telemetryConfigured = await isTelemetryConfigured();
8
8
  let telemetryStatus;
@@ -239,7 +239,20 @@ class GraphModel {
239
239
  type: element.type,
240
240
  name: element.name,
241
241
  description: element.description,
242
- properties: element.attributes || {},
242
+ // `properties` is a SEPARATE COPY of `attributes`, never the same object reference.
243
+ // `Layer`'s node<->Element conversion (layer.ts's constructor/addElement) mutates
244
+ // `node.properties` in place to stash graph-internal bookkeeping keys
245
+ // (__references__/__relationships__/__semanticId__) that have no separate channel
246
+ // on GraphNode. If `properties` and `attributes` aliased the same object (as they
247
+ // did before), those mutations leaked straight into `attributes` too — and
248
+ // `attributes` is exactly what `Model.saveLayer()` persists to YAML and what
249
+ // `Layer.elements`/`getElement()` read back as the element's real domain data.
250
+ // That aliasing is what let `__semanticId__` (added after __references__/
251
+ // __relationships__ were already accounted for elsewhere) slip past
252
+ // model.ts's internal-key filter and get written into persisted YAML. Copying
253
+ // here means a future bookkeeping key added to layer.ts's `nodeProperties` can
254
+ // never repeat that leak, regardless of whether model.ts's filter is kept in sync.
255
+ properties: element.attributes ? { ...element.attributes } : {},
243
256
  spec_node_id: element.spec_node_id || void 0,
244
257
  layer_id: element.layer_id || void 0,
245
258
  attributes: element.attributes && Object.keys(element.attributes).length > 0 ? element.attributes : void 0,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/graph-model.ts"],
4
- "sourcesContent": ["import { Element } from \"./element.js\";\nimport type { ElementMetadata, Relationship, SourceReference } from \"../types/index.js\";\n\n/**\n * Graph node representing a model element\n * Replaces Element class for graph-based storage\n */\nexport interface GraphNode {\n id: string; // Path (slug) used as graph key: {layer}.{type}.{kebab-name}\n uuid?: string; // UUIDv4 preserved for round-tripping (element.id)\n layer: string; // Layer this node belongs to\n type: string; // Element type\n name: string; // Display name\n description?: string; // Optional description\n properties?: Record<string, unknown>; // Custom properties\n // Spec-node aligned fields (preserved through graph round-trip)\n spec_node_id?: string;\n layer_id?: string;\n attributes?: Record<string, unknown>;\n source_reference?: SourceReference;\n metadata?: ElementMetadata;\n}\n\n/**\n * Graph edge representing a relationship between nodes\n */\nexport interface GraphEdge {\n id: string; // Unique identifier\n source: string; // Source node ID\n destination: string; // Destination node ID\n predicate: string; // Relationship type\n properties?: Record<string, unknown>; // Optional properties\n category?: \"structural\" | \"behavioral\";\n}\n\n/**\n * Index structures for efficient querying\n */\ninterface GraphIndices {\n nodesByLayer: Map<string, Set<string>>; // layer -> node IDs\n nodesByType: Map<string, Set<string>>; // type -> node IDs\n edgesBySource: Map<string, Set<string>>; // source node ID -> edge IDs\n edgesByDestination: Map<string, Set<string>>; // destination node ID -> edge IDs\n edgesByPredicate: Map<string, Set<string>>; // predicate -> edge IDs\n}\n\n/**\n * Graph model interface for node/edge storage with query capabilities\n */\nexport interface IGraphModel {\n nodes: Map<string, GraphNode>;\n edges: Map<string, GraphEdge>;\n\n // Query methods\n getNode(id: string): GraphNode | undefined;\n getNodesByLayer(layer: string): GraphNode[];\n getNodesByType(type: string): GraphNode[];\n getEdgesFrom(nodeId: string, predicate?: string): GraphEdge[];\n getEdgesTo(nodeId: string, predicate?: string): GraphEdge[];\n getEdgesBetween(sourceId: string, destinationId: string, predicate?: string): GraphEdge[];\n getAllEdges(): GraphEdge[];\n\n // Mutation methods\n addNode(node: GraphNode): void;\n removeNode(nodeId: string): boolean;\n updateNode(nodeId: string, updates: Partial<GraphNode>): boolean;\n\n addEdge(edge: GraphEdge): void;\n removeEdge(edgeId: string): boolean;\n updateEdge(edgeId: string, updates: Partial<GraphEdge>): boolean;\n\n // Traversal\n traverse(startNodeId: string, predicate?: string, maxDepth?: number): GraphNode[];\n\n // Utility\n getNodeCount(): number;\n getEdgeCount(): number;\n clear(): void;\n}\n\n/**\n * Implementation of graph model with indices for efficient queries\n */\nexport class GraphModel implements IGraphModel {\n nodes: Map<string, GraphNode>;\n edges: Map<string, GraphEdge>;\n private indices: GraphIndices;\n private nodesVersion: number = 0;\n\n constructor() {\n this.nodes = new Map();\n this.edges = new Map();\n this.indices = {\n nodesByLayer: new Map(),\n nodesByType: new Map(),\n edgesBySource: new Map(),\n edgesByDestination: new Map(),\n edgesByPredicate: new Map(),\n };\n }\n\n /**\n * Get the current version of the nodes (for cache invalidation)\n */\n getNodesVersion(): number {\n return this.nodesVersion;\n }\n\n /**\n * Add a node to the graph and update indices\n * If a node with the same ID exists, its old index entries are cleaned up\n */\n addNode(node: GraphNode): void {\n const existingNode = this.nodes.get(node.id);\n\n // Clean up old index entries if node is being replaced\n if (existingNode) {\n // Remove from old layer index\n const oldLayerSet = this.indices.nodesByLayer.get(existingNode.layer);\n if (oldLayerSet) {\n oldLayerSet.delete(node.id);\n if (oldLayerSet.size === 0) {\n this.indices.nodesByLayer.delete(existingNode.layer);\n }\n }\n\n // Remove from old type index\n const oldTypeSet = this.indices.nodesByType.get(existingNode.type);\n if (oldTypeSet) {\n oldTypeSet.delete(node.id);\n if (oldTypeSet.size === 0) {\n this.indices.nodesByType.delete(existingNode.type);\n }\n }\n }\n\n // Add new node\n this.nodes.set(node.id, node);\n\n // Update layer index\n if (!this.indices.nodesByLayer.has(node.layer)) {\n this.indices.nodesByLayer.set(node.layer, new Set());\n }\n const layerSet = this.indices.nodesByLayer.get(node.layer);\n if (layerSet) {\n layerSet.add(node.id);\n }\n\n // Update type index\n if (!this.indices.nodesByType.has(node.type)) {\n this.indices.nodesByType.set(node.type, new Set());\n }\n const typeSet = this.indices.nodesByType.get(node.type);\n if (typeSet) {\n typeSet.add(node.id);\n }\n\n // Increment version to invalidate caches\n this.nodesVersion++;\n }\n\n /**\n * Remove a node from the graph\n * Also removes all associated edges\n */\n removeNode(nodeId: string): boolean {\n const node = this.nodes.get(nodeId);\n if (!node) {\n return false;\n }\n\n // Remove from nodes map\n this.nodes.delete(nodeId);\n\n // Remove from layer index\n const layerSet = this.indices.nodesByLayer.get(node.layer);\n if (layerSet) {\n layerSet.delete(nodeId);\n if (layerSet.size === 0) {\n this.indices.nodesByLayer.delete(node.layer);\n }\n }\n\n // Remove from type index\n const typeSet = this.indices.nodesByType.get(node.type);\n if (typeSet) {\n typeSet.delete(nodeId);\n if (typeSet.size === 0) {\n this.indices.nodesByType.delete(node.type);\n }\n }\n\n // Remove all edges involving this node\n const edgesInvolving: string[] = [];\n for (const [edgeId, edge] of this.edges.entries()) {\n if (edge.source === nodeId || edge.destination === nodeId) {\n edgesInvolving.push(edgeId);\n }\n }\n for (const edgeId of edgesInvolving) {\n this.removeEdge(edgeId);\n }\n\n // Increment version to invalidate caches\n this.nodesVersion++;\n return true;\n }\n\n /**\n * Update node properties\n */\n updateNode(nodeId: string, updates: Partial<GraphNode>): boolean {\n const node = this.nodes.get(nodeId);\n if (!node) {\n return false;\n }\n\n // Handle layer change\n if (updates.layer && updates.layer !== node.layer) {\n const oldLayerSet = this.indices.nodesByLayer.get(node.layer);\n if (oldLayerSet) {\n oldLayerSet.delete(nodeId);\n if (oldLayerSet.size === 0) {\n this.indices.nodesByLayer.delete(node.layer);\n }\n }\n if (!this.indices.nodesByLayer.has(updates.layer)) {\n this.indices.nodesByLayer.set(updates.layer, new Set());\n }\n const newLayerSet = this.indices.nodesByLayer.get(updates.layer);\n if (newLayerSet) {\n newLayerSet.add(nodeId);\n }\n }\n\n // Handle type change\n if (updates.type && updates.type !== node.type) {\n const oldTypeSet = this.indices.nodesByType.get(node.type);\n if (oldTypeSet) {\n oldTypeSet.delete(nodeId);\n if (oldTypeSet.size === 0) {\n this.indices.nodesByType.delete(node.type);\n }\n }\n if (!this.indices.nodesByType.has(updates.type)) {\n this.indices.nodesByType.set(updates.type, new Set());\n }\n const newTypeSet = this.indices.nodesByType.get(updates.type);\n if (newTypeSet) {\n newTypeSet.add(nodeId);\n }\n }\n\n // Apply updates\n Object.assign(node, updates);\n // Increment version to invalidate caches\n this.nodesVersion++;\n return true;\n }\n\n /**\n * Add an edge to the graph and update indices\n * Validates that both source and destination nodes exist\n * @throws Error if source or destination node does not exist\n */\n addEdge(edge: GraphEdge): void {\n // Validate that source node exists\n if (!this.nodes.has(edge.source)) {\n throw new Error(`Cannot add edge: source node \"${edge.source}\" does not exist`);\n }\n\n // Validate that destination node exists\n if (!this.nodes.has(edge.destination)) {\n throw new Error(`Cannot add edge: destination node \"${edge.destination}\" does not exist`);\n }\n\n this.edges.set(edge.id, edge);\n\n // Update source index\n if (!this.indices.edgesBySource.has(edge.source)) {\n this.indices.edgesBySource.set(edge.source, new Set());\n }\n const sourceSet = this.indices.edgesBySource.get(edge.source);\n if (sourceSet) {\n sourceSet.add(edge.id);\n }\n\n // Update destination index\n if (!this.indices.edgesByDestination.has(edge.destination)) {\n this.indices.edgesByDestination.set(edge.destination, new Set());\n }\n const destSet = this.indices.edgesByDestination.get(edge.destination);\n if (destSet) {\n destSet.add(edge.id);\n }\n\n // Update predicate index\n if (!this.indices.edgesByPredicate.has(edge.predicate)) {\n this.indices.edgesByPredicate.set(edge.predicate, new Set());\n }\n const predicateSet = this.indices.edgesByPredicate.get(edge.predicate);\n if (predicateSet) {\n predicateSet.add(edge.id);\n }\n }\n\n /**\n * Remove an edge from the graph\n */\n removeEdge(edgeId: string): boolean {\n const edge = this.edges.get(edgeId);\n if (!edge) {\n return false;\n }\n\n // Remove from edges map\n this.edges.delete(edgeId);\n\n // Remove from source index\n const sourceSet = this.indices.edgesBySource.get(edge.source);\n if (sourceSet) {\n sourceSet.delete(edgeId);\n if (sourceSet.size === 0) {\n this.indices.edgesBySource.delete(edge.source);\n }\n }\n\n // Remove from destination index\n const destSet = this.indices.edgesByDestination.get(edge.destination);\n if (destSet) {\n destSet.delete(edgeId);\n if (destSet.size === 0) {\n this.indices.edgesByDestination.delete(edge.destination);\n }\n }\n\n // Remove from predicate index\n const predicateSet = this.indices.edgesByPredicate.get(edge.predicate);\n if (predicateSet) {\n predicateSet.delete(edgeId);\n if (predicateSet.size === 0) {\n this.indices.edgesByPredicate.delete(edge.predicate);\n }\n }\n\n return true;\n }\n\n /**\n * Update edge properties\n */\n updateEdge(edgeId: string, updates: Partial<GraphEdge>): boolean {\n const edge = this.edges.get(edgeId);\n if (!edge) {\n return false;\n }\n\n // Handle predicate change\n if (updates.predicate && updates.predicate !== edge.predicate) {\n const oldPredicateSet = this.indices.edgesByPredicate.get(edge.predicate);\n if (oldPredicateSet) {\n oldPredicateSet.delete(edgeId);\n if (oldPredicateSet.size === 0) {\n this.indices.edgesByPredicate.delete(edge.predicate);\n }\n }\n if (!this.indices.edgesByPredicate.has(updates.predicate)) {\n this.indices.edgesByPredicate.set(updates.predicate, new Set());\n }\n const newPredicateSet = this.indices.edgesByPredicate.get(updates.predicate);\n if (newPredicateSet) {\n newPredicateSet.add(edgeId);\n }\n }\n\n // Note: Source and destination changes are not supported (use removeEdge + addEdge)\n // Apply updates\n Object.assign(edge, updates);\n return true;\n }\n\n /**\n * Get a node by its ID\n */\n getNode(id: string): GraphNode | undefined {\n return this.nodes.get(id);\n }\n\n /**\n * Get all nodes in a specific layer\n */\n getNodesByLayer(layer: string): GraphNode[] {\n const nodeIds = this.indices.nodesByLayer.get(layer) ?? new Set();\n const result: GraphNode[] = [];\n for (const id of nodeIds) {\n const node = this.nodes.get(id);\n if (node) {\n result.push(node);\n }\n }\n return result;\n }\n\n /**\n * Get all nodes of a specific type\n */\n getNodesByType(type: string): GraphNode[] {\n const nodeIds = this.indices.nodesByType.get(type) ?? new Set();\n const result: GraphNode[] = [];\n for (const id of nodeIds) {\n const node = this.nodes.get(id);\n if (node) {\n result.push(node);\n }\n }\n return result;\n }\n\n /**\n * Get outgoing edges from a node, optionally filtered by predicate\n */\n getEdgesFrom(nodeId: string, predicate?: string): GraphEdge[] {\n const edgeIds = this.indices.edgesBySource.get(nodeId) ?? new Set();\n const edges: GraphEdge[] = [];\n for (const id of edgeIds) {\n const edge = this.edges.get(id);\n if (edge) {\n edges.push(edge);\n }\n }\n\n if (predicate) {\n return edges.filter((e) => e.predicate === predicate);\n }\n return edges;\n }\n\n /**\n * Get incoming edges to a node, optionally filtered by predicate\n */\n getEdgesTo(nodeId: string, predicate?: string): GraphEdge[] {\n const edgeIds = this.indices.edgesByDestination.get(nodeId) ?? new Set();\n const edges: GraphEdge[] = [];\n for (const id of edgeIds) {\n const edge = this.edges.get(id);\n if (edge) {\n edges.push(edge);\n }\n }\n\n if (predicate) {\n return edges.filter((e) => e.predicate === predicate);\n }\n return edges;\n }\n\n /**\n * Get edges between two specific nodes\n */\n getEdgesBetween(sourceId: string, destinationId: string, predicate?: string): GraphEdge[] {\n const outgoing = this.getEdgesFrom(sourceId, predicate);\n return outgoing.filter((e) => e.destination === destinationId);\n }\n\n /**\n * Get all edges in the graph\n */\n getAllEdges(): GraphEdge[] {\n return Array.from(this.edges.values());\n }\n\n /**\n * Traverse the graph starting from a node using breadth-first search\n */\n traverse(startNodeId: string, predicate?: string, maxDepth: number = Infinity): GraphNode[] {\n const visited = new Set<string>();\n const result: GraphNode[] = [];\n const queue: { nodeId: string; depth: number }[] = [{ nodeId: startNodeId, depth: 0 }];\n\n while (queue.length > 0) {\n const { nodeId, depth } = queue.shift()!;\n\n if (visited.has(nodeId) || depth > maxDepth) {\n continue;\n }\n\n visited.add(nodeId);\n const node = this.nodes.get(nodeId);\n if (node) {\n result.push(node);\n }\n\n // Find outgoing edges\n const edges = this.getEdgesFrom(nodeId, predicate);\n for (const edge of edges) {\n if (!visited.has(edge.destination)) {\n queue.push({ nodeId: edge.destination, depth: depth + 1 });\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get total number of nodes\n */\n getNodeCount(): number {\n return this.nodes.size;\n }\n\n /**\n * Get total number of edges\n */\n getEdgeCount(): number {\n return this.edges.size;\n }\n\n /**\n * Clear all data from the graph\n */\n clear(): void {\n this.nodes.clear();\n this.edges.clear();\n this.indices.nodesByLayer.clear();\n this.indices.nodesByType.clear();\n this.indices.edgesBySource.clear();\n this.indices.edgesByDestination.clear();\n this.indices.edgesByPredicate.clear();\n }\n\n /**\n * Convert Element to GraphNode\n * Preserves spec-node fields so they survive graph round-trips\n */\n /** UUID pattern for detecting actual UUIDs vs slug IDs */\n private static readonly UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n static fromElement(element: Element): GraphNode {\n // path is the graph key; fall back to id for elements without a path\n const graphId = element.path || element.id;\n // Only preserve UUID when the id is actually a UUID (not a slug)\n const isUUID = GraphModel.UUID_PATTERN.test(element.id);\n return {\n id: graphId,\n uuid: isUUID ? element.id : undefined, // preserve UUID for round-tripping\n layer: element.layer || \"unknown\",\n type: element.type,\n name: element.name,\n description: element.description,\n properties: element.attributes || {},\n spec_node_id: element.spec_node_id || undefined,\n layer_id: element.layer_id || undefined,\n attributes: element.attributes && Object.keys(element.attributes).length > 0\n ? element.attributes\n : undefined,\n source_reference: element.source_reference,\n metadata: element.metadata,\n };\n }\n\n /**\n * Convert Relationship to GraphEdge\n */\n static relationshipToEdge(relationship: Relationship, edgeId: string): GraphEdge {\n return {\n id: edgeId,\n source: relationship.source,\n destination: relationship.target,\n predicate: relationship.predicate,\n properties: relationship.properties,\n };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Element } from \"./element.js\";\nimport type { ElementMetadata, Relationship, SourceReference } from \"../types/index.js\";\n\n/**\n * Graph node representing a model element\n * Replaces Element class for graph-based storage\n */\nexport interface GraphNode {\n id: string; // Path (slug) used as graph key: {layer}.{type}.{kebab-name}\n uuid?: string; // UUIDv4 preserved for round-tripping (element.id)\n layer: string; // Layer this node belongs to\n type: string; // Element type\n name: string; // Display name\n description?: string; // Optional description\n properties?: Record<string, unknown>; // Custom properties\n // Spec-node aligned fields (preserved through graph round-trip)\n spec_node_id?: string;\n layer_id?: string;\n attributes?: Record<string, unknown>;\n source_reference?: SourceReference;\n metadata?: ElementMetadata;\n}\n\n/**\n * Graph edge representing a relationship between nodes\n */\nexport interface GraphEdge {\n id: string; // Unique identifier\n source: string; // Source node ID\n destination: string; // Destination node ID\n predicate: string; // Relationship type\n properties?: Record<string, unknown>; // Optional properties\n category?: \"structural\" | \"behavioral\";\n}\n\n/**\n * Index structures for efficient querying\n */\ninterface GraphIndices {\n nodesByLayer: Map<string, Set<string>>; // layer -> node IDs\n nodesByType: Map<string, Set<string>>; // type -> node IDs\n edgesBySource: Map<string, Set<string>>; // source node ID -> edge IDs\n edgesByDestination: Map<string, Set<string>>; // destination node ID -> edge IDs\n edgesByPredicate: Map<string, Set<string>>; // predicate -> edge IDs\n}\n\n/**\n * Graph model interface for node/edge storage with query capabilities\n */\nexport interface IGraphModel {\n nodes: Map<string, GraphNode>;\n edges: Map<string, GraphEdge>;\n\n // Query methods\n getNode(id: string): GraphNode | undefined;\n getNodesByLayer(layer: string): GraphNode[];\n getNodesByType(type: string): GraphNode[];\n getEdgesFrom(nodeId: string, predicate?: string): GraphEdge[];\n getEdgesTo(nodeId: string, predicate?: string): GraphEdge[];\n getEdgesBetween(sourceId: string, destinationId: string, predicate?: string): GraphEdge[];\n getAllEdges(): GraphEdge[];\n\n // Mutation methods\n addNode(node: GraphNode): void;\n removeNode(nodeId: string): boolean;\n updateNode(nodeId: string, updates: Partial<GraphNode>): boolean;\n\n addEdge(edge: GraphEdge): void;\n removeEdge(edgeId: string): boolean;\n updateEdge(edgeId: string, updates: Partial<GraphEdge>): boolean;\n\n // Traversal\n traverse(startNodeId: string, predicate?: string, maxDepth?: number): GraphNode[];\n\n // Utility\n getNodeCount(): number;\n getEdgeCount(): number;\n clear(): void;\n}\n\n/**\n * Implementation of graph model with indices for efficient queries\n */\nexport class GraphModel implements IGraphModel {\n nodes: Map<string, GraphNode>;\n edges: Map<string, GraphEdge>;\n private indices: GraphIndices;\n private nodesVersion: number = 0;\n\n constructor() {\n this.nodes = new Map();\n this.edges = new Map();\n this.indices = {\n nodesByLayer: new Map(),\n nodesByType: new Map(),\n edgesBySource: new Map(),\n edgesByDestination: new Map(),\n edgesByPredicate: new Map(),\n };\n }\n\n /**\n * Get the current version of the nodes (for cache invalidation)\n */\n getNodesVersion(): number {\n return this.nodesVersion;\n }\n\n /**\n * Add a node to the graph and update indices\n * If a node with the same ID exists, its old index entries are cleaned up\n */\n addNode(node: GraphNode): void {\n const existingNode = this.nodes.get(node.id);\n\n // Clean up old index entries if node is being replaced\n if (existingNode) {\n // Remove from old layer index\n const oldLayerSet = this.indices.nodesByLayer.get(existingNode.layer);\n if (oldLayerSet) {\n oldLayerSet.delete(node.id);\n if (oldLayerSet.size === 0) {\n this.indices.nodesByLayer.delete(existingNode.layer);\n }\n }\n\n // Remove from old type index\n const oldTypeSet = this.indices.nodesByType.get(existingNode.type);\n if (oldTypeSet) {\n oldTypeSet.delete(node.id);\n if (oldTypeSet.size === 0) {\n this.indices.nodesByType.delete(existingNode.type);\n }\n }\n }\n\n // Add new node\n this.nodes.set(node.id, node);\n\n // Update layer index\n if (!this.indices.nodesByLayer.has(node.layer)) {\n this.indices.nodesByLayer.set(node.layer, new Set());\n }\n const layerSet = this.indices.nodesByLayer.get(node.layer);\n if (layerSet) {\n layerSet.add(node.id);\n }\n\n // Update type index\n if (!this.indices.nodesByType.has(node.type)) {\n this.indices.nodesByType.set(node.type, new Set());\n }\n const typeSet = this.indices.nodesByType.get(node.type);\n if (typeSet) {\n typeSet.add(node.id);\n }\n\n // Increment version to invalidate caches\n this.nodesVersion++;\n }\n\n /**\n * Remove a node from the graph\n * Also removes all associated edges\n */\n removeNode(nodeId: string): boolean {\n const node = this.nodes.get(nodeId);\n if (!node) {\n return false;\n }\n\n // Remove from nodes map\n this.nodes.delete(nodeId);\n\n // Remove from layer index\n const layerSet = this.indices.nodesByLayer.get(node.layer);\n if (layerSet) {\n layerSet.delete(nodeId);\n if (layerSet.size === 0) {\n this.indices.nodesByLayer.delete(node.layer);\n }\n }\n\n // Remove from type index\n const typeSet = this.indices.nodesByType.get(node.type);\n if (typeSet) {\n typeSet.delete(nodeId);\n if (typeSet.size === 0) {\n this.indices.nodesByType.delete(node.type);\n }\n }\n\n // Remove all edges involving this node\n const edgesInvolving: string[] = [];\n for (const [edgeId, edge] of this.edges.entries()) {\n if (edge.source === nodeId || edge.destination === nodeId) {\n edgesInvolving.push(edgeId);\n }\n }\n for (const edgeId of edgesInvolving) {\n this.removeEdge(edgeId);\n }\n\n // Increment version to invalidate caches\n this.nodesVersion++;\n return true;\n }\n\n /**\n * Update node properties\n */\n updateNode(nodeId: string, updates: Partial<GraphNode>): boolean {\n const node = this.nodes.get(nodeId);\n if (!node) {\n return false;\n }\n\n // Handle layer change\n if (updates.layer && updates.layer !== node.layer) {\n const oldLayerSet = this.indices.nodesByLayer.get(node.layer);\n if (oldLayerSet) {\n oldLayerSet.delete(nodeId);\n if (oldLayerSet.size === 0) {\n this.indices.nodesByLayer.delete(node.layer);\n }\n }\n if (!this.indices.nodesByLayer.has(updates.layer)) {\n this.indices.nodesByLayer.set(updates.layer, new Set());\n }\n const newLayerSet = this.indices.nodesByLayer.get(updates.layer);\n if (newLayerSet) {\n newLayerSet.add(nodeId);\n }\n }\n\n // Handle type change\n if (updates.type && updates.type !== node.type) {\n const oldTypeSet = this.indices.nodesByType.get(node.type);\n if (oldTypeSet) {\n oldTypeSet.delete(nodeId);\n if (oldTypeSet.size === 0) {\n this.indices.nodesByType.delete(node.type);\n }\n }\n if (!this.indices.nodesByType.has(updates.type)) {\n this.indices.nodesByType.set(updates.type, new Set());\n }\n const newTypeSet = this.indices.nodesByType.get(updates.type);\n if (newTypeSet) {\n newTypeSet.add(nodeId);\n }\n }\n\n // Apply updates\n Object.assign(node, updates);\n // Increment version to invalidate caches\n this.nodesVersion++;\n return true;\n }\n\n /**\n * Add an edge to the graph and update indices\n * Validates that both source and destination nodes exist\n * @throws Error if source or destination node does not exist\n */\n addEdge(edge: GraphEdge): void {\n // Validate that source node exists\n if (!this.nodes.has(edge.source)) {\n throw new Error(`Cannot add edge: source node \"${edge.source}\" does not exist`);\n }\n\n // Validate that destination node exists\n if (!this.nodes.has(edge.destination)) {\n throw new Error(`Cannot add edge: destination node \"${edge.destination}\" does not exist`);\n }\n\n this.edges.set(edge.id, edge);\n\n // Update source index\n if (!this.indices.edgesBySource.has(edge.source)) {\n this.indices.edgesBySource.set(edge.source, new Set());\n }\n const sourceSet = this.indices.edgesBySource.get(edge.source);\n if (sourceSet) {\n sourceSet.add(edge.id);\n }\n\n // Update destination index\n if (!this.indices.edgesByDestination.has(edge.destination)) {\n this.indices.edgesByDestination.set(edge.destination, new Set());\n }\n const destSet = this.indices.edgesByDestination.get(edge.destination);\n if (destSet) {\n destSet.add(edge.id);\n }\n\n // Update predicate index\n if (!this.indices.edgesByPredicate.has(edge.predicate)) {\n this.indices.edgesByPredicate.set(edge.predicate, new Set());\n }\n const predicateSet = this.indices.edgesByPredicate.get(edge.predicate);\n if (predicateSet) {\n predicateSet.add(edge.id);\n }\n }\n\n /**\n * Remove an edge from the graph\n */\n removeEdge(edgeId: string): boolean {\n const edge = this.edges.get(edgeId);\n if (!edge) {\n return false;\n }\n\n // Remove from edges map\n this.edges.delete(edgeId);\n\n // Remove from source index\n const sourceSet = this.indices.edgesBySource.get(edge.source);\n if (sourceSet) {\n sourceSet.delete(edgeId);\n if (sourceSet.size === 0) {\n this.indices.edgesBySource.delete(edge.source);\n }\n }\n\n // Remove from destination index\n const destSet = this.indices.edgesByDestination.get(edge.destination);\n if (destSet) {\n destSet.delete(edgeId);\n if (destSet.size === 0) {\n this.indices.edgesByDestination.delete(edge.destination);\n }\n }\n\n // Remove from predicate index\n const predicateSet = this.indices.edgesByPredicate.get(edge.predicate);\n if (predicateSet) {\n predicateSet.delete(edgeId);\n if (predicateSet.size === 0) {\n this.indices.edgesByPredicate.delete(edge.predicate);\n }\n }\n\n return true;\n }\n\n /**\n * Update edge properties\n */\n updateEdge(edgeId: string, updates: Partial<GraphEdge>): boolean {\n const edge = this.edges.get(edgeId);\n if (!edge) {\n return false;\n }\n\n // Handle predicate change\n if (updates.predicate && updates.predicate !== edge.predicate) {\n const oldPredicateSet = this.indices.edgesByPredicate.get(edge.predicate);\n if (oldPredicateSet) {\n oldPredicateSet.delete(edgeId);\n if (oldPredicateSet.size === 0) {\n this.indices.edgesByPredicate.delete(edge.predicate);\n }\n }\n if (!this.indices.edgesByPredicate.has(updates.predicate)) {\n this.indices.edgesByPredicate.set(updates.predicate, new Set());\n }\n const newPredicateSet = this.indices.edgesByPredicate.get(updates.predicate);\n if (newPredicateSet) {\n newPredicateSet.add(edgeId);\n }\n }\n\n // Note: Source and destination changes are not supported (use removeEdge + addEdge)\n // Apply updates\n Object.assign(edge, updates);\n return true;\n }\n\n /**\n * Get a node by its ID\n */\n getNode(id: string): GraphNode | undefined {\n return this.nodes.get(id);\n }\n\n /**\n * Get all nodes in a specific layer\n */\n getNodesByLayer(layer: string): GraphNode[] {\n const nodeIds = this.indices.nodesByLayer.get(layer) ?? new Set();\n const result: GraphNode[] = [];\n for (const id of nodeIds) {\n const node = this.nodes.get(id);\n if (node) {\n result.push(node);\n }\n }\n return result;\n }\n\n /**\n * Get all nodes of a specific type\n */\n getNodesByType(type: string): GraphNode[] {\n const nodeIds = this.indices.nodesByType.get(type) ?? new Set();\n const result: GraphNode[] = [];\n for (const id of nodeIds) {\n const node = this.nodes.get(id);\n if (node) {\n result.push(node);\n }\n }\n return result;\n }\n\n /**\n * Get outgoing edges from a node, optionally filtered by predicate\n */\n getEdgesFrom(nodeId: string, predicate?: string): GraphEdge[] {\n const edgeIds = this.indices.edgesBySource.get(nodeId) ?? new Set();\n const edges: GraphEdge[] = [];\n for (const id of edgeIds) {\n const edge = this.edges.get(id);\n if (edge) {\n edges.push(edge);\n }\n }\n\n if (predicate) {\n return edges.filter((e) => e.predicate === predicate);\n }\n return edges;\n }\n\n /**\n * Get incoming edges to a node, optionally filtered by predicate\n */\n getEdgesTo(nodeId: string, predicate?: string): GraphEdge[] {\n const edgeIds = this.indices.edgesByDestination.get(nodeId) ?? new Set();\n const edges: GraphEdge[] = [];\n for (const id of edgeIds) {\n const edge = this.edges.get(id);\n if (edge) {\n edges.push(edge);\n }\n }\n\n if (predicate) {\n return edges.filter((e) => e.predicate === predicate);\n }\n return edges;\n }\n\n /**\n * Get edges between two specific nodes\n */\n getEdgesBetween(sourceId: string, destinationId: string, predicate?: string): GraphEdge[] {\n const outgoing = this.getEdgesFrom(sourceId, predicate);\n return outgoing.filter((e) => e.destination === destinationId);\n }\n\n /**\n * Get all edges in the graph\n */\n getAllEdges(): GraphEdge[] {\n return Array.from(this.edges.values());\n }\n\n /**\n * Traverse the graph starting from a node using breadth-first search\n */\n traverse(startNodeId: string, predicate?: string, maxDepth: number = Infinity): GraphNode[] {\n const visited = new Set<string>();\n const result: GraphNode[] = [];\n const queue: { nodeId: string; depth: number }[] = [{ nodeId: startNodeId, depth: 0 }];\n\n while (queue.length > 0) {\n const { nodeId, depth } = queue.shift()!;\n\n if (visited.has(nodeId) || depth > maxDepth) {\n continue;\n }\n\n visited.add(nodeId);\n const node = this.nodes.get(nodeId);\n if (node) {\n result.push(node);\n }\n\n // Find outgoing edges\n const edges = this.getEdgesFrom(nodeId, predicate);\n for (const edge of edges) {\n if (!visited.has(edge.destination)) {\n queue.push({ nodeId: edge.destination, depth: depth + 1 });\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get total number of nodes\n */\n getNodeCount(): number {\n return this.nodes.size;\n }\n\n /**\n * Get total number of edges\n */\n getEdgeCount(): number {\n return this.edges.size;\n }\n\n /**\n * Clear all data from the graph\n */\n clear(): void {\n this.nodes.clear();\n this.edges.clear();\n this.indices.nodesByLayer.clear();\n this.indices.nodesByType.clear();\n this.indices.edgesBySource.clear();\n this.indices.edgesByDestination.clear();\n this.indices.edgesByPredicate.clear();\n }\n\n /**\n * Convert Element to GraphNode\n * Preserves spec-node fields so they survive graph round-trips\n */\n /** UUID pattern for detecting actual UUIDs vs slug IDs */\n private static readonly UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n static fromElement(element: Element): GraphNode {\n // path is the graph key; fall back to id for elements without a path\n const graphId = element.path || element.id;\n // Only preserve UUID when the id is actually a UUID (not a slug)\n const isUUID = GraphModel.UUID_PATTERN.test(element.id);\n return {\n id: graphId,\n uuid: isUUID ? element.id : undefined, // preserve UUID for round-tripping\n layer: element.layer || \"unknown\",\n type: element.type,\n name: element.name,\n description: element.description,\n // `properties` is a SEPARATE COPY of `attributes`, never the same object reference.\n // `Layer`'s node<->Element conversion (layer.ts's constructor/addElement) mutates\n // `node.properties` in place to stash graph-internal bookkeeping keys\n // (__references__/__relationships__/__semanticId__) that have no separate channel\n // on GraphNode. If `properties` and `attributes` aliased the same object (as they\n // did before), those mutations leaked straight into `attributes` too \u2014 and\n // `attributes` is exactly what `Model.saveLayer()` persists to YAML and what\n // `Layer.elements`/`getElement()` read back as the element's real domain data.\n // That aliasing is what let `__semanticId__` (added after __references__/\n // __relationships__ were already accounted for elsewhere) slip past\n // model.ts's internal-key filter and get written into persisted YAML. Copying\n // here means a future bookkeeping key added to layer.ts's `nodeProperties` can\n // never repeat that leak, regardless of whether model.ts's filter is kept in sync.\n properties: element.attributes ? { ...element.attributes } : {},\n spec_node_id: element.spec_node_id || undefined,\n layer_id: element.layer_id || undefined,\n attributes: element.attributes && Object.keys(element.attributes).length > 0\n ? element.attributes\n : undefined,\n source_reference: element.source_reference,\n metadata: element.metadata,\n };\n }\n\n /**\n * Convert Relationship to GraphEdge\n */\n static relationshipToEdge(relationship: Relationship, edgeId: string): GraphEdge {\n return {\n id: edgeId,\n source: relationship.source,\n destination: relationship.target,\n predicate: relationship.predicate,\n properties: relationship.properties,\n };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -37,7 +37,7 @@ function typeToSnakeStem(specNodeId, type) {
37
37
  }
38
38
  return type;
39
39
  }
40
- const ALWAYS_INTERNAL_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set(["__references__", "__relationships__"]);
40
+ const ALWAYS_INTERNAL_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set(["__references__", "__relationships__", "__semanticId__"]);
41
41
  function isPersistableAttribute(specNodeId, key) {
42
42
  if (!ALWAYS_INTERNAL_ATTRIBUTE_KEYS.has(key))
43
43
  return !0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/model.ts"],
4
- "sourcesContent": ["import { Layer } from \"./layer.js\";\nimport { Manifest } from \"./manifest.js\";\nimport { Element } from \"./element.js\";\nimport { GraphModel, type GraphEdge } from \"./graph-model.js\";\nimport { VirtualProjectionEngine } from \"./virtual-projection.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { Relationships } from \"./relationships.js\";\nimport { CANONICAL_LAYER_NAMES } from \"./layers.js\";\nimport type { StagedChange } from \"./changeset.js\";\nimport { ensureDir, writeFile } from \"../utils/file-io.js\";\nimport { getCliVersion } from \"../utils/spec-version.js\";\nimport { startSpan, endSpan, startActiveSpan } from \"../telemetry/index.js\";\nimport { findProjectRoot } from \"../utils/project-paths.js\";\nimport { getNodeType } from \"../generated/node-types.js\";\nimport type { SpecNodeId } from \"../generated/node-types.js\";\nimport type { ManifestData, ModelOptions } from \"../types/index.js\";\nimport path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport crypto from \"node:crypto\";\n\n/**\n * Derive a deterministic UUID from a path string using SHA-256.\n * Produces a stable UUID4-like value so migration doesn't regenerate UUIDs on every load.\n */\nfunction deterministicUUID(path: string): string {\n const hash = crypto.createHash(\"sha256\").update(path).digest(\"hex\");\n return [\n hash.slice(0, 8),\n hash.slice(8, 12),\n \"4\" + hash.slice(13, 16), // version 4 marker\n ((parseInt(hash.slice(16, 18), 16) & 0x3f) | 0x80).toString(16) + hash.slice(18, 20), // variant\n hash.slice(20, 32),\n ].join(\"-\");\n}\n\n/**\n * Convert a PascalCase title to snake_case.\n * Handles acronyms correctly: \"UXApplication\" \u2192 \"ux_application\", \"OpenAPIDocument\" \u2192 \"open_api_document\".\n */\nfunction titleToSnakeCase(title: string): string {\n return title\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\") // \"APIDoc\" \u2192 \"API_Doc\"\n .replace(/([a-z\\d])([A-Z])/g, \"$1_$2\") // \"appComponent\" \u2192 \"app_Component\"\n .toLowerCase();\n}\n\n/**\n * Derive a snake_case file stem for a given element type.\n * Uses the spec node title for accurate word boundaries (e.g., \"InputSpacePartition\" \u2192 \"input_space_partition\").\n * Falls back to the raw type string when the title is unavailable.\n */\nfunction typeToSnakeStem(specNodeId: string | undefined, type: string): string {\n if (specNodeId) {\n const nodeType = getNodeType(specNodeId as SpecNodeId);\n if (nodeType?.title) {\n return titleToSnakeCase(nodeType.title);\n }\n }\n return type;\n}\n\n/**\n * Graph-internal bookkeeping keys that `layer.ts`'s node<->Element conversion stuffs into the\n * same flat `attributes`/`properties` bag the underlying graph structure uses for everything\n * (it has no separate channel for them) \u2014 `__references__`/`__relationships__` mirror\n * `element.references`/`element.relationships`, which are real, separately-typed fields on\n * `Element` and get serialized to their own place (references inline, relationships to\n * relationships.yaml). These must never reach the persisted YAML's `attributes` object.\n *\n * Double-underscore-prefixed specifically so a schema author would have to go out of their way\n * to collide with one \u2014 confirmed zero of the 191 node schemas declare an attribute with either\n * name. Contrast with the *type-blind* set this replaced, which also unconditionally stripped\n * plain `\"source\"`/`\"x-source-reference\"` \u2014 both of which ARE legitimate, sometimes-required\n * domain attributes on 41 node schemas across 8 layers (e.g. security.accesscondition.source),\n * and neither of which is ever actually used as internal bookkeeping anywhere in this codebase.\n * That collision silently destroyed real data on every save for any element of one of those 41\n * types, without ever failing validation (validation runs on the in-memory element, before this\n * stripping) \u2014 see CHANGELOG for the incident this fixes.\n */\nconst ALWAYS_INTERNAL_ATTRIBUTE_KEYS = new Set([\"__references__\", \"__relationships__\"]);\n\n/**\n * Whether `key` should survive persistence in `specNodeId`'s `attributes`.\n *\n * Only ever strips the two confirmed-always-internal keys above, and even then only when the\n * node type's OWN schema doesn't declare that name as a real attribute \u2014 so if some future\n * schema ever legitimately needs an attribute literally called `__references__` (unlikely, but\n * the whole point of this function is to not have to trust \"unlikely\" again), it still survives.\n * Nothing else is ever stripped: the class of bug this fixes was a hardcoded, type-blind\n * reserved-word blocklist making that same \"trust the list, not the schema\" mistake for\n * `source`/`x-source-reference`. Consulting the schema instead of a fixed string list is what\n * keeps this from silently recurring for some other attribute name in the future.\n */\nfunction isPersistableAttribute(specNodeId: string | undefined, key: string): boolean {\n if (!ALWAYS_INTERNAL_ATTRIBUTE_KEYS.has(key)) return true;\n if (!specNodeId) return false;\n const nodeType = getNodeType(specNodeId as SpecNodeId);\n if (!nodeType) return false;\n return nodeType.requiredAttributes.includes(key) || nodeType.optionalAttributes.includes(key);\n}\n\n// Fallback for runtime environments where TELEMETRY_ENABLED is not defined by esbuild\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\n/**\n * Model class representing the complete architecture model\n * Uses GraphModel internally for graph-based storage\n */\nexport class Model {\n rootPath: string;\n manifest: Manifest;\n graph: GraphModel;\n layers: Map<string, Layer>;\n relationships: Relationships;\n lazyLoad: boolean;\n private loadedLayers: Set<string>;\n private virtualProjectionEngine?: VirtualProjectionEngine;\n /** ID of the currently active changeset, or null if none is active. Set during load(). */\n private _activeChangesetId: string | null = null;\n /** Layer names that exist only in staged changes (not yet committed to base model). */\n private _stagedLayerNames: Set<string> = new Set();\n\n constructor(rootPath: string, manifest: Manifest, options: ModelOptions = {}) {\n this.rootPath = rootPath;\n this.manifest = manifest;\n this.graph = new GraphModel();\n this.layers = new Map();\n this.relationships = new Relationships();\n this.lazyLoad = options.lazyLoad ?? false;\n this.loadedLayers = new Set();\n }\n\n /**\n * Get the committed base layer (no staging projection).\n * Used internally by VirtualProjectionEngine and commit() to access the raw base model\n * without triggering infinite recursion or double-projection.\n */\n async getBaseLayer(name: string): Promise<Layer | undefined> {\n if (this.lazyLoad && !this.loadedLayers.has(name)) {\n await this.loadLayer(name);\n }\n return this.layers.get(name);\n }\n\n /**\n * Get a layer, transparently merging staged changes when an active changeset exists.\n * Falls back to the committed base layer if no changeset is active or projection fails.\n */\n async getLayer(name: string): Promise<Layer | undefined> {\n if (this.lazyLoad && !this.loadedLayers.has(name)) {\n await this.loadLayer(name);\n }\n\n // If an active changeset is present, return a projected (base + staged) layer\n if (this._activeChangesetId) {\n try {\n return await this.getVirtualProjectionEngine().projectLayer(\n this,\n this._activeChangesetId,\n name\n );\n } catch {\n // Fall back to base layer on projection failure (e.g. corrupt changeset)\n return this.layers.get(name);\n }\n }\n\n return this.layers.get(name);\n }\n\n /**\n * Get an element by ID across all layers\n * Supports both UUID and semantic ID (layer.type.kebab-name) lookup\n */\n getElementById(id: string): Element | undefined {\n for (const layer of this.layers.values()) {\n const element = layer.getElement(id);\n if (element) {\n return element;\n }\n }\n return undefined;\n }\n\n /**\n * Get virtual projection engine (lazily initialized)\n */\n getVirtualProjectionEngine(): VirtualProjectionEngine {\n if (!this.virtualProjectionEngine) {\n this.virtualProjectionEngine = new VirtualProjectionEngine(this.rootPath);\n }\n return this.virtualProjectionEngine;\n }\n\n /**\n * Returns the active changeset ID, or null if no changeset is active.\n * Used by commands that need to project the full model (e.g. validate).\n */\n getActiveChangesetId(): string | null {\n return this._activeChangesetId;\n }\n\n /**\n * Load a layer from disk (model/XX_layername/*.yaml files)\n */\n async loadLayer(name: string): Promise<void> {\n await startActiveSpan(\"layer.load\", async (layerSpan) => {\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n\n let layerPath: string | null = null;\n\n // Auto-discover by scanning directory with naming convention\n const modelDir = `${this.rootPath}/documentation-robotics/model`;\n\n try {\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n const layerDir = entries.find(\n (e) =>\n e.isDirectory() && e.name.match(/^\\d{2}_/) && e.name.replace(/^\\d{2}_/, \"\") === name\n );\n\n if (layerDir) {\n layerPath = `${modelDir}/${layerDir.name}`;\n }\n } catch {\n // Model directory doesn't exist or can't be read\n }\n\n if (!layerPath) {\n return; // Layer directory not found\n }\n\n const layer = new Layer(name, this.graph);\n let parseErrorCount = 0;\n\n // Load all YAML files in the layer directory\n const files = await fs.readdir(layerPath);\n for (const file of files) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n const filePath = `${layerPath}/${file}`;\n try {\n const yamlContent = await fs.readFile(filePath, \"utf-8\");\n const elements = yaml.parse(yamlContent);\n\n // Add each element from the YAML file\n if (elements && typeof elements === \"object\") {\n for (const [key, element] of Object.entries(elements)) {\n if (element && typeof element === \"object\") {\n const el: any = element;\n\n // Derive id, path, and type using the 3-case migration logic:\n // Case 1: element already has both id (UUID) and path \u2192 use as-is\n // Case 2: element.id looks like a slug (layer.type.name) \u2192 move to path, generate UUID\n // Case 3: element has UUID id but no path \u2192 derive path from layer_id.type.kebab(name)\n const isSlugPattern = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/.test(el.id || \"\");\n const isUUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(el.id || \"\");\n\n let elementId: string;\n let elementPath: string;\n let extractedType: string | undefined = undefined;\n\n if (el.path && isUUIDPattern) {\n // Case 1: already has both path and UUID id\n elementId = el.id;\n elementPath = el.path;\n const pathParts = el.path.split(\".\");\n if (pathParts.length >= 3) extractedType = pathParts[1];\n } else if (el.path && !isUUIDPattern) {\n // Has a path but id is not a UUID \u2014 migrate id to deterministic UUID\n elementPath = el.path;\n elementId = deterministicUUID(el.path);\n const pathParts = el.path.split(\".\");\n if (pathParts.length >= 3) extractedType = pathParts[1];\n } else if (isSlugPattern) {\n // Case 2: slug-looking id \u2192 move to path, generate deterministic UUID\n elementPath = el.id;\n elementId = deterministicUUID(el.id);\n const parts = el.id.split(\".\");\n if (parts.length >= 3) extractedType = parts[1];\n } else if (isUUIDPattern) {\n // Case 3: UUID id, no path \u2192 derive path\n elementId = el.id;\n const layerId = el.layer_id || name;\n const rawType = el.type || \"\";\n const rawName = el.name || key;\n const kebabName = rawName.toLowerCase().replace(/[\\s_]+/g, \"-\").replace(/[^a-z0-9-]/g, \"\").replace(/-+/g, \"-\").replace(/^-+|-+$/g, \"\");\n elementPath = `${layerId}.${rawType}.${kebabName}`;\n extractedType = rawType || undefined;\n } else {\n // Fallback: derive a path from available data, generate deterministic UUID\n const layerId = el.layer_id || name;\n const rawType = el.type || (typeof el.id === \"string\" && el.id.includes(\".\") ? el.id.split(\".\")[1] : \"\") || \"\";\n const rawName = el.name || key;\n const kebabName = rawName.toLowerCase().replace(/[\\s_]+/g, \"-\").replace(/[^a-z0-9-]/g, \"\").replace(/-+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"unknown\";\n elementPath = `${layerId}.${rawType || \"element\"}.${kebabName}`;\n elementId = deterministicUUID(elementPath);\n if (rawType) extractedType = rawType;\n else if (typeof el.id === \"string\" && el.id.includes(\".\")) {\n const parts = el.id.split(\".\");\n if (parts.length >= 3) extractedType = parts[1];\n }\n }\n\n const elementName = el.name || key;\n const elementType = el.type || extractedType;\n const layerId = el.layer_id || name;\n\n const newElement = new Element({\n id: elementId,\n path: elementPath,\n spec_node_id: el.spec_node_id,\n layer_id: layerId,\n attributes: el.attributes,\n source_reference: el.source_reference,\n metadata: el.metadata,\n name: elementName,\n type: elementType,\n description: el.description || \"\",\n relationships: el.relationships || [],\n references: el.references || [],\n layer: layerId,\n } as any);\n layer.addElement(newElement);\n }\n }\n }\n } catch (fileError) {\n // Log YAML parsing errors but continue loading other files\n const errorMsg = fileError instanceof Error ? fileError.message : String(fileError);\n console.error(`Warning: Failed to parse ${filePath}: ${errorMsg}`);\n parseErrorCount++;\n // Continue with next file\n }\n }\n }\n\n // If we had parse errors, add a note to the layer\n if (parseErrorCount > 0) {\n console.error(\n `Warning: Layer '${name}' loaded with ${parseErrorCount} YAML parse error(s)`\n );\n }\n\n this.layers.set(name, layer);\n this.loadedLayers.add(name);\n\n layerSpan.setAttribute(\"layer.element_count\", layer.elements.size);\n }, { \"layer.name\": name });\n }\n\n /**\n * Add a layer to the model\n */\n addLayer(layer: Layer): void {\n // If the layer has elements in its isolated graph, migrate them to the model's graph\n if (layer.graph.getNodeCount() > 0) {\n for (const node of layer.graph.nodes.values()) {\n this.graph.addNode(node);\n }\n for (const edge of layer.graph.edges.values()) {\n this.graph.addEdge(edge);\n }\n }\n // Ensure layer uses the model's graph for future operations\n layer.graph = this.graph;\n this.layers.set(layer.name, layer);\n this.loadedLayers.add(layer.name);\n }\n\n /**\n * Get all layer names, including layers that exist only in staged changes.\n * When an active changeset introduces elements for a layer with no committed data,\n * that layer name is included so findElementLayer() and other callers can discover it.\n */\n getLayerNames(): string[] {\n const base = Array.from(this.layers.keys());\n if (this._stagedLayerNames.size === 0) {\n return base;\n }\n // Include staged-only layers (layers that have staged elements but no committed elements)\n const staged = Array.from(this._stagedLayerNames).filter((l) => !this.layers.has(l));\n return [...base, ...staged];\n }\n\n /**\n * Save a layer to disk (model/XX_layername/*.yaml)\n */\n async saveLayer(name: string): Promise<void> {\n const layer = this.layers.get(name);\n if (!layer) {\n throw new Error(`Layer ${name} not found`);\n }\n\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n\n let layerPath: string | null = null;\n\n // Discover by scanning directory\n const modelDir = `${this.rootPath}/documentation-robotics/model`;\n\n try {\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n const layerDir = entries.find(\n (e) =>\n e.isDirectory() && e.name.match(/^\\d{2}_/) && e.name.replace(/^\\d{2}_/, \"\") === name\n );\n\n if (layerDir) {\n layerPath = `${modelDir}/${layerDir.name}`;\n }\n } catch {\n // Model directory doesn't exist or can't be read\n }\n\n // If still not found, create using default format\n if (!layerPath) {\n // Find the order number for this layer\n // Handle layer names with or without numeric prefix (e.g., \"motivation\" or \"01-motivation\")\n const layerNameWithoutPrefix = name.replace(/^\\d{2}-/, \"\");\n const index = CANONICAL_LAYER_NAMES.indexOf(layerNameWithoutPrefix as typeof CANONICAL_LAYER_NAMES[number]);\n if (index >= 0) {\n const orderNum = String(index + 1).padStart(2, \"0\");\n layerPath = `${this.rootPath}/documentation-robotics/model/${orderNum}_${layerNameWithoutPrefix}`;\n await ensureDir(layerPath);\n } else {\n throw new Error(`Unknown layer ${name} and no path configured in manifest`);\n }\n }\n\n // Delete all existing YAML files in the layer directory to ensure\n // we don't keep stale files after element deletion\n try {\n const existingFiles = await fs.readdir(layerPath);\n for (const file of existingFiles) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n await fs.unlink(`${layerPath}/${file}`);\n }\n }\n } catch (err) {\n // Only ignore \"directory not found\" errors \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw new Error(\n `Failed to clean existing YAML files: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n // Group elements by type, computing the snake_case file stem from the spec node title\n const elementsByType = new Map<string, { stem: string; elements: Element[] }>();\n for (const element of layer.elements.values()) {\n const typeKey = element.type;\n if (!elementsByType.has(typeKey)) {\n const stem = typeToSnakeStem(element.spec_node_id, element.type);\n elementsByType.set(typeKey, { stem, elements: [] });\n }\n elementsByType.get(typeKey)!.elements.push(element);\n }\n\n // Write each type to its own YAML file\n for (const [, { stem, elements }] of elementsByType) {\n const filename = `${stem}.yaml`;\n const filePath = `${layerPath}/${filename}`;\n\n // Convert elements to spec-node format YAML\n const yamlData: any = {};\n for (const element of elements) {\n const json = element.toJSON();\n\n // Clean attributes: remove graph-internal keys that should not be persisted \u2014 see\n // isPersistableAttribute's own doc for why this checks the schema rather than a\n // hardcoded name list.\n const cleanAttrs = json.attributes\n ? Object.fromEntries(\n Object.entries(json.attributes).filter(([k]) =>\n isPersistableAttribute(json.spec_node_id, k)\n )\n )\n : undefined;\n\n // Use path as YAML key (human-readable); fall back to id for elements without path\n const key = element.path || element.id;\n yamlData[key] = {\n id: json.id,\n path: json.path,\n spec_node_id: json.spec_node_id,\n type: json.type,\n layer_id: json.layer_id,\n name: json.name,\n ...(json.description && { description: json.description }),\n ...(cleanAttrs && Object.keys(cleanAttrs).length > 0 && { attributes: cleanAttrs }),\n ...(json.source_reference && { source_reference: json.source_reference }),\n ...(json.metadata && { metadata: json.metadata }),\n ...(json.references && json.references.length > 0 && { references: json.references }),\n // Relationships are now stored in centralized relationships.yaml, not inline\n };\n }\n\n await writeFile(filePath, yaml.stringify(yamlData));\n }\n\n layer.markClean();\n }\n\n /**\n * Save model (saves all dirty layers and manifest)\n */\n async save(): Promise<void> {\n await this.saveDirtyLayers();\n await this.saveManifest();\n }\n\n /**\n * Save all dirty layers to disk\n */\n async saveDirtyLayers(): Promise<void> {\n // Count dirty layers before operation\n let dirtyLayerCount = 0;\n for (const layer of this.layers.values()) {\n if (layer.isDirty()) {\n dirtyLayerCount++;\n }\n }\n\n const span = isTelemetryEnabled\n ? startSpan(\"model.save\", {\n \"model.path\": this.rootPath,\n \"model.dirty_layer_count\": dirtyLayerCount,\n })\n : null;\n\n try {\n for (const layer of this.layers.values()) {\n if (layer.isDirty()) {\n await this.saveLayer(layer.name);\n }\n }\n } finally {\n if (isTelemetryEnabled) {\n endSpan(span);\n }\n }\n }\n\n /**\n * Save the manifest to disk (documentation-robotics/model/manifest.yaml)\n */\n async saveManifest(): Promise<void> {\n this.manifest.updateModified();\n const yaml = await import(\"yaml\");\n const manifestPath = `${this.rootPath}/documentation-robotics/model/manifest.yaml`;\n\n // Convert to YAML format\n const yamlData: any = {\n version: this.manifest.version,\n schema: \"documentation-robotics-v1\",\n cli_version: getCliVersion(),\n spec_version: this.manifest.specVersion,\n created: this.manifest.created,\n updated: this.manifest.modified,\n project: {\n name: this.manifest.name,\n description: this.manifest.description,\n version: this.manifest.version,\n },\n documentation: \".dr/README.md\",\n layers: {},\n };\n\n // Generate layer structure from current model state\n for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {\n const layerName = CANONICAL_LAYER_NAMES[i];\n const layer = this.layers.get(layerName);\n const orderNum = String(i + 1).padStart(2, \"0\");\n\n yamlData.layers[layerName] = {\n order: i + 1,\n name: layerName.charAt(0).toUpperCase() + layerName.slice(1).replace(\"-\", \" \"),\n path: `documentation-robotics/model/${orderNum}_${layerName}/`,\n enabled: true,\n ...(layer && { elements: this.getLayerElementCounts(layer) }),\n };\n }\n\n if (this.manifest.changeset_history && this.manifest.changeset_history.length > 0) {\n yamlData.changeset_history = this.manifest.changeset_history;\n }\n\n await ensureDir(`${this.rootPath}/documentation-robotics/model`);\n await writeFile(manifestPath, yaml.stringify(yamlData));\n }\n\n private getLayerElementCounts(layer: Layer): Record<string, number> {\n const counts: Record<string, number> = {};\n for (const element of layer.elements.values()) {\n counts[element.type] = (counts[element.type] || 0) + 1;\n }\n return counts;\n }\n\n /**\n * Load relationships from relationships.yaml\n */\n async loadRelationships(): Promise<void> {\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n const relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;\n\n let data: any;\n try {\n const yamlContent = await fs.readFile(relationshipsPath, \"utf-8\");\n data = yaml.parse(yamlContent);\n } catch (err) {\n // If file doesn't exist, that's okay - start with empty relationships\n if ((err as any).code !== \"ENOENT\") {\n // But if it's a different error (YAML parse, permission denied, etc), throw it\n // so the caller knows about the issue\n throw new Error(\n `Failed to load relationships.yaml: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n // ENOENT: file doesn't exist, return with empty relationships\n return;\n }\n\n // Validate that parsed content is an array\n if (!Array.isArray(data)) {\n const typeLabel = data === null ? \"null\" : typeof data;\n console.warn(\n `Warning: relationships.yaml contains non-array content (received ${typeLabel}). Expected an array of relationships. Skipping relationship loading.`\n );\n return;\n }\n\n this.relationships = Relationships.fromArray(data);\n // Sync relationships to graph as a secondary in-memory index.\n // addEdge() may throw if nodes from other layers haven't been loaded yet;\n // failures are silently skipped \u2014 this.relationships holds the primary data.\n for (let i = 0; i < data.length; i++) {\n const rel = data[i];\n const edge: GraphEdge = {\n id: `rel-${i}-${crypto.randomBytes(4).toString(\"hex\")}`,\n source: rel.source,\n destination: rel.target,\n predicate: rel.predicate,\n properties: rel.properties,\n category: rel.category,\n };\n try {\n this.graph.addEdge(edge);\n } catch {\n // Silently skip edges whose nodes haven't been loaded yet.\n // The primary relationship data lives in this.relationships; the graph\n // is a secondary index. Cross-layer relationships referencing nodes from\n // unloaded layers are expected during partial model construction.\n }\n }\n }\n\n /**\n * Save relationships to relationships.yaml\n */\n async saveRelationships(): Promise<void> {\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n const relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;\n\n await ensureDir(`${this.rootPath}/documentation-robotics/model`);\n\n const data = this.relationships.toArray();\n\n // Add header comment with timestamp to ensure file always changes\n const timestamp = new Date().toISOString();\n const header = `# Intra-layer relationships\n# Format: source_id -> predicate -> target_id\n# Last updated: ${timestamp}\n\n`;\n const yamlContent = header + yaml.stringify(data);\n\n await fs.writeFile(relationshipsPath, yamlContent, \"utf-8\");\n this.relationships.markClean();\n }\n\n /**\n * Resolve model paths (private helper)\n *\n * @param startPath - Starting path for search\n * @returns Project root and manifest path\n */\n private static async resolveModelPaths(startPath?: string): Promise<{\n projectRoot: string;\n manifestPath: string;\n }> {\n const searchPath = startPath || process.cwd();\n\n // Optional override via env var (highest priority)\n if (process.env.DR_MODEL_PATH) {\n const envPath = path.resolve(process.env.DR_MODEL_PATH);\n let manifestPath: string;\n\n if (envPath.endsWith(\"manifest.yaml\")) {\n manifestPath = envPath;\n } else if (envPath.endsWith(\"model\")) {\n manifestPath = path.join(envPath, \"manifest.yaml\");\n } else {\n manifestPath = path.join(envPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n }\n\n try {\n await fs.access(manifestPath);\n const projectRoot = path.dirname(path.dirname(path.dirname(manifestPath)));\n return { projectRoot, manifestPath: path.normalize(manifestPath) };\n } catch {\n throw new Error(`Model not found at DR_MODEL_PATH: ${process.env.DR_MODEL_PATH}`);\n }\n }\n\n // Use findProjectRoot to locate documentation_robotics/ folder\n const projectRoot = await findProjectRoot(searchPath);\n\n if (!projectRoot) {\n throw new Error(\n \"No DR project found. Could not find documentation_robotics/ folder.\\n\" +\n 'Run \"dr init\" to create a new project, or navigate to a directory containing a DR project.'\n );\n }\n\n const manifestPath = path.join(projectRoot, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n\n try {\n await fs.access(manifestPath);\n return { projectRoot, manifestPath };\n } catch {\n throw new Error(\n `Found documentation_robotics/ at ${projectRoot} but no model found. ` +\n `Expected: ${manifestPath}\\n` +\n `Run \"dr init\" to create a model.`\n );\n }\n }\n\n /**\n * Load a model from disk\n *\n * Expected structure:\n * <project-root>/\n * \u2514\u2500\u2500 documentation_robotics/\n * \u2514\u2500\u2500 model/\n * \u2514\u2500\u2500 manifest.yaml\n *\n * @param startPath - Starting path for project search (defaults to process.cwd())\n * @param options - Model loading options\n * @returns Loaded Model instance\n */\n static async load(startPath?: string, options: ModelOptions = {}): Promise<Model> {\n return startActiveSpan(\"model.load\", async (span) => {\n // Resolve project root and manifest path\n const { projectRoot, manifestPath } = await Model.resolveModelPaths(startPath);\n\n const yaml = await import(\"yaml\");\n\n // Load manifest from resolved path\n const yamlContent = await fs.readFile(manifestPath, \"utf-8\");\n const manifestYaml = yaml.parse(yamlContent);\n\n // Convert manifest to internal format\n const manifestData: any = {\n name: manifestYaml.project?.name || \"Unnamed Model\",\n description: manifestYaml.project?.description || \"\",\n version: manifestYaml.project?.version || manifestYaml.version || \"0.1.0\",\n specVersion: manifestYaml.spec_version || \"0.6.0\",\n created: manifestYaml.created || new Date().toISOString(),\n modified: manifestYaml.updated || new Date().toISOString(),\n };\n const manifest = new Manifest(manifestData);\n const model = new Model(projectRoot, manifest, options);\n\n // Load all available layers if lazyLoad is false\n if (!options.lazyLoad) {\n try {\n // If manifest has layer definitions, use those\n if (manifestYaml.layers) {\n for (const layerName of Object.keys(manifestYaml.layers)) {\n const layerConfig = manifestYaml.layers[layerName];\n if (layerConfig.enabled !== false) {\n if (!options.layers || options.layers.includes(layerName)) {\n await model.loadLayer(layerName);\n }\n }\n }\n } else {\n // Otherwise scan for layer directories\n const modelDir = manifestPath.replace(\"/manifest.yaml\", \"\");\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name.match(/^\\d{2}_/)) {\n const layerName = entry.name.replace(/^\\d{2}_/, \"\");\n if (!options.layers || options.layers.includes(layerName)) {\n await model.loadLayer(layerName);\n }\n }\n }\n }\n } catch (e) {\n // Check if this is a \"directory not found\" error (expected case)\n const isNotFoundError = (e as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n // Re-throw non-ENOENT errors: permission errors, YAML parsing failures, I/O errors, etc.\n throw new Error(\n `Failed to load layers: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n // ENOENT is expected if layer directory doesn't exist yet, so just continue\n }\n }\n\n // Load relationships from relationships.yaml\n await model.loadRelationships();\n\n // Check for active changeset and pre-load staged metadata for transparent projection.\n // This enables getLayer() to return projected views and getLayerNames() to include\n // layers that exist only in staged changes.\n const activePath = path.join(\n projectRoot,\n \"documentation-robotics\",\n \"changesets\",\n \".active\"\n );\n try {\n const activeContent = await fs.readFile(activePath, \"utf-8\");\n const activeId = activeContent.trim();\n if (activeId) {\n model._activeChangesetId = activeId;\n const storage = new StagedChangesetStorage(projectRoot);\n const changeset = await storage.load(activeId);\n if (changeset) {\n for (const change of changeset.changes as StagedChange[]) {\n // Track layer names for getLayerNames()\n if (\n change.type === \"add\" ||\n change.type === \"update\" ||\n change.type === \"delete\"\n ) {\n model._stagedLayerNames.add(change.layerName);\n }\n\n // Inject staged relationship-adds into model.relationships so that\n // read commands (list, show, export) transparently see staged relationships.\n if (change.type === \"relationship-add\" && change.after) {\n const rel = change.after as {\n source: string;\n target: string;\n predicate: string;\n layer: string;\n targetLayer?: string;\n category?: \"structural\" | \"behavioral\";\n properties?: Record<string, unknown>;\n };\n // Only inject if not already in relationships.yaml (prevents duplicates\n // if .active file is stale after a partial commit or manual edit).\n const alreadyPresent = model.relationships.find(\n rel.source,\n rel.target,\n rel.predicate\n );\n if (alreadyPresent.length === 0) {\n model.relationships.add({\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n category: rel.category ?? \"structural\",\n ...(rel.properties ? { properties: rel.properties } : {}),\n });\n }\n }\n\n // Remove staged relationship-deletes from model.relationships so that\n // read commands see the relationship as already deleted.\n if (change.type === \"relationship-delete\" && change.before) {\n const rel = change.before as {\n source: string;\n target: string;\n predicate?: string;\n };\n model.relationships.delete(rel.source, rel.target, rel.predicate);\n }\n }\n }\n }\n } catch {\n // No active changeset or unreadable \u2014 proceed with base model only\n model._activeChangesetId = null;\n }\n\n // Count total entities across all layers\n let entityCount = 0;\n for (const layer of model.layers.values()) {\n entityCount += layer.elements.size;\n }\n\n span.setAttribute(\"model.entity_count\", entityCount);\n span.setAttribute(\"model.layer_count\", model.layers.size);\n\n return model;\n }, {\n \"model.path\": startPath || process.cwd(),\n \"model.type\": \"dr\",\n });\n }\n\n /**\n * Initialize a new model in a directory\n */\n static async init(\n rootPath: string,\n manifestData: ManifestData,\n options: ModelOptions = {}\n ): Promise<Model> {\n // Create model directory structure\n await ensureDir(`${rootPath}/documentation-robotics/model`);\n\n for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {\n const orderNum = String(i + 1).padStart(2, \"0\");\n const layerName = CANONICAL_LAYER_NAMES[i];\n await ensureDir(`${rootPath}/documentation-robotics/model/${orderNum}_${layerName}`);\n }\n\n const manifest = new Manifest(manifestData);\n const model = new Model(rootPath, manifest, options);\n\n await model.saveManifest();\n\n return model;\n }\n\n /**\n * Graph-based API methods for querying the model\n * These methods delegate to the underlying GraphModel for efficient queries\n */\n\n /**\n * Get a node by its UUID\n */\n getNode(id: string) {\n return this.graph.getNode(id);\n }\n\n /**\n * Get all nodes in a specific layer\n */\n getNodesByLayer(layerId: string) {\n return this.graph.getNodesByLayer(layerId);\n }\n\n /**\n * Get all nodes of a specific type\n */\n getNodesByType(type: string) {\n return this.graph.getNodesByType(type);\n }\n\n /**\n * Get all edges (relationships) in the model\n */\n getAllEdges() {\n return this.graph.getAllEdges();\n }\n\n /**\n * Get edges originating from a specific node\n */\n getEdgesFrom(nodeId: string, predicate?: string) {\n return this.graph.getEdgesFrom(nodeId, predicate);\n }\n\n /**\n * Get edges terminating at a specific node\n */\n getEdgesTo(nodeId: string, predicate?: string) {\n return this.graph.getEdgesTo(nodeId, predicate);\n }\n\n /**\n * Get edges between two specific nodes\n */\n getEdgesBetween(sourceId: string, destinationId: string, predicate?: string) {\n return this.graph.getEdgesBetween(sourceId, destinationId, predicate);\n }\n\n /**\n * Traverse the graph starting from a node, following a specific predicate\n */\n async traverse(startNodeId: string, predicate: string, maxDepth: number = 10) {\n return this.graph.traverse(startNodeId, predicate, maxDepth);\n }\n\n /**\n * Create a backup of the current model state\n * Used for rollback support in migration operations\n *\n * @param label Descriptive label for the backup (e.g., \"pre-migration\")\n * @returns Path to the backup directory\n */\n async createBackup(label: string): Promise<string> {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupDir = path.join(this.rootPath, \".dr-backups\", `${label}-${timestamp}`);\n\n // Create backup directory\n await ensureDir(backupDir);\n\n // Backup layers directory\n const layersSource = path.join(this.rootPath, \"documentation-robotics\", \"model\");\n const layersBackup = path.join(backupDir, \"layers\");\n if (await this.directoryExists(layersSource)) {\n await this.copyDirectory(layersSource, layersBackup);\n }\n\n // Backup manifest\n const manifestSource = path.join(this.rootPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n const manifestBackup = path.join(backupDir, \"manifest.yaml\");\n if (await this.fileExists(manifestSource)) {\n await fs.copyFile(manifestSource, manifestBackup);\n }\n\n return backupDir;\n }\n\n /**\n * Restore model from a backup directory\n * Used for rollback after failed migrations\n *\n * @param backupPath Path to the backup directory\n */\n async restoreFromBackup(backupPath: string): Promise<void> {\n // Restore layers\n const layersBackup = path.join(backupPath, \"layers\");\n const layersTarget = path.join(this.rootPath, \"documentation-robotics\", \"model\");\n if (await this.directoryExists(layersBackup)) {\n await this.copyDirectory(layersBackup, layersTarget);\n }\n\n // Restore manifest\n const manifestBackup = path.join(backupPath, \"manifest.yaml\");\n const manifestTarget = path.join(this.rootPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n if (await this.fileExists(manifestBackup)) {\n await fs.copyFile(manifestBackup, manifestTarget);\n }\n\n // Reload model to refresh in-memory state\n const reloadedModel = await Model.load(this.rootPath, { lazyLoad: this.lazyLoad });\n this.layers = reloadedModel.layers;\n this.manifest = reloadedModel.manifest;\n this.graph = reloadedModel.graph;\n this.relationships = reloadedModel.relationships;\n }\n\n /**\n * Save dirty layers with atomic write semantics\n * Writes to temporary file first, then renames on success to ensure data safety\n *\n * @throws Error if atomic write fails\n */\n async saveDirtyLayersAtomic(): Promise<void> {\n // First, save all layers to temporary files\n const tempLayerWrites: Array<{ layer: Layer; tempPath: string }> = [];\n\n try {\n for (const layer of this.layers.values()) {\n if (layer.isDirty()) {\n const layerPath = path.join(\n this.rootPath,\n \"documentation-robotics\",\n \"model\",\n `${layer.name}_layer`\n );\n\n const tempPath = `${layerPath}.tmp`;\n await ensureDir(tempPath);\n\n // Write layer elements to temp directory\n for (const element of layer.elements.values()) {\n const elementPath = path.join(tempPath, `${element.path || element.id}.json`);\n const content = JSON.stringify(element.toJSON(), null, 2);\n await writeFile(elementPath, content);\n }\n\n tempLayerWrites.push({ layer, tempPath });\n }\n }\n\n // All temp writes succeeded, now commit by renaming\n for (const { layer, tempPath } of tempLayerWrites) {\n const layerPath = path.join(\n this.rootPath,\n \"documentation-robotics\",\n \"model\",\n `${layer.name}_layer`\n );\n\n // Remove old directory and rename temp to actual\n if (await this.directoryExists(layerPath)) {\n await fs.rm(layerPath, { recursive: true, force: true });\n }\n await fs.rename(tempPath, layerPath);\n }\n } catch (error) {\n // Cleanup any temp directories on failure\n for (const { tempPath } of tempLayerWrites) {\n if (await this.directoryExists(tempPath)) {\n await fs.rm(tempPath, { recursive: true, force: true });\n }\n }\n throw error;\n }\n }\n\n /**\n * Helper: Check if file exists\n */\n private async fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Helper: Check if directory exists\n */\n private async directoryExists(dirPath: string): Promise<boolean> {\n try {\n const stat = await fs.stat(dirPath);\n return stat.isDirectory();\n } catch {\n return false;\n }\n }\n\n /**\n * Helper: Recursively copy directory\n */\n private async copyDirectory(src: string, dest: string): Promise<void> {\n await ensureDir(dest);\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await this.copyDirectory(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Layer } from \"./layer.js\";\nimport { Manifest } from \"./manifest.js\";\nimport { Element } from \"./element.js\";\nimport { GraphModel, type GraphEdge } from \"./graph-model.js\";\nimport { VirtualProjectionEngine } from \"./virtual-projection.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { Relationships } from \"./relationships.js\";\nimport { CANONICAL_LAYER_NAMES } from \"./layers.js\";\nimport type { StagedChange } from \"./changeset.js\";\nimport { ensureDir, writeFile } from \"../utils/file-io.js\";\nimport { getCliVersion } from \"../utils/spec-version.js\";\nimport { startSpan, endSpan, startActiveSpan } from \"../telemetry/index.js\";\nimport { findProjectRoot } from \"../utils/project-paths.js\";\nimport { getNodeType } from \"../generated/node-types.js\";\nimport type { SpecNodeId } from \"../generated/node-types.js\";\nimport type { ManifestData, ModelOptions } from \"../types/index.js\";\nimport path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport crypto from \"node:crypto\";\n\n/**\n * Derive a deterministic UUID from a path string using SHA-256.\n * Produces a stable UUID4-like value so migration doesn't regenerate UUIDs on every load.\n */\nfunction deterministicUUID(path: string): string {\n const hash = crypto.createHash(\"sha256\").update(path).digest(\"hex\");\n return [\n hash.slice(0, 8),\n hash.slice(8, 12),\n \"4\" + hash.slice(13, 16), // version 4 marker\n ((parseInt(hash.slice(16, 18), 16) & 0x3f) | 0x80).toString(16) + hash.slice(18, 20), // variant\n hash.slice(20, 32),\n ].join(\"-\");\n}\n\n/**\n * Convert a PascalCase title to snake_case.\n * Handles acronyms correctly: \"UXApplication\" \u2192 \"ux_application\", \"OpenAPIDocument\" \u2192 \"open_api_document\".\n */\nfunction titleToSnakeCase(title: string): string {\n return title\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\") // \"APIDoc\" \u2192 \"API_Doc\"\n .replace(/([a-z\\d])([A-Z])/g, \"$1_$2\") // \"appComponent\" \u2192 \"app_Component\"\n .toLowerCase();\n}\n\n/**\n * Derive a snake_case file stem for a given element type.\n * Uses the spec node title for accurate word boundaries (e.g., \"InputSpacePartition\" \u2192 \"input_space_partition\").\n * Falls back to the raw type string when the title is unavailable.\n */\nfunction typeToSnakeStem(specNodeId: string | undefined, type: string): string {\n if (specNodeId) {\n const nodeType = getNodeType(specNodeId as SpecNodeId);\n if (nodeType?.title) {\n return titleToSnakeCase(nodeType.title);\n }\n }\n return type;\n}\n\n/**\n * Graph-internal bookkeeping keys that `layer.ts`'s node<->Element conversion stuffs into the\n * same flat `attributes`/`properties` bag the underlying graph structure uses for everything\n * (it has no separate channel for them) \u2014 `__references__`/`__relationships__`/`__semanticId__`\n * mirror `element.references`/`element.relationships`/`element.semanticId`, which are real,\n * separately-typed fields on `Element` and get serialized to their own place (references\n * inline, relationships to relationships.yaml, semanticId isn't persisted at all \u2014 it only\n * exists to resolve legacy-format elements during a single load). These must never reach the\n * persisted YAML's `attributes` object.\n *\n * Double-underscore-prefixed specifically so a schema author would have to go out of their way\n * to collide with one \u2014 confirmed zero of the 191 node schemas declare an attribute with any of\n * these names. Contrast with the *type-blind* set this replaced, which also unconditionally\n * stripped plain `\"source\"`/`\"x-source-reference\"` \u2014 both of which ARE legitimate, sometimes-\n * required domain attributes on 41 node schemas across 8 layers (e.g.\n * security.accesscondition.source), and neither of which is ever actually used as internal\n * bookkeeping anywhere in this codebase. That collision silently destroyed real data on every\n * save for any element of one of those 41 types, without ever failing validation (validation\n * runs on the in-memory element, before this stripping) \u2014 see CHANGELOG for the incident this\n * fixes.\n *\n * `__semanticId__` is listed here purely as defense-in-depth, not as the primary guard: it can\n * only ever reach `attributes` via `GraphModel.fromElement()` aliasing `properties` and\n * `attributes` to the same object, which `layer.ts`'s bookkeeping-key injection would then\n * mutate through. That aliasing is fixed at the source in graph-model.ts (`properties` is now\n * always an independent copy), so `__semanticId__` should never actually arrive here \u2014 but\n * listing it means a regression of that aliasing fix fails safe instead of silently reopening\n * the same leak `__semanticId__` briefly caused.\n */\nconst ALWAYS_INTERNAL_ATTRIBUTE_KEYS = new Set([\"__references__\", \"__relationships__\", \"__semanticId__\"]);\n\n/**\n * Whether `key` should survive persistence in `specNodeId`'s `attributes`.\n *\n * Only ever strips the two confirmed-always-internal keys above, and even then only when the\n * node type's OWN schema doesn't declare that name as a real attribute \u2014 so if some future\n * schema ever legitimately needs an attribute literally called `__references__` (unlikely, but\n * the whole point of this function is to not have to trust \"unlikely\" again), it still survives.\n * Nothing else is ever stripped: the class of bug this fixes was a hardcoded, type-blind\n * reserved-word blocklist making that same \"trust the list, not the schema\" mistake for\n * `source`/`x-source-reference`. Consulting the schema instead of a fixed string list is what\n * keeps this from silently recurring for some other attribute name in the future.\n */\nfunction isPersistableAttribute(specNodeId: string | undefined, key: string): boolean {\n if (!ALWAYS_INTERNAL_ATTRIBUTE_KEYS.has(key)) return true;\n if (!specNodeId) return false;\n const nodeType = getNodeType(specNodeId as SpecNodeId);\n if (!nodeType) return false;\n return nodeType.requiredAttributes.includes(key) || nodeType.optionalAttributes.includes(key);\n}\n\n// Fallback for runtime environments where TELEMETRY_ENABLED is not defined by esbuild\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\n/**\n * Model class representing the complete architecture model\n * Uses GraphModel internally for graph-based storage\n */\nexport class Model {\n rootPath: string;\n manifest: Manifest;\n graph: GraphModel;\n layers: Map<string, Layer>;\n relationships: Relationships;\n lazyLoad: boolean;\n private loadedLayers: Set<string>;\n private virtualProjectionEngine?: VirtualProjectionEngine;\n /** ID of the currently active changeset, or null if none is active. Set during load(). */\n private _activeChangesetId: string | null = null;\n /** Layer names that exist only in staged changes (not yet committed to base model). */\n private _stagedLayerNames: Set<string> = new Set();\n\n constructor(rootPath: string, manifest: Manifest, options: ModelOptions = {}) {\n this.rootPath = rootPath;\n this.manifest = manifest;\n this.graph = new GraphModel();\n this.layers = new Map();\n this.relationships = new Relationships();\n this.lazyLoad = options.lazyLoad ?? false;\n this.loadedLayers = new Set();\n }\n\n /**\n * Get the committed base layer (no staging projection).\n * Used internally by VirtualProjectionEngine and commit() to access the raw base model\n * without triggering infinite recursion or double-projection.\n */\n async getBaseLayer(name: string): Promise<Layer | undefined> {\n if (this.lazyLoad && !this.loadedLayers.has(name)) {\n await this.loadLayer(name);\n }\n return this.layers.get(name);\n }\n\n /**\n * Get a layer, transparently merging staged changes when an active changeset exists.\n * Falls back to the committed base layer if no changeset is active or projection fails.\n */\n async getLayer(name: string): Promise<Layer | undefined> {\n if (this.lazyLoad && !this.loadedLayers.has(name)) {\n await this.loadLayer(name);\n }\n\n // If an active changeset is present, return a projected (base + staged) layer\n if (this._activeChangesetId) {\n try {\n return await this.getVirtualProjectionEngine().projectLayer(\n this,\n this._activeChangesetId,\n name\n );\n } catch {\n // Fall back to base layer on projection failure (e.g. corrupt changeset)\n return this.layers.get(name);\n }\n }\n\n return this.layers.get(name);\n }\n\n /**\n * Get an element by ID across all layers\n * Supports both UUID and semantic ID (layer.type.kebab-name) lookup\n */\n getElementById(id: string): Element | undefined {\n for (const layer of this.layers.values()) {\n const element = layer.getElement(id);\n if (element) {\n return element;\n }\n }\n return undefined;\n }\n\n /**\n * Get virtual projection engine (lazily initialized)\n */\n getVirtualProjectionEngine(): VirtualProjectionEngine {\n if (!this.virtualProjectionEngine) {\n this.virtualProjectionEngine = new VirtualProjectionEngine(this.rootPath);\n }\n return this.virtualProjectionEngine;\n }\n\n /**\n * Returns the active changeset ID, or null if no changeset is active.\n * Used by commands that need to project the full model (e.g. validate).\n */\n getActiveChangesetId(): string | null {\n return this._activeChangesetId;\n }\n\n /**\n * Load a layer from disk (model/XX_layername/*.yaml files)\n */\n async loadLayer(name: string): Promise<void> {\n await startActiveSpan(\"layer.load\", async (layerSpan) => {\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n\n let layerPath: string | null = null;\n\n // Auto-discover by scanning directory with naming convention\n const modelDir = `${this.rootPath}/documentation-robotics/model`;\n\n try {\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n const layerDir = entries.find(\n (e) =>\n e.isDirectory() && e.name.match(/^\\d{2}_/) && e.name.replace(/^\\d{2}_/, \"\") === name\n );\n\n if (layerDir) {\n layerPath = `${modelDir}/${layerDir.name}`;\n }\n } catch {\n // Model directory doesn't exist or can't be read\n }\n\n if (!layerPath) {\n return; // Layer directory not found\n }\n\n const layer = new Layer(name, this.graph);\n let parseErrorCount = 0;\n\n // Load all YAML files in the layer directory\n const files = await fs.readdir(layerPath);\n for (const file of files) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n const filePath = `${layerPath}/${file}`;\n try {\n const yamlContent = await fs.readFile(filePath, \"utf-8\");\n const elements = yaml.parse(yamlContent);\n\n // Add each element from the YAML file\n if (elements && typeof elements === \"object\") {\n for (const [key, element] of Object.entries(elements)) {\n if (element && typeof element === \"object\") {\n const el: any = element;\n\n // Derive id, path, and type using the 3-case migration logic:\n // Case 1: element already has both id (UUID) and path \u2192 use as-is\n // Case 2: element.id looks like a slug (layer.type.name) \u2192 move to path, generate UUID\n // Case 3: element has UUID id but no path \u2192 derive path from layer_id.type.kebab(name)\n const isSlugPattern = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/.test(el.id || \"\");\n const isUUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(el.id || \"\");\n\n let elementId: string;\n let elementPath: string;\n let extractedType: string | undefined = undefined;\n\n if (el.path && isUUIDPattern) {\n // Case 1: already has both path and UUID id\n elementId = el.id;\n elementPath = el.path;\n const pathParts = el.path.split(\".\");\n if (pathParts.length >= 3) extractedType = pathParts[1];\n } else if (el.path && !isUUIDPattern) {\n // Has a path but id is not a UUID \u2014 migrate id to deterministic UUID\n elementPath = el.path;\n elementId = deterministicUUID(el.path);\n const pathParts = el.path.split(\".\");\n if (pathParts.length >= 3) extractedType = pathParts[1];\n } else if (isSlugPattern) {\n // Case 2: slug-looking id \u2192 move to path, generate deterministic UUID\n elementPath = el.id;\n elementId = deterministicUUID(el.id);\n const parts = el.id.split(\".\");\n if (parts.length >= 3) extractedType = parts[1];\n } else if (isUUIDPattern) {\n // Case 3: UUID id, no path \u2192 derive path\n elementId = el.id;\n const layerId = el.layer_id || name;\n const rawType = el.type || \"\";\n const rawName = el.name || key;\n const kebabName = rawName.toLowerCase().replace(/[\\s_]+/g, \"-\").replace(/[^a-z0-9-]/g, \"\").replace(/-+/g, \"-\").replace(/^-+|-+$/g, \"\");\n elementPath = `${layerId}.${rawType}.${kebabName}`;\n extractedType = rawType || undefined;\n } else {\n // Fallback: derive a path from available data, generate deterministic UUID\n const layerId = el.layer_id || name;\n const rawType = el.type || (typeof el.id === \"string\" && el.id.includes(\".\") ? el.id.split(\".\")[1] : \"\") || \"\";\n const rawName = el.name || key;\n const kebabName = rawName.toLowerCase().replace(/[\\s_]+/g, \"-\").replace(/[^a-z0-9-]/g, \"\").replace(/-+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"unknown\";\n elementPath = `${layerId}.${rawType || \"element\"}.${kebabName}`;\n elementId = deterministicUUID(elementPath);\n if (rawType) extractedType = rawType;\n else if (typeof el.id === \"string\" && el.id.includes(\".\")) {\n const parts = el.id.split(\".\");\n if (parts.length >= 3) extractedType = parts[1];\n }\n }\n\n const elementName = el.name || key;\n const elementType = el.type || extractedType;\n const layerId = el.layer_id || name;\n\n const newElement = new Element({\n id: elementId,\n path: elementPath,\n spec_node_id: el.spec_node_id,\n layer_id: layerId,\n attributes: el.attributes,\n source_reference: el.source_reference,\n metadata: el.metadata,\n name: elementName,\n type: elementType,\n description: el.description || \"\",\n relationships: el.relationships || [],\n references: el.references || [],\n layer: layerId,\n } as any);\n layer.addElement(newElement);\n }\n }\n }\n } catch (fileError) {\n // Log YAML parsing errors but continue loading other files\n const errorMsg = fileError instanceof Error ? fileError.message : String(fileError);\n console.error(`Warning: Failed to parse ${filePath}: ${errorMsg}`);\n parseErrorCount++;\n // Continue with next file\n }\n }\n }\n\n // If we had parse errors, add a note to the layer\n if (parseErrorCount > 0) {\n console.error(\n `Warning: Layer '${name}' loaded with ${parseErrorCount} YAML parse error(s)`\n );\n }\n\n this.layers.set(name, layer);\n this.loadedLayers.add(name);\n\n layerSpan.setAttribute(\"layer.element_count\", layer.elements.size);\n }, { \"layer.name\": name });\n }\n\n /**\n * Add a layer to the model\n */\n addLayer(layer: Layer): void {\n // If the layer has elements in its isolated graph, migrate them to the model's graph\n if (layer.graph.getNodeCount() > 0) {\n for (const node of layer.graph.nodes.values()) {\n this.graph.addNode(node);\n }\n for (const edge of layer.graph.edges.values()) {\n this.graph.addEdge(edge);\n }\n }\n // Ensure layer uses the model's graph for future operations\n layer.graph = this.graph;\n this.layers.set(layer.name, layer);\n this.loadedLayers.add(layer.name);\n }\n\n /**\n * Get all layer names, including layers that exist only in staged changes.\n * When an active changeset introduces elements for a layer with no committed data,\n * that layer name is included so findElementLayer() and other callers can discover it.\n */\n getLayerNames(): string[] {\n const base = Array.from(this.layers.keys());\n if (this._stagedLayerNames.size === 0) {\n return base;\n }\n // Include staged-only layers (layers that have staged elements but no committed elements)\n const staged = Array.from(this._stagedLayerNames).filter((l) => !this.layers.has(l));\n return [...base, ...staged];\n }\n\n /**\n * Save a layer to disk (model/XX_layername/*.yaml)\n */\n async saveLayer(name: string): Promise<void> {\n const layer = this.layers.get(name);\n if (!layer) {\n throw new Error(`Layer ${name} not found`);\n }\n\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n\n let layerPath: string | null = null;\n\n // Discover by scanning directory\n const modelDir = `${this.rootPath}/documentation-robotics/model`;\n\n try {\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n const layerDir = entries.find(\n (e) =>\n e.isDirectory() && e.name.match(/^\\d{2}_/) && e.name.replace(/^\\d{2}_/, \"\") === name\n );\n\n if (layerDir) {\n layerPath = `${modelDir}/${layerDir.name}`;\n }\n } catch {\n // Model directory doesn't exist or can't be read\n }\n\n // If still not found, create using default format\n if (!layerPath) {\n // Find the order number for this layer\n // Handle layer names with or without numeric prefix (e.g., \"motivation\" or \"01-motivation\")\n const layerNameWithoutPrefix = name.replace(/^\\d{2}-/, \"\");\n const index = CANONICAL_LAYER_NAMES.indexOf(layerNameWithoutPrefix as typeof CANONICAL_LAYER_NAMES[number]);\n if (index >= 0) {\n const orderNum = String(index + 1).padStart(2, \"0\");\n layerPath = `${this.rootPath}/documentation-robotics/model/${orderNum}_${layerNameWithoutPrefix}`;\n await ensureDir(layerPath);\n } else {\n throw new Error(`Unknown layer ${name} and no path configured in manifest`);\n }\n }\n\n // Delete all existing YAML files in the layer directory to ensure\n // we don't keep stale files after element deletion\n try {\n const existingFiles = await fs.readdir(layerPath);\n for (const file of existingFiles) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n await fs.unlink(`${layerPath}/${file}`);\n }\n }\n } catch (err) {\n // Only ignore \"directory not found\" errors \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw new Error(\n `Failed to clean existing YAML files: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n // Group elements by type, computing the snake_case file stem from the spec node title\n const elementsByType = new Map<string, { stem: string; elements: Element[] }>();\n for (const element of layer.elements.values()) {\n const typeKey = element.type;\n if (!elementsByType.has(typeKey)) {\n const stem = typeToSnakeStem(element.spec_node_id, element.type);\n elementsByType.set(typeKey, { stem, elements: [] });\n }\n elementsByType.get(typeKey)!.elements.push(element);\n }\n\n // Write each type to its own YAML file\n for (const [, { stem, elements }] of elementsByType) {\n const filename = `${stem}.yaml`;\n const filePath = `${layerPath}/${filename}`;\n\n // Convert elements to spec-node format YAML\n const yamlData: any = {};\n for (const element of elements) {\n const json = element.toJSON();\n\n // Clean attributes: remove graph-internal keys that should not be persisted \u2014 see\n // isPersistableAttribute's own doc for why this checks the schema rather than a\n // hardcoded name list.\n const cleanAttrs = json.attributes\n ? Object.fromEntries(\n Object.entries(json.attributes).filter(([k]) =>\n isPersistableAttribute(json.spec_node_id, k)\n )\n )\n : undefined;\n\n // Use path as YAML key (human-readable); fall back to id for elements without path\n const key = element.path || element.id;\n yamlData[key] = {\n id: json.id,\n path: json.path,\n spec_node_id: json.spec_node_id,\n type: json.type,\n layer_id: json.layer_id,\n name: json.name,\n ...(json.description && { description: json.description }),\n ...(cleanAttrs && Object.keys(cleanAttrs).length > 0 && { attributes: cleanAttrs }),\n ...(json.source_reference && { source_reference: json.source_reference }),\n ...(json.metadata && { metadata: json.metadata }),\n ...(json.references && json.references.length > 0 && { references: json.references }),\n // Relationships are now stored in centralized relationships.yaml, not inline\n };\n }\n\n await writeFile(filePath, yaml.stringify(yamlData));\n }\n\n layer.markClean();\n }\n\n /**\n * Save model (saves all dirty layers and manifest)\n */\n async save(): Promise<void> {\n await this.saveDirtyLayers();\n await this.saveManifest();\n }\n\n /**\n * Save all dirty layers to disk\n */\n async saveDirtyLayers(): Promise<void> {\n // Count dirty layers before operation\n let dirtyLayerCount = 0;\n for (const layer of this.layers.values()) {\n if (layer.isDirty()) {\n dirtyLayerCount++;\n }\n }\n\n const span = isTelemetryEnabled\n ? startSpan(\"model.save\", {\n \"model.path\": this.rootPath,\n \"model.dirty_layer_count\": dirtyLayerCount,\n })\n : null;\n\n try {\n for (const layer of this.layers.values()) {\n if (layer.isDirty()) {\n await this.saveLayer(layer.name);\n }\n }\n } finally {\n if (isTelemetryEnabled) {\n endSpan(span);\n }\n }\n }\n\n /**\n * Save the manifest to disk (documentation-robotics/model/manifest.yaml)\n */\n async saveManifest(): Promise<void> {\n this.manifest.updateModified();\n const yaml = await import(\"yaml\");\n const manifestPath = `${this.rootPath}/documentation-robotics/model/manifest.yaml`;\n\n // Convert to YAML format\n const yamlData: any = {\n version: this.manifest.version,\n schema: \"documentation-robotics-v1\",\n cli_version: getCliVersion(),\n spec_version: this.manifest.specVersion,\n created: this.manifest.created,\n updated: this.manifest.modified,\n project: {\n name: this.manifest.name,\n description: this.manifest.description,\n version: this.manifest.version,\n },\n documentation: \".dr/README.md\",\n layers: {},\n };\n\n // Generate layer structure from current model state\n for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {\n const layerName = CANONICAL_LAYER_NAMES[i];\n const layer = this.layers.get(layerName);\n const orderNum = String(i + 1).padStart(2, \"0\");\n\n yamlData.layers[layerName] = {\n order: i + 1,\n name: layerName.charAt(0).toUpperCase() + layerName.slice(1).replace(\"-\", \" \"),\n path: `documentation-robotics/model/${orderNum}_${layerName}/`,\n enabled: true,\n ...(layer && { elements: this.getLayerElementCounts(layer) }),\n };\n }\n\n if (this.manifest.changeset_history && this.manifest.changeset_history.length > 0) {\n yamlData.changeset_history = this.manifest.changeset_history;\n }\n\n await ensureDir(`${this.rootPath}/documentation-robotics/model`);\n await writeFile(manifestPath, yaml.stringify(yamlData));\n }\n\n private getLayerElementCounts(layer: Layer): Record<string, number> {\n const counts: Record<string, number> = {};\n for (const element of layer.elements.values()) {\n counts[element.type] = (counts[element.type] || 0) + 1;\n }\n return counts;\n }\n\n /**\n * Load relationships from relationships.yaml\n */\n async loadRelationships(): Promise<void> {\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n const relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;\n\n let data: any;\n try {\n const yamlContent = await fs.readFile(relationshipsPath, \"utf-8\");\n data = yaml.parse(yamlContent);\n } catch (err) {\n // If file doesn't exist, that's okay - start with empty relationships\n if ((err as any).code !== \"ENOENT\") {\n // But if it's a different error (YAML parse, permission denied, etc), throw it\n // so the caller knows about the issue\n throw new Error(\n `Failed to load relationships.yaml: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n // ENOENT: file doesn't exist, return with empty relationships\n return;\n }\n\n // Validate that parsed content is an array\n if (!Array.isArray(data)) {\n const typeLabel = data === null ? \"null\" : typeof data;\n console.warn(\n `Warning: relationships.yaml contains non-array content (received ${typeLabel}). Expected an array of relationships. Skipping relationship loading.`\n );\n return;\n }\n\n this.relationships = Relationships.fromArray(data);\n // Sync relationships to graph as a secondary in-memory index.\n // addEdge() may throw if nodes from other layers haven't been loaded yet;\n // failures are silently skipped \u2014 this.relationships holds the primary data.\n for (let i = 0; i < data.length; i++) {\n const rel = data[i];\n const edge: GraphEdge = {\n id: `rel-${i}-${crypto.randomBytes(4).toString(\"hex\")}`,\n source: rel.source,\n destination: rel.target,\n predicate: rel.predicate,\n properties: rel.properties,\n category: rel.category,\n };\n try {\n this.graph.addEdge(edge);\n } catch {\n // Silently skip edges whose nodes haven't been loaded yet.\n // The primary relationship data lives in this.relationships; the graph\n // is a secondary index. Cross-layer relationships referencing nodes from\n // unloaded layers are expected during partial model construction.\n }\n }\n }\n\n /**\n * Save relationships to relationships.yaml\n */\n async saveRelationships(): Promise<void> {\n const fs = await import(\"fs/promises\");\n const yaml = await import(\"yaml\");\n const relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;\n\n await ensureDir(`${this.rootPath}/documentation-robotics/model`);\n\n const data = this.relationships.toArray();\n\n // Add header comment with timestamp to ensure file always changes\n const timestamp = new Date().toISOString();\n const header = `# Intra-layer relationships\n# Format: source_id -> predicate -> target_id\n# Last updated: ${timestamp}\n\n`;\n const yamlContent = header + yaml.stringify(data);\n\n await fs.writeFile(relationshipsPath, yamlContent, \"utf-8\");\n this.relationships.markClean();\n }\n\n /**\n * Resolve model paths (private helper)\n *\n * @param startPath - Starting path for search\n * @returns Project root and manifest path\n */\n private static async resolveModelPaths(startPath?: string): Promise<{\n projectRoot: string;\n manifestPath: string;\n }> {\n const searchPath = startPath || process.cwd();\n\n // Optional override via env var (highest priority)\n if (process.env.DR_MODEL_PATH) {\n const envPath = path.resolve(process.env.DR_MODEL_PATH);\n let manifestPath: string;\n\n if (envPath.endsWith(\"manifest.yaml\")) {\n manifestPath = envPath;\n } else if (envPath.endsWith(\"model\")) {\n manifestPath = path.join(envPath, \"manifest.yaml\");\n } else {\n manifestPath = path.join(envPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n }\n\n try {\n await fs.access(manifestPath);\n const projectRoot = path.dirname(path.dirname(path.dirname(manifestPath)));\n return { projectRoot, manifestPath: path.normalize(manifestPath) };\n } catch {\n throw new Error(`Model not found at DR_MODEL_PATH: ${process.env.DR_MODEL_PATH}`);\n }\n }\n\n // Use findProjectRoot to locate documentation_robotics/ folder\n const projectRoot = await findProjectRoot(searchPath);\n\n if (!projectRoot) {\n throw new Error(\n \"No DR project found. Could not find documentation_robotics/ folder.\\n\" +\n 'Run \"dr init\" to create a new project, or navigate to a directory containing a DR project.'\n );\n }\n\n const manifestPath = path.join(projectRoot, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n\n try {\n await fs.access(manifestPath);\n return { projectRoot, manifestPath };\n } catch {\n throw new Error(\n `Found documentation_robotics/ at ${projectRoot} but no model found. ` +\n `Expected: ${manifestPath}\\n` +\n `Run \"dr init\" to create a model.`\n );\n }\n }\n\n /**\n * Load a model from disk\n *\n * Expected structure:\n * <project-root>/\n * \u2514\u2500\u2500 documentation_robotics/\n * \u2514\u2500\u2500 model/\n * \u2514\u2500\u2500 manifest.yaml\n *\n * @param startPath - Starting path for project search (defaults to process.cwd())\n * @param options - Model loading options\n * @returns Loaded Model instance\n */\n static async load(startPath?: string, options: ModelOptions = {}): Promise<Model> {\n return startActiveSpan(\"model.load\", async (span) => {\n // Resolve project root and manifest path\n const { projectRoot, manifestPath } = await Model.resolveModelPaths(startPath);\n\n const yaml = await import(\"yaml\");\n\n // Load manifest from resolved path\n const yamlContent = await fs.readFile(manifestPath, \"utf-8\");\n const manifestYaml = yaml.parse(yamlContent);\n\n // Convert manifest to internal format\n const manifestData: any = {\n name: manifestYaml.project?.name || \"Unnamed Model\",\n description: manifestYaml.project?.description || \"\",\n version: manifestYaml.project?.version || manifestYaml.version || \"0.1.0\",\n specVersion: manifestYaml.spec_version || \"0.6.0\",\n created: manifestYaml.created || new Date().toISOString(),\n modified: manifestYaml.updated || new Date().toISOString(),\n };\n const manifest = new Manifest(manifestData);\n const model = new Model(projectRoot, manifest, options);\n\n // Load all available layers if lazyLoad is false\n if (!options.lazyLoad) {\n try {\n // If manifest has layer definitions, use those\n if (manifestYaml.layers) {\n for (const layerName of Object.keys(manifestYaml.layers)) {\n const layerConfig = manifestYaml.layers[layerName];\n if (layerConfig.enabled !== false) {\n if (!options.layers || options.layers.includes(layerName)) {\n await model.loadLayer(layerName);\n }\n }\n }\n } else {\n // Otherwise scan for layer directories\n const modelDir = manifestPath.replace(\"/manifest.yaml\", \"\");\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name.match(/^\\d{2}_/)) {\n const layerName = entry.name.replace(/^\\d{2}_/, \"\");\n if (!options.layers || options.layers.includes(layerName)) {\n await model.loadLayer(layerName);\n }\n }\n }\n }\n } catch (e) {\n // Check if this is a \"directory not found\" error (expected case)\n const isNotFoundError = (e as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n // Re-throw non-ENOENT errors: permission errors, YAML parsing failures, I/O errors, etc.\n throw new Error(\n `Failed to load layers: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n // ENOENT is expected if layer directory doesn't exist yet, so just continue\n }\n }\n\n // Load relationships from relationships.yaml\n await model.loadRelationships();\n\n // Check for active changeset and pre-load staged metadata for transparent projection.\n // This enables getLayer() to return projected views and getLayerNames() to include\n // layers that exist only in staged changes.\n const activePath = path.join(\n projectRoot,\n \"documentation-robotics\",\n \"changesets\",\n \".active\"\n );\n try {\n const activeContent = await fs.readFile(activePath, \"utf-8\");\n const activeId = activeContent.trim();\n if (activeId) {\n model._activeChangesetId = activeId;\n const storage = new StagedChangesetStorage(projectRoot);\n const changeset = await storage.load(activeId);\n if (changeset) {\n for (const change of changeset.changes as StagedChange[]) {\n // Track layer names for getLayerNames()\n if (\n change.type === \"add\" ||\n change.type === \"update\" ||\n change.type === \"delete\"\n ) {\n model._stagedLayerNames.add(change.layerName);\n }\n\n // Inject staged relationship-adds into model.relationships so that\n // read commands (list, show, export) transparently see staged relationships.\n if (change.type === \"relationship-add\" && change.after) {\n const rel = change.after as {\n source: string;\n target: string;\n predicate: string;\n layer: string;\n targetLayer?: string;\n category?: \"structural\" | \"behavioral\";\n properties?: Record<string, unknown>;\n };\n // Only inject if not already in relationships.yaml (prevents duplicates\n // if .active file is stale after a partial commit or manual edit).\n const alreadyPresent = model.relationships.find(\n rel.source,\n rel.target,\n rel.predicate\n );\n if (alreadyPresent.length === 0) {\n model.relationships.add({\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n category: rel.category ?? \"structural\",\n ...(rel.properties ? { properties: rel.properties } : {}),\n });\n }\n }\n\n // Remove staged relationship-deletes from model.relationships so that\n // read commands see the relationship as already deleted.\n if (change.type === \"relationship-delete\" && change.before) {\n const rel = change.before as {\n source: string;\n target: string;\n predicate?: string;\n };\n model.relationships.delete(rel.source, rel.target, rel.predicate);\n }\n }\n }\n }\n } catch {\n // No active changeset or unreadable \u2014 proceed with base model only\n model._activeChangesetId = null;\n }\n\n // Count total entities across all layers\n let entityCount = 0;\n for (const layer of model.layers.values()) {\n entityCount += layer.elements.size;\n }\n\n span.setAttribute(\"model.entity_count\", entityCount);\n span.setAttribute(\"model.layer_count\", model.layers.size);\n\n return model;\n }, {\n \"model.path\": startPath || process.cwd(),\n \"model.type\": \"dr\",\n });\n }\n\n /**\n * Initialize a new model in a directory\n */\n static async init(\n rootPath: string,\n manifestData: ManifestData,\n options: ModelOptions = {}\n ): Promise<Model> {\n // Create model directory structure\n await ensureDir(`${rootPath}/documentation-robotics/model`);\n\n for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {\n const orderNum = String(i + 1).padStart(2, \"0\");\n const layerName = CANONICAL_LAYER_NAMES[i];\n await ensureDir(`${rootPath}/documentation-robotics/model/${orderNum}_${layerName}`);\n }\n\n const manifest = new Manifest(manifestData);\n const model = new Model(rootPath, manifest, options);\n\n await model.saveManifest();\n\n return model;\n }\n\n /**\n * Graph-based API methods for querying the model\n * These methods delegate to the underlying GraphModel for efficient queries\n */\n\n /**\n * Get a node by its UUID\n */\n getNode(id: string) {\n return this.graph.getNode(id);\n }\n\n /**\n * Get all nodes in a specific layer\n */\n getNodesByLayer(layerId: string) {\n return this.graph.getNodesByLayer(layerId);\n }\n\n /**\n * Get all nodes of a specific type\n */\n getNodesByType(type: string) {\n return this.graph.getNodesByType(type);\n }\n\n /**\n * Get all edges (relationships) in the model\n */\n getAllEdges() {\n return this.graph.getAllEdges();\n }\n\n /**\n * Get edges originating from a specific node\n */\n getEdgesFrom(nodeId: string, predicate?: string) {\n return this.graph.getEdgesFrom(nodeId, predicate);\n }\n\n /**\n * Get edges terminating at a specific node\n */\n getEdgesTo(nodeId: string, predicate?: string) {\n return this.graph.getEdgesTo(nodeId, predicate);\n }\n\n /**\n * Get edges between two specific nodes\n */\n getEdgesBetween(sourceId: string, destinationId: string, predicate?: string) {\n return this.graph.getEdgesBetween(sourceId, destinationId, predicate);\n }\n\n /**\n * Traverse the graph starting from a node, following a specific predicate\n */\n async traverse(startNodeId: string, predicate: string, maxDepth: number = 10) {\n return this.graph.traverse(startNodeId, predicate, maxDepth);\n }\n\n /**\n * Create a backup of the current model state\n * Used for rollback support in migration operations\n *\n * @param label Descriptive label for the backup (e.g., \"pre-migration\")\n * @returns Path to the backup directory\n */\n async createBackup(label: string): Promise<string> {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupDir = path.join(this.rootPath, \".dr-backups\", `${label}-${timestamp}`);\n\n // Create backup directory\n await ensureDir(backupDir);\n\n // Backup layers directory\n const layersSource = path.join(this.rootPath, \"documentation-robotics\", \"model\");\n const layersBackup = path.join(backupDir, \"layers\");\n if (await this.directoryExists(layersSource)) {\n await this.copyDirectory(layersSource, layersBackup);\n }\n\n // Backup manifest\n const manifestSource = path.join(this.rootPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n const manifestBackup = path.join(backupDir, \"manifest.yaml\");\n if (await this.fileExists(manifestSource)) {\n await fs.copyFile(manifestSource, manifestBackup);\n }\n\n return backupDir;\n }\n\n /**\n * Restore model from a backup directory\n * Used for rollback after failed migrations\n *\n * @param backupPath Path to the backup directory\n */\n async restoreFromBackup(backupPath: string): Promise<void> {\n // Restore layers\n const layersBackup = path.join(backupPath, \"layers\");\n const layersTarget = path.join(this.rootPath, \"documentation-robotics\", \"model\");\n if (await this.directoryExists(layersBackup)) {\n await this.copyDirectory(layersBackup, layersTarget);\n }\n\n // Restore manifest\n const manifestBackup = path.join(backupPath, \"manifest.yaml\");\n const manifestTarget = path.join(this.rootPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n if (await this.fileExists(manifestBackup)) {\n await fs.copyFile(manifestBackup, manifestTarget);\n }\n\n // Reload model to refresh in-memory state\n const reloadedModel = await Model.load(this.rootPath, { lazyLoad: this.lazyLoad });\n this.layers = reloadedModel.layers;\n this.manifest = reloadedModel.manifest;\n this.graph = reloadedModel.graph;\n this.relationships = reloadedModel.relationships;\n }\n\n /**\n * Save dirty layers with atomic write semantics\n * Writes to temporary file first, then renames on success to ensure data safety\n *\n * @throws Error if atomic write fails\n */\n async saveDirtyLayersAtomic(): Promise<void> {\n // First, save all layers to temporary files\n const tempLayerWrites: Array<{ layer: Layer; tempPath: string }> = [];\n\n try {\n for (const layer of this.layers.values()) {\n if (layer.isDirty()) {\n const layerPath = path.join(\n this.rootPath,\n \"documentation-robotics\",\n \"model\",\n `${layer.name}_layer`\n );\n\n const tempPath = `${layerPath}.tmp`;\n await ensureDir(tempPath);\n\n // Write layer elements to temp directory\n for (const element of layer.elements.values()) {\n const elementPath = path.join(tempPath, `${element.path || element.id}.json`);\n const content = JSON.stringify(element.toJSON(), null, 2);\n await writeFile(elementPath, content);\n }\n\n tempLayerWrites.push({ layer, tempPath });\n }\n }\n\n // All temp writes succeeded, now commit by renaming\n for (const { layer, tempPath } of tempLayerWrites) {\n const layerPath = path.join(\n this.rootPath,\n \"documentation-robotics\",\n \"model\",\n `${layer.name}_layer`\n );\n\n // Remove old directory and rename temp to actual\n if (await this.directoryExists(layerPath)) {\n await fs.rm(layerPath, { recursive: true, force: true });\n }\n await fs.rename(tempPath, layerPath);\n }\n } catch (error) {\n // Cleanup any temp directories on failure\n for (const { tempPath } of tempLayerWrites) {\n if (await this.directoryExists(tempPath)) {\n await fs.rm(tempPath, { recursive: true, force: true });\n }\n }\n throw error;\n }\n }\n\n /**\n * Helper: Check if file exists\n */\n private async fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Helper: Check if directory exists\n */\n private async directoryExists(dirPath: string): Promise<boolean> {\n try {\n const stat = await fs.stat(dirPath);\n return stat.isDirectory();\n } catch {\n return false;\n }\n }\n\n /**\n * Helper: Recursively copy directory\n */\n private async copyDirectory(src: string, dest: string): Promise<void> {\n await ensureDir(dest);\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await this.copyDirectory(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["path", "fs", "layerId", "manifestPath"]
7
7
  }
@@ -1,9 +1,15 @@
1
1
  import Graph from "graphology";
2
+ import { looksLikeElementReference } from "../utils/element-reference-shape.js";
2
3
  class ReferenceRegistry {
3
4
  references;
4
5
  targetIndex;
5
6
  typeIndex;
6
- // Known reference property names from Python CLI
7
+ // Known reference property names from Python CLI. NOTE: matching one of these names (or the
8
+ // *Ref/*Reference naming convention scanNestedReferences uses) is necessary but not
9
+ // sufficient — the attribute's VALUE also has to look like an element id
10
+ // (looksLikeElementReference) before we register it as a reference. Name alone isn't
11
+ // reliable: e.g. api.link.operationRef is a schema-declared OpenAPI URL fragment, not a DR
12
+ // element reference, and matches "*Ref" purely by naming coincidence.
7
13
  static KNOWN_REF_PROPERTIES = /* @__PURE__ */ new Set([
8
14
  "realizes",
9
15
  "realizedBy",
@@ -98,14 +104,14 @@ class ReferenceRegistry {
98
104
  for (const [propName, propValue] of Object.entries(element.attributes))
99
105
  if (ReferenceRegistry.KNOWN_REF_PROPERTIES.has(propName)) {
100
106
  if (typeof propValue == "string")
101
- this.addReference({
107
+ looksLikeElementReference(propValue) && this.addReference({
102
108
  source: elementId,
103
109
  target: propValue,
104
110
  type: propName
105
111
  });
106
112
  else if (Array.isArray(propValue))
107
113
  for (const target of propValue)
108
- typeof target == "string" && this.addReference({
114
+ typeof target == "string" && looksLikeElementReference(target) && this.addReference({
109
115
  source: elementId,
110
116
  target,
111
117
  type: propName
@@ -137,7 +143,7 @@ class ReferenceRegistry {
137
143
  if (!(!path && ReferenceRegistry.KNOWN_REF_PROPERTIES.has(key)))
138
144
  if (key.endsWith("Ref") || key.endsWith("Reference")) {
139
145
  if (typeof value == "string")
140
- references.push({
146
+ looksLikeElementReference(value) && references.push({
141
147
  source: sourceId,
142
148
  target: value,
143
149
  type: key,
@@ -145,7 +151,7 @@ class ReferenceRegistry {
145
151
  });
146
152
  else if (Array.isArray(value))
147
153
  for (const target of value)
148
- typeof target == "string" && references.push({
154
+ typeof target == "string" && looksLikeElementReference(target) && references.push({
149
155
  source: sourceId,
150
156
  target,
151
157
  type: key,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/reference-registry.ts"],
4
- "sourcesContent": ["import Graph from \"graphology\";\nimport type { Reference, Element } from \"../types/index.js\";\n\n\n/**\n * Reference registry - tracks and validates cross-layer references\n *\n * NOTE: Phase 3 Deferred - Registry Consolidation\n * This registry will be consolidated into the GraphModel query API in a future phase.\n * The graph model provides the foundation with graph-based reference tracking.\n * Current approach maintains backward compatibility while the transition occurs.\n *\n * Future: Use GraphModel.getEdgesFrom/To/Between with predicate filtering\n * instead of maintaining separate reference registry.\n *\n * See: https://github.com/tinkermonkey/documentation_robotics/discussions/317\n */\nexport class ReferenceRegistry {\n private references: Map<string, Reference[]>;\n private targetIndex: Map<string, Reference[]>;\n private typeIndex: Map<string, Reference[]>;\n\n // Known reference property names from Python CLI\n private static readonly KNOWN_REF_PROPERTIES = new Set([\n \"realizes\",\n \"realizedBy\",\n \"serves\",\n \"servedBy\",\n \"accesses\",\n \"accessedBy\",\n \"uses\",\n \"usedBy\",\n \"composedOf\",\n \"partOf\",\n \"flows\",\n \"triggers\",\n \"archimateRef\",\n \"businessActorRef\",\n \"stakeholderRef\",\n \"motivationGoalRef\",\n \"dataObjectRef\",\n \"apiOperationRef\",\n \"applicationServiceRef\",\n \"schemaRef\",\n ]);\n\n constructor() {\n this.references = new Map();\n this.targetIndex = new Map();\n this.typeIndex = new Map();\n }\n\n /**\n * Add a reference to the registry\n */\n addReference(reference: Reference): void {\n // Add to source index\n const sourceKey = reference.source;\n if (!this.references.has(sourceKey)) {\n this.references.set(sourceKey, []);\n }\n this.references.get(sourceKey)!.push(reference);\n\n // Add to target index\n const targetKey = reference.target;\n if (!this.targetIndex.has(targetKey)) {\n this.targetIndex.set(targetKey, []);\n }\n this.targetIndex.get(targetKey)!.push(reference);\n\n // Add to type index\n const typeKey = reference.type;\n if (!this.typeIndex.has(typeKey)) {\n this.typeIndex.set(typeKey, []);\n }\n this.typeIndex.get(typeKey)!.push(reference);\n }\n\n /**\n * Get all references from a source element\n */\n getReferencesFrom(sourceId: string): Reference[] {\n return this.references.get(sourceId) ?? [];\n }\n\n /**\n * Get all references to a target element\n */\n getReferencesTo(targetId: string): Reference[] {\n return this.targetIndex.get(targetId) ?? [];\n }\n\n /**\n * Get all references of a specific type\n */\n getReferencesByType(type: string): Reference[] {\n return this.typeIndex.get(type) ?? [];\n }\n\n /**\n * Check if a reference exists\n */\n hasReference(sourceId: string, targetId: string): boolean {\n const refs = this.references.get(sourceId) ?? [];\n return refs.some((ref) => ref.target === targetId);\n }\n\n /**\n * Get all references\n */\n getAllReferences(): Reference[] {\n const allRefs: Reference[] = [];\n for (const refs of this.references.values()) {\n allRefs.push(...refs);\n }\n return allRefs;\n }\n\n /**\n * Clear all references\n */\n clear(): void {\n this.references.clear();\n this.targetIndex.clear();\n this.typeIndex.clear();\n }\n\n /**\n * Register an element and all its references\n * Scans element properties for known reference names\n */\n registerElement(element: Element): void {\n // Validate required element properties\n if (!element) {\n return;\n }\n\n // Use element's path (slug) as the registry key; fall back to id for legacy elements\n const elementId = element.path || element.id;\n\n // Skip if element has no ID\n if (!elementId) {\n return;\n }\n\n // Extract references from explicit references array\n if (element.references) {\n for (const ref of element.references) {\n // Rewrite source to use consistent ID\n this.addReference({\n ...ref,\n source: elementId,\n });\n }\n }\n\n // Scan attributes for known reference properties\n if (element.attributes) {\n for (const [propName, propValue] of Object.entries(element.attributes)) {\n if (ReferenceRegistry.KNOWN_REF_PROPERTIES.has(propName)) {\n // Handle string value\n if (typeof propValue === \"string\") {\n this.addReference({\n source: elementId,\n target: propValue,\n type: propName,\n });\n }\n // Handle array of strings\n else if (Array.isArray(propValue)) {\n for (const target of propValue) {\n if (typeof target === \"string\") {\n this.addReference({\n source: elementId,\n target: target,\n type: propName,\n });\n }\n }\n }\n }\n }\n\n // Scan nested attributes for *Ref/*Reference properties\n const nestedRefs = this.scanNestedReferences(elementId, element.attributes);\n for (const ref of nestedRefs) {\n this.addReference(ref);\n }\n }\n }\n\n /**\n * Recursively scan nested structures for reference properties\n * Matches Python CLI behavior: looks for properties ending in \"Ref\" or \"Reference\"\n */\n private scanNestedReferences(sourceId: string, data: any, path: string = \"\"): Reference[] {\n const references: Reference[] = [];\n\n if (!data || typeof data !== \"object\") {\n return references;\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n for (let i = 0; i < data.length; i++) {\n const itemPath = path ? `${path}[${i}]` : `[${i}]`;\n references.push(...this.scanNestedReferences(sourceId, data[i], itemPath));\n }\n return references;\n }\n\n // Scan object properties\n for (const [key, value] of Object.entries(data)) {\n const currentPath = path ? `${path}.${key}` : key;\n\n // Skip if this is a top-level known reference property (already handled)\n if (!path && ReferenceRegistry.KNOWN_REF_PROPERTIES.has(key)) {\n continue;\n }\n\n // Check if property name ends with \"Ref\" or \"Reference\"\n if (key.endsWith(\"Ref\") || key.endsWith(\"Reference\")) {\n // Handle string value\n if (typeof value === \"string\") {\n references.push({\n source: sourceId,\n target: value,\n type: key,\n description: `Nested reference at ${currentPath}`,\n });\n }\n // Handle array of strings\n else if (Array.isArray(value)) {\n for (const target of value) {\n if (typeof target === \"string\") {\n references.push({\n source: sourceId,\n target: target,\n type: key,\n description: `Nested reference at ${currentPath}`,\n });\n }\n }\n }\n }\n // Recurse into nested objects\n else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n references.push(...this.scanNestedReferences(sourceId, value, currentPath));\n }\n // Recurse into arrays\n else if (Array.isArray(value)) {\n references.push(...this.scanNestedReferences(sourceId, value, currentPath));\n }\n }\n\n return references;\n }\n\n /**\n * Find broken references (references to non-existent elements)\n */\n findBrokenReferences(validIds: Set<string>): Reference[] {\n return this.getAllReferences().filter((ref) => !validIds.has(ref.target));\n }\n\n /**\n * Get dependency graph as a directed graph using graphology\n * Nodes are element IDs, edges represent references\n */\n getDependencyGraph(): Graph {\n const graph = new Graph({ type: \"directed\" });\n\n // Add all unique nodes\n const nodes = new Set<string>();\n for (const ref of this.getAllReferences()) {\n nodes.add(ref.source);\n nodes.add(ref.target);\n }\n\n for (const node of nodes) {\n graph.addNode(node);\n }\n\n // Add edges for all references\n for (const ref of this.getAllReferences()) {\n // Use reference type as edge data\n graph.addEdge(ref.source, ref.target, {\n type: ref.type,\n description: ref.description,\n });\n }\n\n return graph;\n }\n\n /**\n * Get statistics about registered references\n */\n getStats(): {\n totalReferences: number;\n uniqueSources: number;\n uniqueTargets: number;\n referenceTypes: string[];\n } {\n return {\n totalReferences: this.getAllReferences().length,\n uniqueSources: this.references.size,\n uniqueTargets: this.targetIndex.size,\n referenceTypes: Array.from(this.typeIndex.keys()),\n };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import Graph from \"graphology\";\nimport type { Reference, Element } from \"../types/index.js\";\nimport { looksLikeElementReference } from \"../utils/element-reference-shape.js\";\n\n\n/**\n * Reference registry - tracks and validates cross-layer references\n *\n * NOTE: Phase 3 Deferred - Registry Consolidation\n * This registry will be consolidated into the GraphModel query API in a future phase.\n * The graph model provides the foundation with graph-based reference tracking.\n * Current approach maintains backward compatibility while the transition occurs.\n *\n * Future: Use GraphModel.getEdgesFrom/To/Between with predicate filtering\n * instead of maintaining separate reference registry.\n *\n * See: https://github.com/tinkermonkey/documentation_robotics/discussions/317\n */\nexport class ReferenceRegistry {\n private references: Map<string, Reference[]>;\n private targetIndex: Map<string, Reference[]>;\n private typeIndex: Map<string, Reference[]>;\n\n // Known reference property names from Python CLI. NOTE: matching one of these names (or the\n // *Ref/*Reference naming convention scanNestedReferences uses) is necessary but not\n // sufficient \u2014 the attribute's VALUE also has to look like an element id\n // (looksLikeElementReference) before we register it as a reference. Name alone isn't\n // reliable: e.g. api.link.operationRef is a schema-declared OpenAPI URL fragment, not a DR\n // element reference, and matches \"*Ref\" purely by naming coincidence.\n private static readonly KNOWN_REF_PROPERTIES = new Set([\n \"realizes\",\n \"realizedBy\",\n \"serves\",\n \"servedBy\",\n \"accesses\",\n \"accessedBy\",\n \"uses\",\n \"usedBy\",\n \"composedOf\",\n \"partOf\",\n \"flows\",\n \"triggers\",\n \"archimateRef\",\n \"businessActorRef\",\n \"stakeholderRef\",\n \"motivationGoalRef\",\n \"dataObjectRef\",\n \"apiOperationRef\",\n \"applicationServiceRef\",\n \"schemaRef\",\n ]);\n\n constructor() {\n this.references = new Map();\n this.targetIndex = new Map();\n this.typeIndex = new Map();\n }\n\n /**\n * Add a reference to the registry\n */\n addReference(reference: Reference): void {\n // Add to source index\n const sourceKey = reference.source;\n if (!this.references.has(sourceKey)) {\n this.references.set(sourceKey, []);\n }\n this.references.get(sourceKey)!.push(reference);\n\n // Add to target index\n const targetKey = reference.target;\n if (!this.targetIndex.has(targetKey)) {\n this.targetIndex.set(targetKey, []);\n }\n this.targetIndex.get(targetKey)!.push(reference);\n\n // Add to type index\n const typeKey = reference.type;\n if (!this.typeIndex.has(typeKey)) {\n this.typeIndex.set(typeKey, []);\n }\n this.typeIndex.get(typeKey)!.push(reference);\n }\n\n /**\n * Get all references from a source element\n */\n getReferencesFrom(sourceId: string): Reference[] {\n return this.references.get(sourceId) ?? [];\n }\n\n /**\n * Get all references to a target element\n */\n getReferencesTo(targetId: string): Reference[] {\n return this.targetIndex.get(targetId) ?? [];\n }\n\n /**\n * Get all references of a specific type\n */\n getReferencesByType(type: string): Reference[] {\n return this.typeIndex.get(type) ?? [];\n }\n\n /**\n * Check if a reference exists\n */\n hasReference(sourceId: string, targetId: string): boolean {\n const refs = this.references.get(sourceId) ?? [];\n return refs.some((ref) => ref.target === targetId);\n }\n\n /**\n * Get all references\n */\n getAllReferences(): Reference[] {\n const allRefs: Reference[] = [];\n for (const refs of this.references.values()) {\n allRefs.push(...refs);\n }\n return allRefs;\n }\n\n /**\n * Clear all references\n */\n clear(): void {\n this.references.clear();\n this.targetIndex.clear();\n this.typeIndex.clear();\n }\n\n /**\n * Register an element and all its references\n * Scans element properties for known reference names\n */\n registerElement(element: Element): void {\n // Validate required element properties\n if (!element) {\n return;\n }\n\n // Use element's path (slug) as the registry key; fall back to id for legacy elements\n const elementId = element.path || element.id;\n\n // Skip if element has no ID\n if (!elementId) {\n return;\n }\n\n // Extract references from explicit references array\n if (element.references) {\n for (const ref of element.references) {\n // Rewrite source to use consistent ID\n this.addReference({\n ...ref,\n source: elementId,\n });\n }\n }\n\n // Scan attributes for known reference properties\n if (element.attributes) {\n for (const [propName, propValue] of Object.entries(element.attributes)) {\n if (ReferenceRegistry.KNOWN_REF_PROPERTIES.has(propName)) {\n // Handle string value\n if (typeof propValue === \"string\") {\n if (looksLikeElementReference(propValue)) {\n this.addReference({\n source: elementId,\n target: propValue,\n type: propName,\n });\n }\n }\n // Handle array of strings\n else if (Array.isArray(propValue)) {\n for (const target of propValue) {\n if (typeof target === \"string\" && looksLikeElementReference(target)) {\n this.addReference({\n source: elementId,\n target: target,\n type: propName,\n });\n }\n }\n }\n }\n }\n\n // Scan nested attributes for *Ref/*Reference properties\n const nestedRefs = this.scanNestedReferences(elementId, element.attributes);\n for (const ref of nestedRefs) {\n this.addReference(ref);\n }\n }\n }\n\n /**\n * Recursively scan nested structures for reference properties\n * Matches Python CLI behavior: looks for properties ending in \"Ref\" or \"Reference\"\n */\n private scanNestedReferences(sourceId: string, data: any, path: string = \"\"): Reference[] {\n const references: Reference[] = [];\n\n if (!data || typeof data !== \"object\") {\n return references;\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n for (let i = 0; i < data.length; i++) {\n const itemPath = path ? `${path}[${i}]` : `[${i}]`;\n references.push(...this.scanNestedReferences(sourceId, data[i], itemPath));\n }\n return references;\n }\n\n // Scan object properties\n for (const [key, value] of Object.entries(data)) {\n const currentPath = path ? `${path}.${key}` : key;\n\n // Skip if this is a top-level known reference property (already handled)\n if (!path && ReferenceRegistry.KNOWN_REF_PROPERTIES.has(key)) {\n continue;\n }\n\n // Check if property name ends with \"Ref\" or \"Reference\"\n if (key.endsWith(\"Ref\") || key.endsWith(\"Reference\")) {\n // Handle string value\n if (typeof value === \"string\") {\n if (looksLikeElementReference(value)) {\n references.push({\n source: sourceId,\n target: value,\n type: key,\n description: `Nested reference at ${currentPath}`,\n });\n }\n }\n // Handle array of strings\n else if (Array.isArray(value)) {\n for (const target of value) {\n if (typeof target === \"string\" && looksLikeElementReference(target)) {\n references.push({\n source: sourceId,\n target: target,\n type: key,\n description: `Nested reference at ${currentPath}`,\n });\n }\n }\n }\n }\n // Recurse into nested objects\n else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n references.push(...this.scanNestedReferences(sourceId, value, currentPath));\n }\n // Recurse into arrays\n else if (Array.isArray(value)) {\n references.push(...this.scanNestedReferences(sourceId, value, currentPath));\n }\n }\n\n return references;\n }\n\n /**\n * Find broken references (references to non-existent elements)\n */\n findBrokenReferences(validIds: Set<string>): Reference[] {\n return this.getAllReferences().filter((ref) => !validIds.has(ref.target));\n }\n\n /**\n * Get dependency graph as a directed graph using graphology\n * Nodes are element IDs, edges represent references\n */\n getDependencyGraph(): Graph {\n const graph = new Graph({ type: \"directed\" });\n\n // Add all unique nodes\n const nodes = new Set<string>();\n for (const ref of this.getAllReferences()) {\n nodes.add(ref.source);\n nodes.add(ref.target);\n }\n\n for (const node of nodes) {\n graph.addNode(node);\n }\n\n // Add edges for all references\n for (const ref of this.getAllReferences()) {\n // Use reference type as edge data\n graph.addEdge(ref.source, ref.target, {\n type: ref.type,\n description: ref.description,\n });\n }\n\n return graph;\n }\n\n /**\n * Get statistics about registered references\n */\n getStats(): {\n totalReferences: number;\n uniqueSources: number;\n uniqueTargets: number;\n referenceTypes: string[];\n } {\n return {\n totalReferences: this.getAllReferences().length,\n uniqueSources: this.references.size,\n uniqueTargets: this.targetIndex.size,\n referenceTypes: Array.from(this.typeIndex.keys()),\n };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { ALL_LAYERS, LAYER_COLORS } from "./types.js";
2
2
  import { isTelemetryEnabled, startSpan, endSpan } from "../telemetry/index.js";
3
3
  import { getErrorMessage } from "../utils/errors.js";
4
+ import { getNodeType } from "../generated/node-types.js";
4
5
  class PlantUMLExporter {
5
6
  name = "PlantUML";
6
7
  supportedLayers = ALL_LAYERS;
@@ -96,7 +97,8 @@ class PlantUMLExporter {
96
97
  if (!node)
97
98
  return null;
98
99
  let sourceRef = node.source_reference;
99
- if (sourceRef || (sourceRef = node.properties?.["x-source-reference"]), !sourceRef && node.properties?.source?.reference && (sourceRef = node.properties.source.reference), !sourceRef)
100
+ const nodeType = node.spec_node_id ? getNodeType(node.spec_node_id) : void 0, declaresAttribute = (key) => !!nodeType && (nodeType.requiredAttributes.includes(key) || nodeType.optionalAttributes.includes(key));
101
+ if (!sourceRef && !declaresAttribute("x-source-reference") && (sourceRef = node.properties?.["x-source-reference"]), !sourceRef && !declaresAttribute("source") && node.properties?.source?.reference && (sourceRef = node.properties.source.reference), !sourceRef)
100
102
  return null;
101
103
  const parts = [];
102
104
  if (Array.isArray(sourceRef.locations) && sourceRef.locations.length > 0) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/export/plantuml-exporter.ts"],
4
- "sourcesContent": ["import type { Model } from \"../core/model.js\";\nimport type { Exporter, ExportOptions } from \"./types.js\";\nimport { ALL_LAYERS, LAYER_COLORS } from \"./types.js\";\nimport { isTelemetryEnabled, startSpan, endSpan } from \"../telemetry/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * PlantUML element entry for diagram generation\n * Contains element metadata for rendering from graph nodes\n */\ninterface PlantUMLElementEntry {\n id: string;\n name: string;\n type: string;\n description?: string;\n}\n\n/**\n * PlantUML Exporter - generates PlantUML syntax for diagram visualization\n * Supports all 13 layers\n *\n * Exports the model as PlantUML component diagram syntax with layers,\n * elements, relationships, and optional source references.\n */\nexport class PlantUMLExporter implements Exporter {\n name = \"PlantUML\";\n supportedLayers = ALL_LAYERS;\n\n async export(model: Model, options: ExportOptions = {}): Promise<string> {\n const span = isTelemetryEnabled\n ? startSpan(\"export.format.plantuml\", {\n \"export.layerCount\": options.layers?.length || this.supportedLayers.length,\n \"export.includeSources\": options.includeSources === true,\n })\n : null;\n\n try {\n const lines: string[] = [];\n\n lines.push(\"@startuml\");\n lines.push(`title \"${this.escapeQuotes(model.manifest.name)}\"`);\n lines.push(\"\");\n\n if (model.manifest.description) {\n lines.push(`note top : \"${this.escapeQuotes(model.manifest.description)}\"`);\n lines.push(\"\");\n }\n\n const layersToExport = options.layers || this.supportedLayers;\n const elementsByLayer = new Map<string, PlantUMLElementEntry[]>();\n const nodesByLayer = new Map<string, any[]>();\n // Map from raw graph node ID \u2192 sanitized PlantUML ID for edge rendering\n const rawToPlantUMLId = new Map<string, string>();\n\n // Collect nodes by layer from graph model\n for (const layerName of layersToExport) {\n const nodes = model.graph.getNodesByLayer(layerName);\n if (nodes.length === 0) continue;\n\n const layerElements: PlantUMLElementEntry[] = [];\n nodesByLayer.set(layerName, nodes);\n\n for (const node of nodes) {\n const pumlId = this.toPlantUMLId(node.id);\n rawToPlantUMLId.set(node.id, pumlId);\n layerElements.push({\n id: pumlId,\n name: node.name,\n type: node.type,\n description: node.description,\n });\n }\n elementsByLayer.set(layerName, layerElements);\n }\n\n // Create packages for each layer\n for (const layerName of layersToExport) {\n const elements = elementsByLayer.get(layerName);\n if (!elements || elements.length === 0) continue;\n\n const color = LAYER_COLORS[layerName] || \"FFFFFF\";\n lines.push(`package \"${layerName}\" #${color} {`);\n\n const nodes = nodesByLayer.get(layerName) || [];\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n lines.push(` component \"${this.escapeQuotes(element.name)}\" as ${element.id}`);\n\n // Add source reference as note if includeSources option is enabled\n if (options.includeSources) {\n const node = nodes[i];\n const sourceRef = this.extractSourceReference(node);\n if (sourceRef) {\n lines.push(` note right of ${element.id}`);\n lines.push(` ${this.escapeQuotes(sourceRef)}`);\n lines.push(` end note`);\n }\n }\n }\n\n lines.push(\"}\");\n lines.push(\"\");\n }\n\n // Add relationships from graph edges\n const edges = model.graph.getAllEdges();\n\n for (const edge of edges) {\n // Only include edges between exported nodes (check raw IDs)\n const srcPuml = rawToPlantUMLId.get(edge.source);\n const dstPuml = rawToPlantUMLId.get(edge.destination);\n if (srcPuml && dstPuml) {\n const arrow = this.getArrowType(edge.predicate);\n lines.push(\n `${srcPuml} ${arrow} ${dstPuml} : ${this.escapeQuotes(edge.predicate)}`\n );\n }\n }\n\n lines.push(\"\");\n lines.push(\"@enduml\");\n\n const result = lines.join(\"\\n\") + \"\\n\";\n\n if (isTelemetryEnabled && span) {\n // Count total elements and relationships\n let totalElements = 0;\n let totalEdges = 0;\n\n for (const elements of elementsByLayer.values()) {\n totalElements += elements.length;\n }\n\n for (const edge of edges) {\n if (rawToPlantUMLId.has(edge.source) && rawToPlantUMLId.has(edge.destination)) {\n totalEdges++;\n }\n }\n\n (span as any).setAttribute(\"export.elementCount\", totalElements);\n (span as any).setAttribute(\"export.relationshipCount\", totalEdges);\n (span as any).setAttribute(\"export.size\", result.length);\n (span as any).setStatus({ code: 0 });\n }\n\n return result;\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n throw error;\n } finally {\n endSpan(span);\n }\n }\n\n /**\n * Get PlantUML arrow type based on predicate\n */\n private getArrowType(predicate: string): string {\n const arrowMap: Record<string, string> = {\n composes: \"*--\",\n aggregates: \"o--\",\n supports: \"-->\",\n realizes: \"..|>\",\n \"flows-to\": \"-->\",\n \"refers-to\": \"..>\",\n };\n return arrowMap[predicate] || \"-->\";\n }\n\n /**\n * Extract and format source reference from node properties\n */\n private extractSourceReference(node: any): string | null {\n if (!node) return null;\n\n // Try node.source_reference first (primary field on GraphNode)\n let sourceRef = node.source_reference;\n\n // Fall back to x-source-reference in properties\n if (!sourceRef) {\n sourceRef = node.properties?.[\"x-source-reference\"];\n }\n\n // Fall back to nested source.reference format\n if (!sourceRef && node.properties?.source?.reference) {\n sourceRef = node.properties.source.reference;\n }\n\n if (!sourceRef) return null;\n\n // Format source reference for display\n const parts: string[] = [];\n\n if (Array.isArray(sourceRef.locations) && sourceRef.locations.length > 0) {\n const location = sourceRef.locations[0];\n if (location.file) {\n parts.push(`Source: ${location.file}`);\n }\n if (location.symbol) {\n parts.push(`Symbol: ${location.symbol}`);\n }\n }\n\n if (sourceRef.repository?.url) {\n parts.push(`Repo: ${sourceRef.repository.url}`);\n if (sourceRef.repository.commit) {\n parts.push(`Commit: ${sourceRef.repository.commit.substring(0, 8)}`);\n }\n }\n\n return parts.length > 0 ? parts.join(\"\\\\n\") : null;\n }\n\n /**\n * Escape quotes for PlantUML syntax\n */\n private escapeQuotes(str: string): string {\n // Escape double quotes within strings for PlantUML\n return str.replace(/\"/g, '\\\\\"');\n }\n\n /**\n * Convert a raw element ID (path or UUID) to a valid PlantUML identifier.\n * PlantUML identifiers cannot contain dots or hyphens, so replace with underscores.\n */\n private toPlantUMLId(id: string): string {\n return id.replace(/[.\\-]/g, \"_\");\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Model } from \"../core/model.js\";\nimport type { Exporter, ExportOptions } from \"./types.js\";\nimport { ALL_LAYERS, LAYER_COLORS } from \"./types.js\";\nimport { isTelemetryEnabled, startSpan, endSpan } from \"../telemetry/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { getNodeType, type SpecNodeId } from \"../generated/node-types.js\";\n\n/**\n * PlantUML element entry for diagram generation\n * Contains element metadata for rendering from graph nodes\n */\ninterface PlantUMLElementEntry {\n id: string;\n name: string;\n type: string;\n description?: string;\n}\n\n/**\n * PlantUML Exporter - generates PlantUML syntax for diagram visualization\n * Supports all 13 layers\n *\n * Exports the model as PlantUML component diagram syntax with layers,\n * elements, relationships, and optional source references.\n */\nexport class PlantUMLExporter implements Exporter {\n name = \"PlantUML\";\n supportedLayers = ALL_LAYERS;\n\n async export(model: Model, options: ExportOptions = {}): Promise<string> {\n const span = isTelemetryEnabled\n ? startSpan(\"export.format.plantuml\", {\n \"export.layerCount\": options.layers?.length || this.supportedLayers.length,\n \"export.includeSources\": options.includeSources === true,\n })\n : null;\n\n try {\n const lines: string[] = [];\n\n lines.push(\"@startuml\");\n lines.push(`title \"${this.escapeQuotes(model.manifest.name)}\"`);\n lines.push(\"\");\n\n if (model.manifest.description) {\n lines.push(`note top : \"${this.escapeQuotes(model.manifest.description)}\"`);\n lines.push(\"\");\n }\n\n const layersToExport = options.layers || this.supportedLayers;\n const elementsByLayer = new Map<string, PlantUMLElementEntry[]>();\n const nodesByLayer = new Map<string, any[]>();\n // Map from raw graph node ID \u2192 sanitized PlantUML ID for edge rendering\n const rawToPlantUMLId = new Map<string, string>();\n\n // Collect nodes by layer from graph model\n for (const layerName of layersToExport) {\n const nodes = model.graph.getNodesByLayer(layerName);\n if (nodes.length === 0) continue;\n\n const layerElements: PlantUMLElementEntry[] = [];\n nodesByLayer.set(layerName, nodes);\n\n for (const node of nodes) {\n const pumlId = this.toPlantUMLId(node.id);\n rawToPlantUMLId.set(node.id, pumlId);\n layerElements.push({\n id: pumlId,\n name: node.name,\n type: node.type,\n description: node.description,\n });\n }\n elementsByLayer.set(layerName, layerElements);\n }\n\n // Create packages for each layer\n for (const layerName of layersToExport) {\n const elements = elementsByLayer.get(layerName);\n if (!elements || elements.length === 0) continue;\n\n const color = LAYER_COLORS[layerName] || \"FFFFFF\";\n lines.push(`package \"${layerName}\" #${color} {`);\n\n const nodes = nodesByLayer.get(layerName) || [];\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n lines.push(` component \"${this.escapeQuotes(element.name)}\" as ${element.id}`);\n\n // Add source reference as note if includeSources option is enabled\n if (options.includeSources) {\n const node = nodes[i];\n const sourceRef = this.extractSourceReference(node);\n if (sourceRef) {\n lines.push(` note right of ${element.id}`);\n lines.push(` ${this.escapeQuotes(sourceRef)}`);\n lines.push(` end note`);\n }\n }\n }\n\n lines.push(\"}\");\n lines.push(\"\");\n }\n\n // Add relationships from graph edges\n const edges = model.graph.getAllEdges();\n\n for (const edge of edges) {\n // Only include edges between exported nodes (check raw IDs)\n const srcPuml = rawToPlantUMLId.get(edge.source);\n const dstPuml = rawToPlantUMLId.get(edge.destination);\n if (srcPuml && dstPuml) {\n const arrow = this.getArrowType(edge.predicate);\n lines.push(\n `${srcPuml} ${arrow} ${dstPuml} : ${this.escapeQuotes(edge.predicate)}`\n );\n }\n }\n\n lines.push(\"\");\n lines.push(\"@enduml\");\n\n const result = lines.join(\"\\n\") + \"\\n\";\n\n if (isTelemetryEnabled && span) {\n // Count total elements and relationships\n let totalElements = 0;\n let totalEdges = 0;\n\n for (const elements of elementsByLayer.values()) {\n totalElements += elements.length;\n }\n\n for (const edge of edges) {\n if (rawToPlantUMLId.has(edge.source) && rawToPlantUMLId.has(edge.destination)) {\n totalEdges++;\n }\n }\n\n (span as any).setAttribute(\"export.elementCount\", totalElements);\n (span as any).setAttribute(\"export.relationshipCount\", totalEdges);\n (span as any).setAttribute(\"export.size\", result.length);\n (span as any).setStatus({ code: 0 });\n }\n\n return result;\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n throw error;\n } finally {\n endSpan(span);\n }\n }\n\n /**\n * Get PlantUML arrow type based on predicate\n */\n private getArrowType(predicate: string): string {\n const arrowMap: Record<string, string> = {\n composes: \"*--\",\n aggregates: \"o--\",\n supports: \"-->\",\n realizes: \"..|>\",\n \"flows-to\": \"-->\",\n \"refers-to\": \"..>\",\n };\n return arrowMap[predicate] || \"-->\";\n }\n\n /**\n * Extract and format source reference from node properties\n */\n private extractSourceReference(node: any): string | null {\n if (!node) return null;\n\n // Try node.source_reference first (primary, structurally-typed field on GraphNode)\n let sourceRef = node.source_reference;\n\n // Legacy fallback for pre-migration data that stored provenance directly under\n // properties[\"x-source-reference\"] / properties.source.reference instead of the\n // first-class source_reference field. Only trust these fallbacks when the node's OWN\n // schema does NOT declare \"x-source-reference\"/\"source\" as a real attribute \u2014 4 node\n // types (data-store.accesspattern/collection/field/storedlogic) declare\n // \"x-source-reference\", and 37 more declare \"source\", as genuine domain string\n // attributes (e.g. security.accesscondition.source); for those, node.properties[...]\n // holds real domain data, not source-code provenance, and reading it here would\n // silently mislabel it as \"Source:\"/\"Repo:\" in the generated diagram.\n const nodeType = node.spec_node_id ? getNodeType(node.spec_node_id as SpecNodeId) : undefined;\n const declaresAttribute = (key: string): boolean =>\n !!nodeType && (nodeType.requiredAttributes.includes(key) || nodeType.optionalAttributes.includes(key));\n\n if (!sourceRef && !declaresAttribute(\"x-source-reference\")) {\n sourceRef = node.properties?.[\"x-source-reference\"];\n }\n\n if (!sourceRef && !declaresAttribute(\"source\") && node.properties?.source?.reference) {\n sourceRef = node.properties.source.reference;\n }\n\n if (!sourceRef) return null;\n\n // Format source reference for display\n const parts: string[] = [];\n\n if (Array.isArray(sourceRef.locations) && sourceRef.locations.length > 0) {\n const location = sourceRef.locations[0];\n if (location.file) {\n parts.push(`Source: ${location.file}`);\n }\n if (location.symbol) {\n parts.push(`Symbol: ${location.symbol}`);\n }\n }\n\n if (sourceRef.repository?.url) {\n parts.push(`Repo: ${sourceRef.repository.url}`);\n if (sourceRef.repository.commit) {\n parts.push(`Commit: ${sourceRef.repository.commit.substring(0, 8)}`);\n }\n }\n\n return parts.length > 0 ? parts.join(\"\\\\n\") : null;\n }\n\n /**\n * Escape quotes for PlantUML syntax\n */\n private escapeQuotes(str: string): string {\n // Escape double quotes within strings for PlantUML\n return str.replace(/\"/g, '\\\\\"');\n }\n\n /**\n * Convert a raw element ID (path or UUID) to a valid PlantUML identifier.\n * PlantUML identifiers cannot contain dots or hyphens, so replace with underscores.\n */\n private toPlantUMLId(id: string): string {\n return id.replace(/[.\\-]/g, \"_\");\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -21,7 +21,7 @@ async function initTelemetry() {
21
21
  `), process.stderr.write(` - Logs endpoint: ${otlpConfig.logsEndpoint}
22
22
  `), process.stderr.write(` - Service name: ${otlpConfig.serviceName}
23
23
  `));
24
- const cliVersion = "0.1.11";
24
+ const cliVersion = "0.1.12";
25
25
  let projectName = "unknown";
26
26
  try {
27
27
  const [{ readFileSync, existsSync }, { parse }] = await Promise.all([
@@ -0,0 +1,12 @@
1
+ import { getAllLayerIds } from "../generated/layer-registry.js";
2
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, KNOWN_LAYERS = new Set(getAllLayerIds());
3
+ function looksLikeElementReference(value) {
4
+ if (UUID_PATTERN.test(value))
5
+ return !0;
6
+ const dotIndex = value.indexOf(".");
7
+ return dotIndex <= 0 ? !1 : KNOWN_LAYERS.has(value.slice(0, dotIndex));
8
+ }
9
+ export {
10
+ looksLikeElementReference
11
+ };
12
+ //# sourceMappingURL=element-reference-shape.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/element-reference-shape.ts"],
4
+ "sourcesContent": ["/**\n * Shape check for \"does this string plausibly identify a DR model element\" \u2014 a UUIDv4\n * (element.id) or a dot-separated `{layer}.{type}.{slug}` id whose first segment is a real,\n * known DR layer (matching the id scheme `NamingValidator` enforces on every element).\n *\n * Used by `ReferenceRegistry` to gate its `*Ref`/`*Reference` attribute-name heuristic: an\n * attribute NAME matching that convention is not, by itself, reliable evidence that its VALUE\n * is an element reference \u2014 `api.link.operationRef` is a schema-declared OpenAPI URL fragment\n * (e.g. \"#/paths/~1users/get\"), not an element id, and matches the naming convention purely by\n * coincidence. Checking the value's shape catches that case (and any future one like it)\n * without needing per-attribute schema annotations that don't exist today.\n *\n * Deliberately a shape check, not an existence check: a value that looks like an element id but\n * doesn't resolve to one is exactly what `ReferenceRegistry.findBrokenReferences()` is supposed\n * to catch, so this must not filter those out too.\n */\nimport { getAllLayerIds } from \"../generated/layer-registry.js\";\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst KNOWN_LAYERS = new Set(getAllLayerIds());\n\nexport function looksLikeElementReference(value: string): boolean {\n if (UUID_PATTERN.test(value)) return true;\n\n const dotIndex = value.indexOf(\".\");\n if (dotIndex <= 0) return false;\n\n return KNOWN_LAYERS.has(value.slice(0, dotIndex));\n}\n"],
5
+ "mappings": "AAgBA,SAAS,sBAAsB;AAE/B,MAAM,eAAe,mEACf,eAAe,IAAI,IAAI,eAAc,CAAE;AAEvC,SAAU,0BAA0B,OAAa;AACrD,MAAI,aAAa,KAAK,KAAK;AAAG,WAAO;AAErC,QAAM,WAAW,MAAM,QAAQ,GAAG;AAClC,SAAI,YAAY,IAAU,KAEnB,aAAa,IAAI,MAAM,MAAM,GAAG,QAAQ,CAAC;AAClD;",
6
+ "names": []
7
+ }
@@ -1,6 +1,6 @@
1
1
  import { readJSON, fileExists } from "./file-io.js";
2
2
  import { join } from "path";
3
- const CLI_VERSION = "0.1.11", BUNDLED_SPEC_VERSION = "0.9.0";
3
+ const CLI_VERSION = "0.1.12", BUNDLED_SPEC_VERSION = "0.9.0";
4
4
  function getCliVersion() {
5
5
  return CLI_VERSION;
6
6
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/spec-version.ts"],
4
- "sourcesContent": ["/**\n * Spec Version Utilities\n *\n * Manages spec version information for the CLI\n */\n\nimport { readJSON, fileExists } from \"./file-io.js\";\nimport { join } from \"path\";\n\n/**\n * CLI version\n * This should match the version in ../package.json\n * Updated during release process\n */\nconst CLI_VERSION = \"0.1.11\";\n\n/**\n * Bundled spec version\n * This should match the version in ../../../spec/VERSION\n * Updated during build process\n */\nconst BUNDLED_SPEC_VERSION = \"0.9.0\";\n\n/**\n * Get the CLI version\n *\n * @returns CLI version string\n */\nexport function getCliVersion(): string {\n return CLI_VERSION;\n}\n\n/**\n * Get the CLI's bundled spec version\n *\n * This is the version of the DR specification that is bundled with this CLI.\n * It represents what version of the spec this CLI supports.\n *\n * @returns Bundled spec version string\n */\nexport function getCliBundledSpecVersion(): string {\n return BUNDLED_SPEC_VERSION;\n}\n\n/**\n * Read spec version from .dr/manifest.json (installed spec reference)\n *\n * @param drPath - Path to .dr/ directory\n * @returns Spec version from .dr/manifest.json, or null if not found\n */\nexport async function getInstalledSpecVersion(drPath: string): Promise<string | null> {\n const manifestPath = join(drPath, \"manifest.json\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n const manifest = await readJSON<{ specVersion?: string }>(manifestPath);\n return manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Read spec version from documentation_robotics/model/manifest.yaml\n *\n * @param modelPath - Path to model directory\n * @returns Spec version from model manifest, or null if not found\n */\nexport async function getModelSpecVersion(modelPath: string): Promise<string | null> {\n const manifestPath = join(modelPath, \"manifest.yaml\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n // Use YAML parser\n const yaml = await import(\"yaml\");\n const fs = await import(\"fs/promises\");\n const content = await fs.readFile(manifestPath, \"utf-8\");\n const manifest = yaml.parse(content) as { spec_version?: string; specVersion?: string };\n // Check snake_case first (Python CLI format), then camelCase (legacy)\n return manifest.spec_version || manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Spec Version Utilities\n *\n * Manages spec version information for the CLI\n */\n\nimport { readJSON, fileExists } from \"./file-io.js\";\nimport { join } from \"path\";\n\n/**\n * CLI version\n * This should match the version in ../package.json\n * Updated during release process\n */\nconst CLI_VERSION = \"0.1.12\";\n\n/**\n * Bundled spec version\n * This should match the version in ../../../spec/VERSION\n * Updated during build process\n */\nconst BUNDLED_SPEC_VERSION = \"0.9.0\";\n\n/**\n * Get the CLI version\n *\n * @returns CLI version string\n */\nexport function getCliVersion(): string {\n return CLI_VERSION;\n}\n\n/**\n * Get the CLI's bundled spec version\n *\n * This is the version of the DR specification that is bundled with this CLI.\n * It represents what version of the spec this CLI supports.\n *\n * @returns Bundled spec version string\n */\nexport function getCliBundledSpecVersion(): string {\n return BUNDLED_SPEC_VERSION;\n}\n\n/**\n * Read spec version from .dr/manifest.json (installed spec reference)\n *\n * @param drPath - Path to .dr/ directory\n * @returns Spec version from .dr/manifest.json, or null if not found\n */\nexport async function getInstalledSpecVersion(drPath: string): Promise<string | null> {\n const manifestPath = join(drPath, \"manifest.json\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n const manifest = await readJSON<{ specVersion?: string }>(manifestPath);\n return manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Read spec version from documentation_robotics/model/manifest.yaml\n *\n * @param modelPath - Path to model directory\n * @returns Spec version from model manifest, or null if not found\n */\nexport async function getModelSpecVersion(modelPath: string): Promise<string | null> {\n const manifestPath = join(modelPath, \"manifest.yaml\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n // Use YAML parser\n const yaml = await import(\"yaml\");\n const fs = await import(\"fs/promises\");\n const content = await fs.readFile(manifestPath, \"utf-8\");\n const manifest = yaml.parse(content) as { spec_version?: string; specVersion?: string };\n // Check snake_case first (Python CLI format), then camelCase (legacy)\n return manifest.spec_version || manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n"],
5
5
  "mappings": "AAMA,SAAS,UAAU,kBAAkB;AACrC,SAAS,YAAY;AAOrB,MAAM,cAAc,UAOd,uBAAuB;AAOvB,SAAU,gBAAa;AAC3B,SAAO;AACT;AAUM,SAAU,2BAAwB;AACtC,SAAO;AACT;AAQA,eAAsB,wBAAwB,QAAc;AAC1D,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,CAAE,MAAM,WAAW,YAAY;AACjC,WAAO;AAGT,MAAI;AAEF,YADiB,MAAM,SAAmC,YAAY,GACtD,eAAe;EACjC,QAAQ;AACN,WAAO;EACT;AACF;AAQA,eAAsB,oBAAoB,WAAiB;AACzD,QAAM,eAAe,KAAK,WAAW,eAAe;AAEpD,MAAI,CAAE,MAAM,WAAW,YAAY;AACjC,WAAO;AAGT,MAAI;AAEF,UAAM,OAAO,MAAM,OAAO,MAAM,GAE1B,UAAU,OADL,MAAM,OAAO,aAAa,GACZ,SAAS,cAAc,OAAO,GACjD,WAAW,KAAK,MAAM,OAAO;AAEnC,WAAO,SAAS,gBAAgB,SAAS,eAAe;EAC1D,QAAQ;AACN,WAAO;EACT;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@documentation-robotics/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "CLI for Documentation Robotics",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",