@documentation-robotics/cli 0.1.13 → 0.2.0

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.
Files changed (181) hide show
  1. package/README.md +38 -0
  2. package/dist/analyzers/verify-engine.js +22 -19
  3. package/dist/analyzers/verify-engine.js.map +2 -2
  4. package/dist/cli.js +101 -27
  5. package/dist/cli.js.map +3 -3
  6. package/dist/coding-agents/agents/claude-code.js +3 -1
  7. package/dist/coding-agents/agents/claude-code.js.map +2 -2
  8. package/dist/coding-agents/claude-code-client.js +2 -1
  9. package/dist/coding-agents/claude-code-client.js.map +2 -2
  10. package/dist/coding-agents/copilot-client.js +17 -12
  11. package/dist/coding-agents/copilot-client.js.map +2 -2
  12. package/dist/coding-agents/default-permissions.js +122 -0
  13. package/dist/coding-agents/default-permissions.js.map +7 -0
  14. package/dist/commands/add.js +10 -6
  15. package/dist/commands/add.js.map +2 -2
  16. package/dist/commands/analyzer.js +18 -10
  17. package/dist/commands/analyzer.js.map +2 -2
  18. package/dist/commands/audit.js +6 -6
  19. package/dist/commands/audit.js.map +2 -2
  20. package/dist/commands/changeset.js +238 -145
  21. package/dist/commands/changeset.js.map +2 -2
  22. package/dist/commands/delete.js +45 -18
  23. package/dist/commands/delete.js.map +3 -3
  24. package/dist/commands/element.js +3 -2
  25. package/dist/commands/element.js.map +2 -2
  26. package/dist/commands/farm.js +657 -0
  27. package/dist/commands/farm.js.map +7 -0
  28. package/dist/commands/init.js +11 -1
  29. package/dist/commands/init.js.map +2 -2
  30. package/dist/commands/mcp.js +107 -0
  31. package/dist/commands/mcp.js.map +7 -0
  32. package/dist/commands/relationship.js +53 -30
  33. package/dist/commands/relationship.js.map +2 -2
  34. package/dist/commands/reports.js +1 -1
  35. package/dist/commands/reports.js.map +2 -2
  36. package/dist/commands/update.js +19 -6
  37. package/dist/commands/update.js.map +2 -2
  38. package/dist/commands/validate.js +41 -3
  39. package/dist/commands/validate.js.map +2 -2
  40. package/dist/commands/version.js +2 -2
  41. package/dist/commands/version.js.map +2 -2
  42. package/dist/config.js +83 -0
  43. package/dist/config.js.map +7 -0
  44. package/dist/core/annotation-store.js +138 -0
  45. package/dist/core/annotation-store.js.map +7 -0
  46. package/dist/core/farm-manifest.js +134 -0
  47. package/dist/core/farm-manifest.js.map +7 -0
  48. package/dist/core/farm-sync-engine.js +346 -0
  49. package/dist/core/farm-sync-engine.js.map +7 -0
  50. package/dist/core/farm-sync-state.js +159 -0
  51. package/dist/core/farm-sync-state.js.map +7 -0
  52. package/dist/core/manifest.js +6 -3
  53. package/dist/core/manifest.js.map +2 -2
  54. package/dist/core/migration-registry.js +55 -1
  55. package/dist/core/migration-registry.js.map +2 -2
  56. package/dist/core/model.js +177 -16
  57. package/dist/core/model.js.map +3 -3
  58. package/dist/core/virtual-projection.js +1 -0
  59. package/dist/core/virtual-projection.js.map +2 -2
  60. package/dist/generated/node-types.js +1 -1
  61. package/dist/generated/node-types.js.map +2 -2
  62. package/dist/generated/relationship-index.js +45 -9
  63. package/dist/generated/relationship-index.js.map +2 -2
  64. package/dist/integrations/claude_code/docs/MCP_SERVER.md +55 -0
  65. package/dist/integrations/claude_code/docs/README.md +4 -0
  66. package/dist/mcp/api-key-manager.js +98 -0
  67. package/dist/mcp/api-key-manager.js.map +7 -0
  68. package/dist/mcp/http-transport.js +176 -0
  69. package/dist/mcp/http-transport.js.map +7 -0
  70. package/dist/mcp/resource-registry.js +97 -0
  71. package/dist/mcp/resource-registry.js.map +7 -0
  72. package/dist/mcp/tool-registry.js +44 -0
  73. package/dist/mcp/tool-registry.js.map +7 -0
  74. package/dist/mcp/tools/annotation-create.js +39 -0
  75. package/dist/mcp/tools/annotation-create.js.map +7 -0
  76. package/dist/mcp/tools/annotation-delete.js +29 -0
  77. package/dist/mcp/tools/annotation-delete.js.map +7 -0
  78. package/dist/mcp/tools/annotation-get.js +29 -0
  79. package/dist/mcp/tools/annotation-get.js.map +7 -0
  80. package/dist/mcp/tools/annotation-list.js +24 -0
  81. package/dist/mcp/tools/annotation-list.js.map +7 -0
  82. package/dist/mcp/tools/annotation-reply.js +34 -0
  83. package/dist/mcp/tools/annotation-reply.js.map +7 -0
  84. package/dist/mcp/tools/annotation-shared.js +10 -0
  85. package/dist/mcp/tools/annotation-shared.js.map +7 -0
  86. package/dist/mcp/tools/annotation-update.js +39 -0
  87. package/dist/mcp/tools/annotation-update.js.map +7 -0
  88. package/dist/mcp/tools/changeset-list.js +33 -0
  89. package/dist/mcp/tools/changeset-list.js.map +7 -0
  90. package/dist/mcp/tools/changeset-show.js +39 -0
  91. package/dist/mcp/tools/changeset-show.js.map +7 -0
  92. package/dist/mcp/tools/chat-launch.js +50 -0
  93. package/dist/mcp/tools/chat-launch.js.map +7 -0
  94. package/dist/mcp/tools/chat-status.js +26 -0
  95. package/dist/mcp/tools/chat-status.js.map +7 -0
  96. package/dist/mcp/tools/index.js +47 -0
  97. package/dist/mcp/tools/index.js.map +7 -0
  98. package/dist/mcp/tools/model-add.js +106 -0
  99. package/dist/mcp/tools/model-add.js.map +7 -0
  100. package/dist/mcp/tools/model-delete.js +99 -0
  101. package/dist/mcp/tools/model-delete.js.map +7 -0
  102. package/dist/mcp/tools/model-export.js +72 -0
  103. package/dist/mcp/tools/model-export.js.map +7 -0
  104. package/dist/mcp/tools/model-info.js +51 -0
  105. package/dist/mcp/tools/model-info.js.map +7 -0
  106. package/dist/mcp/tools/model-list.js +53 -0
  107. package/dist/mcp/tools/model-list.js.map +7 -0
  108. package/dist/mcp/tools/model-reload.js +34 -0
  109. package/dist/mcp/tools/model-reload.js.map +7 -0
  110. package/dist/mcp/tools/model-search.js +88 -0
  111. package/dist/mcp/tools/model-search.js.map +7 -0
  112. package/dist/mcp/tools/model-show.js +41 -0
  113. package/dist/mcp/tools/model-show.js.map +7 -0
  114. package/dist/mcp/tools/model-stats.js +22 -0
  115. package/dist/mcp/tools/model-stats.js.map +7 -0
  116. package/dist/mcp/tools/model-trace.js +55 -0
  117. package/dist/mcp/tools/model-trace.js.map +7 -0
  118. package/dist/mcp/tools/model-update.js +105 -0
  119. package/dist/mcp/tools/model-update.js.map +7 -0
  120. package/dist/mcp/tools/model-validate.js +78 -0
  121. package/dist/mcp/tools/model-validate.js.map +7 -0
  122. package/dist/mcp/tools/shared.js +103 -0
  123. package/dist/mcp/tools/shared.js.map +7 -0
  124. package/dist/reports/model-layer-report-generator.js +5 -3
  125. package/dist/reports/model-layer-report-generator.js.map +2 -2
  126. package/dist/reports/model-readme-generator.js +116 -0
  127. package/dist/reports/model-readme-generator.js.map +7 -0
  128. package/dist/reports/model-report-data.js +37 -1
  129. package/dist/reports/model-report-data.js.map +2 -2
  130. package/dist/reports/model-report-orchestrator.js +29 -6
  131. package/dist/reports/model-report-orchestrator.js.map +2 -2
  132. package/dist/reports/model-report-utils.js +9 -0
  133. package/dist/reports/model-report-utils.js.map +7 -0
  134. package/dist/schemas/bundled/analyzers/cbm.json +2 -2
  135. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  136. package/dist/schemas/bundled/api.json +1 -1
  137. package/dist/schemas/bundled/apm.json +1 -1
  138. package/dist/schemas/bundled/application.json +12 -1
  139. package/dist/schemas/bundled/base.json +1 -1
  140. package/dist/schemas/bundled/business.json +1 -1
  141. package/dist/schemas/bundled/data-model.json +38 -2
  142. package/dist/schemas/bundled/data-store.json +1 -1
  143. package/dist/schemas/bundled/manifest.json +4 -4
  144. package/dist/schemas/bundled/motivation.json +1 -1
  145. package/dist/schemas/bundled/navigation.json +1 -1
  146. package/dist/schemas/bundled/product.json +1 -1
  147. package/dist/schemas/bundled/security.json +1 -1
  148. package/dist/schemas/bundled/technology.json +1 -1
  149. package/dist/schemas/bundled/testing.json +1 -1
  150. package/dist/schemas/bundled/ux.json +1 -1
  151. package/dist/server/server.js +22 -3
  152. package/dist/server/server.js.map +2 -2
  153. package/dist/telemetry/config.js +2 -40
  154. package/dist/telemetry/config.js.map +2 -2
  155. package/dist/telemetry/index.js +1 -1
  156. package/dist/types/index.js.map +2 -2
  157. package/dist/utils/ansi-suppressor.js +25 -0
  158. package/dist/utils/ansi-suppressor.js.map +7 -0
  159. package/dist/utils/color-utils.js +34 -0
  160. package/dist/utils/color-utils.js.map +7 -0
  161. package/dist/utils/element-reference-shape.js +15 -1
  162. package/dist/utils/element-reference-shape.js.map +3 -3
  163. package/dist/utils/errors.js +69 -10
  164. package/dist/utils/errors.js.map +2 -2
  165. package/dist/utils/globals.js +17 -1
  166. package/dist/utils/globals.js.map +2 -2
  167. package/dist/utils/project-paths.js +24 -2
  168. package/dist/utils/project-paths.js.map +2 -2
  169. package/dist/utils/reference-path-parser.js +49 -0
  170. package/dist/utils/reference-path-parser.js.map +7 -0
  171. package/dist/utils/source-reference.js +5 -0
  172. package/dist/utils/source-reference.js.map +2 -2
  173. package/dist/utils/spec-version.js +10 -2
  174. package/dist/utils/spec-version.js.map +2 -2
  175. package/dist/validators/composed-reference-validator.js +262 -0
  176. package/dist/validators/composed-reference-validator.js.map +7 -0
  177. package/dist/validators/reference-validator.js +59 -8
  178. package/dist/validators/reference-validator.js.map +3 -3
  179. package/dist/validators/relationship-schema-validator.js +30 -2
  180. package/dist/validators/relationship-schema-validator.js.map +3 -3
  181. package/package.json +6 -3
@@ -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__`/`__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
- "names": ["path", "fs", "layerId", "manifestPath"]
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 { getCodebasePath } from \"../utils/globals.js\";\nimport { getErrorMessage } from \"../utils/errors.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 codebaseRoot: string;\n manifest: Manifest;\n graph: GraphModel;\n layers: Map<string, Layer>;\n relationships: Relationships;\n lazyLoad: boolean;\n loadedLayerFilter?: string[];\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.codebaseRoot = options.codebaseRoot ?? 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.loadedLayerFilter = options.layers;\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 // Support both standard structure (rootPath/documentation-robotics/model) and non-standard (rootPath directly)\n let 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 (err) {\n // Only ignore ENOENT (directory not found) \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n // Standard structure doesn't exist, try non-standard structure (rootPath directly)\n try {\n const entries = await fs.readdir(this.rootPath, { 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 = `${this.rootPath}/${layerDir.name}`;\n }\n } catch (err) {\n // Only ignore ENOENT (directory not found) \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n // Neither structure found\n }\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 (support both standard and non-standard structures)\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 (err) {\n // Only ignore ENOENT (directory not found) \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n // Standard structure doesn't exist, try non-standard structure (rootPath directly)\n try {\n const entries = await fs.readdir(this.rootPath, { 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 = `${this.rootPath}/${layerDir.name}`;\n }\n } catch (err) {\n // Only ignore ENOENT (directory not found) \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n // Neither structure found\n }\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.codebase_path) {\n yamlData.codebase_path = this.manifest.codebase_path;\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 if (this.manifest.models && Object.keys(this.manifest.models).length > 0) {\n yamlData.models = this.manifest.models;\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 // Treat null (from comment-only files) as an empty array\n if (data === null) {\n data = [];\n }\n\n if (!Array.isArray(data)) {\n const typeLabel = 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 * Auto-resolve codebaseRoot from farm manifest if inside a farm\n *\n * @param modelRoot - The model's project root (documentation_robotics parent)\n * @returns Resolved codebase root path, or null if not in farm or resolution fails\n */\n private static async resolveFarmCodebaseRoot(modelRoot: string): Promise<string | null> {\n const { findFarmRoot } = await import(\"../utils/project-paths.js\");\n const { FarmManifest } = await import(\"./farm-manifest.js\");\n\n // Find farm root (may return null if not in a farm)\n const farmRoot = await findFarmRoot(modelRoot);\n if (!farmRoot) {\n return null;\n }\n\n // Load farm manifest\n const farmYamlPath = path.join(farmRoot, \"farm.yaml\");\n let manifest: any;\n try {\n manifest = await FarmManifest.load(farmYamlPath);\n } catch (error) {\n // Re-throw meaningful errors (YAML parse, permission errors, etc.)\n // Only return null if the farm setup is genuinely not present\n const errorMsg = getErrorMessage(error);\n if (errorMsg.includes(\"ENOENT\") || errorMsg.includes(\"not found\")) {\n return null; // Farm not found - expected case, not an error\n }\n throw new Error(`Failed to load farm manifest at ${farmYamlPath}: ${errorMsg}`);\n }\n\n // Find the model folder name relative to farm root\n const modelName = path.basename(modelRoot);\n\n // Look for a project where model matches our model's basename\n for (const project of manifest.getAllProjects()) {\n if (path.basename(project.model) === modelName) {\n // Found matching project, resolve codebase path\n const codebaseFullPath = path.resolve(farmRoot, project.source);\n return codebaseFullPath;\n }\n }\n\n return null; // No matching project found in farm - not an error\n }\n\n /**\n * Walk upward from startPath looking for documentation-robotics/model/manifest.yaml,\n * stopping at the nearest .git directory (repo boundary) so this can never cross into\n * an unrelated parent project or a sibling farm/detached model directory.\n *\n * @param startPath - Directory to start the walk from\n * @returns Manifest path if found within the repo boundary, or null\n */\n private static async findManifestUpwardBounded(startPath: string): Promise<string | null> {\n let currentPath = startPath;\n\n while (true) {\n const candidateManifestPath = path.join(currentPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n try {\n await fs.access(candidateManifestPath);\n return candidateManifestPath;\n } catch {\n // Not found at this level, keep looking\n }\n\n // Stop once we've checked the repo root (marked by .git) - never walk past it\n try {\n await fs.access(path.join(currentPath, \".git\"));\n return null;\n } catch {\n // Not a repo boundary, continue upward\n }\n\n const parentPath = path.dirname(currentPath);\n if (parentPath === currentPath) {\n return null; // Reached filesystem root\n }\n currentPath = parentPath;\n }\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 // Highest priority: explicit startPath parameter (used in concurrent contexts to avoid env var mutations)\n if (startPath) {\n const resolvedPath = path.resolve(startPath);\n let manifestPath: string;\n\n if (resolvedPath.endsWith(\"manifest.yaml\")) {\n manifestPath = resolvedPath;\n } else if (resolvedPath.endsWith(\"model\")) {\n manifestPath = path.join(resolvedPath, \"manifest.yaml\");\n } else {\n manifestPath = path.join(resolvedPath, \"documentation-robotics\", \"model\", \"manifest.yaml\");\n }\n\n try {\n await fs.access(manifestPath);\n\n // Determine projectRoot: flexible approach for both standard and non-standard structures\n let projectRoot: string;\n const manifestParentDir = path.dirname(manifestPath);\n\n // First, try assuming the standard structure (manifest 3 levels deep)\n const standardProjectRoot = path.dirname(path.dirname(path.dirname(manifestPath)));\n const standardDocRobotsPath = path.join(standardProjectRoot, \"documentation-robotics\", \"model\");\n\n try {\n await fs.access(standardDocRobotsPath);\n projectRoot = standardProjectRoot;\n } catch (err) {\n // Only ignore ENOENT (path not found) \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n // Not in standard structure, use manifest's parent directory as project root\n projectRoot = manifestParentDir;\n }\n\n return { projectRoot, manifestPath: path.normalize(manifestPath) };\n } catch (err) {\n // Only wrap ENOENT errors as \"not found\" \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n\n // startPath itself doesn't directly contain the manifest (e.g. it's a\n // subdirectory of the repo, like cli/ within the repo root). Fall back to a\n // bounded upward walk stopping at the nearest .git boundary. This never crosses\n // into a sibling farm/detached model directory since those are siblings of the\n // codebase, not ancestors, so they can't be reached by walking upward.\n const fallbackManifestPath = await Model.findManifestUpwardBounded(resolvedPath);\n if (fallbackManifestPath) {\n const fallbackProjectRoot = path.dirname(path.dirname(path.dirname(fallbackManifestPath)));\n return { projectRoot: fallbackProjectRoot, manifestPath: path.normalize(fallbackManifestPath) };\n }\n\n throw new Error(`Model not found at ${startPath}`);\n }\n }\n\n // Secondary priority: optional override via env var\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\n // Determine projectRoot: flexible approach for both standard and non-standard structures\n let projectRoot: string;\n const manifestParentDir = path.dirname(manifestPath);\n\n // First, try assuming the standard structure (manifest 3 levels deep)\n const standardProjectRoot = path.dirname(path.dirname(path.dirname(manifestPath)));\n const standardDocRobotsPath = path.join(standardProjectRoot, \"documentation-robotics\", \"model\");\n\n try {\n await fs.access(standardDocRobotsPath);\n projectRoot = standardProjectRoot;\n } catch (err) {\n // Only ignore ENOENT (path not found) \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n // Not in standard structure, use manifest's parent directory as project root\n projectRoot = manifestParentDir;\n }\n\n return { projectRoot, manifestPath: path.normalize(manifestPath) };\n } catch (err) {\n // Only wrap ENOENT errors as \"not found\" \u2014 re-throw permission/I/O errors\n const isNotFoundError = (err as any)?.code === \"ENOENT\";\n if (!isNotFoundError) {\n throw err;\n }\n throw new Error(`Model not found at DR_MODEL_PATH: ${process.env.DR_MODEL_PATH}`);\n }\n }\n\n // Fallback: use findProjectRoot to locate documentation_robotics/ folder\n const searchPath = process.cwd();\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 * Validate that the manifest's models field is a plain object.\n *\n * @param models - The raw value from manifest YAML\n * @returns Validation result with valid flag and error message if invalid\n */\n static validateModelsField(models: unknown): { valid: boolean; error?: string } {\n // Check if models is null or undefined (this shouldn't happen since we check before calling)\n if (models === null || models === undefined) {\n return { valid: true };\n }\n\n // Check if models is a plain object (not an array, string, or other primitive)\n if (typeof models !== \"object\" || Array.isArray(models)) {\n return {\n valid: false,\n error:\n `Invalid manifest 'models' field: expected an object with model names as keys ` +\n `(e.g., { \"auth-service\": { } }), but got ${Array.isArray(models) ? \"an array\" : `a ${typeof models}`}. ` +\n `This often happens when YAML syntax is incorrect, like writing 'models: auth-service' ` +\n `instead of 'models: { auth-service: {} }' or 'models:\\\\n auth-service: {}'.`,\n };\n }\n\n // Validate that it's a plain object (not a class instance)\n if (Object.getPrototypeOf(models) !== Object.prototype) {\n return {\n valid: false,\n error:\n `Invalid manifest 'models' field: expected a plain object, but got an instance of ${\n (models as any).constructor?.name || \"unknown\"\n }. ` +\n `Please ensure 'models' is a simple YAML object mapping model names to declarations.`,\n };\n }\n\n return { valid: true };\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 cliVersion: manifestYaml.cli_version,\n specVersion: manifestYaml.spec_version || \"0.6.0\",\n created: manifestYaml.created || new Date().toISOString(),\n modified: manifestYaml.updated || new Date().toISOString(),\n };\n\n // Load external model declarations if present\n if (manifestYaml.models) {\n const modelsValidation = Model.validateModelsField(manifestYaml.models);\n if (!modelsValidation.valid) {\n throw new Error(modelsValidation.error);\n }\n manifestData.models = manifestYaml.models;\n }\n\n // Load codebase_path if present\n if (manifestYaml.codebase_path) {\n manifestData.codebase_path = manifestYaml.codebase_path;\n }\n\n const manifest = new Manifest(manifestData);\n\n // Resolve codebaseRoot with priority: options > CLI flag > manifest > farm > default\n let codebaseRoot = projectRoot;\n if (options.codebaseRoot) {\n codebaseRoot = options.codebaseRoot;\n } else {\n const globalCodebasePath = getCodebasePath();\n if (globalCodebasePath) {\n codebaseRoot = path.resolve(globalCodebasePath);\n } else if (manifest.codebase_path) {\n codebaseRoot = path.resolve(projectRoot, manifest.codebase_path);\n } else {\n // Try to auto-resolve from farm manifest if inside a farm\n const farmCodebaseRoot = await Model.resolveFarmCodebaseRoot(projectRoot);\n if (farmCodebaseRoot) {\n codebaseRoot = farmCodebaseRoot;\n }\n }\n }\n\n const model = new Model(projectRoot, manifest, { ...options, codebaseRoot });\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 const yaml = await import(\"yaml\");\n\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\n // Resolve codebaseRoot with priority: options > CLI flag > manifest > default\n let codebaseRoot = rootPath;\n if (options.codebaseRoot) {\n codebaseRoot = options.codebaseRoot;\n } else {\n const globalCodebasePath = getCodebasePath();\n if (globalCodebasePath) {\n codebaseRoot = path.resolve(globalCodebasePath);\n } else if (manifest.codebase_path) {\n codebaseRoot = path.resolve(rootPath, manifest.codebase_path);\n }\n }\n\n const model = new Model(rootPath, manifest, { ...options, codebaseRoot });\n\n await model.saveManifest();\n\n // Initialize relationships.yaml with empty array\n const relationshipsPath = `${rootPath}/documentation-robotics/model/relationships.yaml`;\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([]);\n await writeFile(relationshipsPath, yamlContent);\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
+ "names": ["path", "fs", "err", "layerId", "manifestPath", "projectRoot"]
7
7
  }
@@ -208,6 +208,7 @@ class VirtualProjectionEngine {
208
208
  manifest: baseModel.manifest,
209
209
  layers: projectedLayers,
210
210
  relationships: baseModel.relationships,
211
+ loadedLayerFilter: baseModel.loadedLayerFilter,
211
212
  isProjection: !0
212
213
  };
213
214
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/virtual-projection.ts"],
4
- "sourcesContent": ["/**\n * VirtualProjectionEngine - Computes merged model views by applying staged changes.\n *\n * Creates virtual (temporary) projections of the base model with staged changes applied,\n * without modifying or persisting the base model. Enables safe preview and validation\n * of changes before commit. Includes projection caching with TTL-based expiration.\n *\n * Staging changes are applied in sequence order to compute accurate merged views.\n */\n\nimport type { Model } from \"./model.js\";\nimport type { Layer } from \"./layer.js\";\nimport type { Element } from \"./element.js\";\nimport type { SourceReference, ElementMetadata } from \"../types/index.js\";\nimport type { Manifest } from \"./manifest.js\";\nimport type { Relationships } from \"./relationships.js\";\nimport { Layer as LayerClass } from \"./layer.js\";\nimport { Element as ElementClass } from \"./element.js\";\nimport type { StagedChange, Change } from \"./changeset.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\n\n/**\n * Projected model with isProjection flag to prevent accidental saves.\n * This marker interface ensures that projected models are recognized as temporary\n * and cannot be persisted to disk without explicit commit of the underlying changeset.\n */\nexport interface ProjectedModel {\n manifest: Manifest;\n layers: Map<string, Layer>;\n relationships: Relationships;\n isProjection: true; // Always true; marks this as a temporary virtual projection\n}\n\n/**\n * Model diff categorizing changeset changes by type.\n * Separates staged changes into additions (new elements), modifications (updated attributes),\n * and deletions (removed elements). Used for preview and diff display operations.\n */\nexport interface ModelDiff {\n additions: Array<{\n elementId: string;\n layerName: string;\n data: Record<string, unknown>; // Complete element state (from change.after)\n }>;\n modifications: Array<{\n elementId: string;\n layerName: string;\n before: Record<string, unknown>; // Previous state (from change.before)\n after: Record<string, unknown>; // New state (from change.after)\n }>;\n deletions: Array<{\n elementId: string;\n layerName: string;\n data: Record<string, unknown>; // Element state before deletion (from change.before)\n }>;\n}\n\n/**\n * Single cached layer projection.\n * Stores the computed projected layer and timestamp for cache expiration.\n */\ninterface CacheEntry {\n layer: Layer;\n computedAt: string; // ISO timestamp for age calculation\n}\n\n/**\n * Cache hit/miss metrics for monitoring cache performance.\n * Tracks successful cache hits, misses, and invalidations per changeset.\n */\ninterface CacheMetrics {\n hits: number; // Number of successful cache hits\n misses: number; // Number of cache misses (required computation)\n invalidations: number; // Number of invalidation operations\n lastInvalidation?: string; // ISO timestamp of last invalidation\n}\n\n/**\n * Projection cache for storing computed layer projections.\n * Uses TTL-based expiration to prevent stale projections.\n * Entries are lazily removed when expired on access or during periodic cleanup.\n */\ninterface ProjectionCache {\n cache: Map<string, CacheEntry>; // Key format: `${changesetId}:${layerName}`\n metrics: Map<string, CacheMetrics>; // Cache metrics keyed by changesetId\n maxAge: number; // Cache entry TTL in milliseconds (5 minutes default)\n lastCleanup: number; // Timestamp of last cleanup to limit cleanup frequency\n}\n\n/**\n * VirtualProjectionEngine - Computes merged model views without persisting.\n * Applies staged changes to a cloned base model to compute projections.\n * Uses caching with TTL-based expiration to optimize repeated projections.\n */\nexport class VirtualProjectionEngine {\n private stagingAreaManager: StagedChangesetStorage;\n private projectionCache: ProjectionCache;\n\n /**\n * Create a new projection engine.\n *\n * @param rootPath - Root path to the model (used by StagedChangesetStorage)\n */\n constructor(rootPath: string) {\n this.stagingAreaManager = new StagedChangesetStorage(rootPath);\n this.projectionCache = {\n cache: new Map(),\n metrics: new Map(),\n maxAge: 5 * 60 * 1000, // 5-minute TTL for cached projections\n lastCleanup: Date.now(),\n };\n }\n\n /**\n * Project a single element by applying staged changes to it.\n * Merges the element's base state with staged changes to compute its projected state.\n *\n * @param baseModel - The base model to project from\n * @param changesetId - ID of the changeset containing staged changes\n * @param elementId - ID of the element to project\n * @returns Projected element with staged changes applied, or null if element is deleted\n * @throws Error if changeset not found\n *\n * @remarks\n * If multiple changes exist for the element, the last change determines final state.\n * For 'delete' operations, returns null.\n * For 'add' operations without base element, creates new element from staged data.\n * For 'update' operations, merges staged changes with base element attributes.\n */\n async projectElement(\n baseModel: Model,\n changesetId: string,\n elementId: string\n ): Promise<Element | null> {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Find all changes for this element across all layers\n const elementChanges = changeset.changes.filter(\n (c: Change) => c.elementId === elementId\n ) as StagedChange[];\n\n if (elementChanges.length === 0) {\n // No changes, return base element if it exists\n return baseModel.getElementById(elementId) || null;\n }\n\n // Get the last change to determine the element's final state\n const lastChange = elementChanges[elementChanges.length - 1];\n\n if (lastChange.type === \"delete\") {\n return null;\n }\n\n // For add or update, construct the projected element\n const baseElement = baseModel.getElementById(elementId);\n const projectedData = lastChange.after || {};\n\n if (baseElement) {\n // Create a new element with projected data merged with base\n const projectedAttributes =\n typeof projectedData.attributes === \"object\" ? projectedData.attributes : {};\n\n const mergedData = {\n id: baseElement.id,\n path: baseElement.path,\n spec_node_id: baseElement.spec_node_id,\n type: typeof projectedData.type === \"string\" ? projectedData.type : baseElement.type,\n layer_id: baseElement.layer_id,\n name: typeof projectedData.name === \"string\" ? projectedData.name : baseElement.name,\n description:\n typeof projectedData.description === \"string\"\n ? projectedData.description\n : baseElement.description,\n attributes: {\n ...baseElement.attributes,\n ...projectedAttributes,\n },\n references: Array.isArray(projectedData.references)\n ? projectedData.references\n : baseElement.references,\n relationships: Array.isArray(projectedData.relationships)\n ? projectedData.relationships\n : baseElement.relationships,\n source_reference: baseElement.source_reference,\n metadata: baseElement.metadata,\n };\n\n return new ElementClass(mergedData);\n } else if (lastChange.type === \"add\") {\n // New element being added\n return new ElementClass({\n id: (lastChange.after?.id as string) || elementId,\n path: (lastChange.after?.path as string) || elementId,\n spec_node_id: (lastChange.after?.spec_node_id as string) || \"\",\n type: (projectedData.type as string) || \"unknown\",\n layer_id: lastChange.layerName,\n name: (projectedData.name as string) || elementId,\n description: (projectedData.description as string) || \"\",\n attributes: (typeof projectedData.attributes === \"object\"\n ? projectedData.attributes\n : {}) as Record<string, unknown>,\n references: Array.isArray(projectedData.references) ? projectedData.references : [],\n relationships: Array.isArray(projectedData.relationships)\n ? projectedData.relationships\n : [],\n source_reference: projectedData.source_reference as SourceReference | undefined,\n metadata: projectedData.metadata as ElementMetadata | undefined,\n layer: lastChange.layerName,\n });\n }\n\n return null;\n }\n\n /**\n * Project a layer by applying staged changes to a clone of the base layer.\n * Checks cache first; computes and caches result if not found or expired.\n *\n * @param baseModel - The base model to project from\n * @param changesetId - ID of the changeset containing staged changes\n * @param layerName - Name of the layer to project\n * @returns Projected layer with staged changes applied\n * @throws Error if changeset or base layer not found\n *\n * @remarks\n * Computation process:\n * 1. Check projection cache (returns if found and not expired)\n * 2. Clone base layer to avoid mutations\n * 3. Load staged changes filtered to this layer\n * 4. Apply changes in sequence number order (add/update/delete)\n * 5. Cache result with timestamp for TTL tracking\n * 6. Trigger periodic cache cleanup\n */\n async projectLayer(baseModel: Model, changesetId: string, layerName: string): Promise<Layer> {\n // Check cache first\n const cacheKey = `${changesetId}:${layerName}`;\n const cached = this.projectionCache.cache.get(cacheKey);\n\n if (cached) {\n const age = Date.now() - new Date(cached.computedAt).getTime();\n if (age < this.projectionCache.maxAge) {\n // Record cache hit\n this.recordCacheHit(changesetId);\n return cached.layer;\n } else {\n // Invalidate expired entry\n this.projectionCache.cache.delete(cacheKey);\n }\n }\n\n // Record cache miss\n this.recordCacheMiss(changesetId);\n\n // Load the committed base layer \u2014 must use getBaseLayer() (not getLayer()) to avoid\n // infinite recursion: getLayer() calls projectLayer(), which would call getLayer() again.\n const baseLayer = await baseModel.getBaseLayer(layerName);\n const projectedLayer = baseLayer\n ? this.cloneLayer(baseLayer)\n : new LayerClass(layerName);\n\n // Load staged changes for this layer\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const layerChanges = (changeset.changes as StagedChange[])\n .filter((c: StagedChange) => c.layerName === layerName)\n .sort((a: StagedChange, b: StagedChange) => a.sequenceNumber - b.sequenceNumber);\n\n // Apply changes in sequence order\n for (const change of layerChanges) {\n switch (change.type) {\n case \"add\":\n if (change.after) {\n const newElement = new ElementClass({\n id: (change.after.id as string) || change.elementId,\n path: (change.after.path as string) || change.elementId,\n spec_node_id: (change.after.spec_node_id as string) || \"\",\n type: (change.after.type as string) || \"unknown\",\n layer_id: layerName,\n name: (change.after.name as string) || change.elementId,\n description: (change.after.description as string) || \"\",\n attributes: (typeof change.after.attributes === \"object\"\n ? change.after.attributes\n : {}) as Record<string, unknown>,\n references: Array.isArray(change.after.references) ? change.after.references : [],\n relationships: Array.isArray(change.after.relationships)\n ? change.after.relationships\n : [],\n source_reference: change.after.source_reference as SourceReference | undefined,\n metadata: change.after.metadata as ElementMetadata | undefined,\n layer: layerName,\n });\n projectedLayer.addElement(newElement);\n }\n break;\n\n case \"update\":\n if (change.after) {\n // Get the existing element to preserve all state\n const existing = projectedLayer.getElement(change.elementId);\n if (existing) {\n // Create updated element with merged attributes\n const afterAttributes =\n typeof change.after.attributes === \"object\" ? change.after.attributes : {};\n\n const mergedAttributes: Record<string, unknown> = {\n ...existing.attributes,\n ...afterAttributes,\n };\n\n const updatedElement = new ElementClass({\n id: existing.id,\n path: existing.path,\n spec_node_id: existing.spec_node_id,\n layer_id: existing.layer_id,\n type: typeof change.after.type === \"string\" ? change.after.type : existing.type,\n name: typeof change.after.name === \"string\" ? change.after.name : existing.name,\n description:\n typeof change.after.description === \"string\"\n ? change.after.description\n : existing.description,\n attributes: mergedAttributes,\n references: Array.isArray(change.after.references)\n ? change.after.references\n : existing.references,\n relationships: Array.isArray(change.after.relationships)\n ? change.after.relationships\n : existing.relationships,\n layer: layerName,\n source_reference: existing.source_reference,\n metadata: existing.metadata,\n });\n\n projectedLayer.updateElement(updatedElement);\n }\n }\n break;\n\n case \"delete\":\n const existingForDelete = projectedLayer.getElement(change.elementId);\n if (existingForDelete) {\n projectedLayer.deleteElement(existingForDelete.path || existingForDelete.id);\n }\n break;\n\n case \"relationship-add\":\n case \"relationship-delete\":\n // Relationship changes are handled at the model level (model.relationships),\n // not the layer level. Skip here; no layer mutation needed.\n break;\n }\n }\n\n // Cache the result\n const cacheEntry: CacheEntry = {\n layer: projectedLayer,\n computedAt: new Date().toISOString(),\n };\n this.projectionCache.cache.set(cacheKey, cacheEntry);\n\n // Periodic cleanup of expired entries\n this.cleanupExpiredCache();\n\n return projectedLayer;\n }\n\n /**\n * Project entire model by applying staged changes to all layers.\n * Creates a complete projected model view that includes both changed and unchanged layers.\n *\n * @param baseModel - The base model to project from\n * @param changesetId - ID of the changeset containing staged changes\n * @returns ProjectedModel with isProjection=true flag (prevents accidental saves)\n * @throws Error if changeset not found\n *\n * @remarks\n * Projection includes:\n * - All layers mentioned in changeset changes\n * - All layers from base model (to have complete model state)\n * - Base model's manifest (unchanged)\n * Returned model has isProjection=true to prevent accidental persistence.\n */\n async projectModel(baseModel: Model, changesetId: string): Promise<ProjectedModel> {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const projectedLayers = new Map<string, Layer>();\n\n // Project all layers mentioned in the changeset\n const layerNames = new Set<string>();\n for (const change of changeset.changes) {\n layerNames.add(change.layerName);\n }\n\n // Also include all base layers to get complete model\n for (const baseLayerName of baseModel.getLayerNames()) {\n layerNames.add(baseLayerName);\n }\n\n // Project each layer\n for (const layerName of layerNames) {\n const projectedLayer = await this.projectLayer(baseModel, changesetId, layerName);\n projectedLayers.set(layerName, projectedLayer);\n }\n\n // Create projected model with isProjection flag\n const projectedModel: ProjectedModel = {\n manifest: baseModel.manifest,\n layers: projectedLayers,\n relationships: baseModel.relationships,\n isProjection: true,\n };\n\n return projectedModel;\n }\n\n /**\n * Compute diff by categorizing changeset changes by type.\n * Extracts the staged changes and organizes them into additions, modifications, and deletions.\n *\n * @param _baseModel - The base model (unused; included for interface compatibility)\n * @param changesetId - ID of the changeset to compute diff for\n * @returns ModelDiff with changes categorized by type\n * @throws Error if changeset not found\n *\n * @remarks\n * Diff is computed directly from changeset changes; does not perform deep comparison.\n * Each change's before/after snapshots are included for detailed display.\n */\n async computeDiff(_baseModel: Model, changesetId: string): Promise<ModelDiff> {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const changes = changeset.changes as StagedChange[];\n\n return {\n additions: changes\n .filter((c: StagedChange) => c.type === \"add\")\n .map((c: StagedChange) => ({\n elementId: c.elementId,\n layerName: c.layerName,\n data: c.after || {},\n })),\n\n modifications: changes\n .filter((c: StagedChange) => c.type === \"update\")\n .map((c: StagedChange) => ({\n elementId: c.elementId,\n layerName: c.layerName,\n before: c.before || {},\n after: c.after || {},\n })),\n\n deletions: changes\n .filter((c: StagedChange) => c.type === \"delete\")\n .map((c: StagedChange) => ({\n elementId: c.elementId,\n layerName: c.layerName,\n data: c.before || {},\n })),\n };\n }\n\n /**\n * Get affected layers for an element in a changeset.\n * Returns all layers that have cached projections and contain changes for the element.\n *\n * @param changesetId - ID of the changeset\n * @param elementId - ID of the element being unstaged\n * @returns Set of affected layer names\n *\n * @remarks\n * When unstaging an element, only the layers where that element was modified\n * need cache invalidation. This enables granular invalidation instead of\n * invalidating the entire changeset cache.\n */\n private async getAffectedLayers(changesetId: string, elementId: string): Promise<Set<string>> {\n const affectedLayers = new Set<string>();\n\n try {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n return affectedLayers;\n }\n\n // Find all layers that have changes for this element\n for (const change of changeset.changes) {\n if (change.elementId === elementId) {\n affectedLayers.add(change.layerName);\n }\n }\n } catch {\n // If changeset can't be loaded, return empty set (no invalidation needed)\n // This handles the case where changeset is deleted or corrupted\n }\n\n return affectedLayers;\n }\n\n /**\n * Record a cache hit for metrics tracking.\n *\n * @param changesetId - ID of the changeset\n */\n private recordCacheHit(changesetId: string): void {\n const metrics = this.projectionCache.metrics.get(changesetId) || {\n hits: 0,\n misses: 0,\n invalidations: 0,\n };\n metrics.hits++;\n this.projectionCache.metrics.set(changesetId, metrics);\n }\n\n /**\n * Record a cache miss for metrics tracking.\n *\n * @param changesetId - ID of the changeset\n */\n private recordCacheMiss(changesetId: string): void {\n const metrics = this.projectionCache.metrics.get(changesetId) || {\n hits: 0,\n misses: 0,\n invalidations: 0,\n };\n metrics.misses++;\n this.projectionCache.metrics.set(changesetId, metrics);\n }\n\n /**\n * Record a cache invalidation for metrics tracking.\n *\n * @param changesetId - ID of the changeset\n */\n private recordInvalidation(changesetId: string): void {\n const metrics = this.projectionCache.metrics.get(changesetId) || {\n hits: 0,\n misses: 0,\n invalidations: 0,\n };\n metrics.invalidations++;\n metrics.lastInvalidation = new Date().toISOString();\n this.projectionCache.metrics.set(changesetId, metrics);\n }\n\n /**\n * Get cache metrics for a changeset (for monitoring and debugging).\n *\n * @param changesetId - ID of the changeset\n * @returns Cache metrics or undefined if no metrics exist\n */\n getCacheMetrics(changesetId: string): CacheMetrics | undefined {\n return this.projectionCache.metrics.get(changesetId);\n }\n\n /**\n * Invalidate projection cache when changes are staged.\n * Removes cached projections to ensure fresh computation next time.\n *\n * @param changesetId - ID of the changeset being modified\n * @param layerName - Optional: specific layer to invalidate (all if omitted)\n *\n * @remarks\n * If layerName is provided, only that layer's projection is invalidated.\n * Otherwise, all projections for the changeset are invalidated.\n */\n invalidateOnStage(changesetId: string, layerName?: string): void {\n if (layerName) {\n // Invalidate specific layer projection\n const cacheKey = `${changesetId}:${layerName}`;\n this.projectionCache.cache.delete(cacheKey);\n this.recordInvalidation(changesetId);\n } else {\n // Invalidate all projections for this changeset\n for (const key of this.projectionCache.cache.keys()) {\n if (key.startsWith(`${changesetId}:`)) {\n this.projectionCache.cache.delete(key);\n }\n }\n this.recordInvalidation(changesetId);\n }\n }\n\n /**\n * Invalidate projection cache when changes are unstaged.\n * Uses layer-specific invalidation for the affected element's layers.\n *\n * @param changesetId - ID of the changeset being modified\n * @param elementId - Optional: ID of element being unstaged (enables granular invalidation)\n *\n * @remarks\n * If elementId is provided, only the layers containing that element are invalidated.\n * This is more efficient than invalidating the entire changeset cache when\n * unstaging elements from a multi-layer changeset.\n * If elementId is omitted, invalidates all projections for the changeset.\n */\n async invalidateOnUnstage(changesetId: string, elementId?: string): Promise<void> {\n if (elementId) {\n // Layer-specific invalidation: only invalidate affected layers\n const affectedLayers = await this.getAffectedLayers(changesetId, elementId);\n for (const layerName of affectedLayers) {\n const cacheKey = `${changesetId}:${layerName}`;\n this.projectionCache.cache.delete(cacheKey);\n }\n if (affectedLayers.size > 0) {\n this.recordInvalidation(changesetId);\n }\n } else {\n // Full invalidation: clear all projections for this changeset\n for (const key of this.projectionCache.cache.keys()) {\n if (key.startsWith(`${changesetId}:`)) {\n this.projectionCache.cache.delete(key);\n }\n }\n this.recordInvalidation(changesetId);\n }\n }\n\n /**\n * Invalidate all projections for a changeset when it's discarded.\n * Clears all cached projections for this changeset to free memory.\n *\n * @param changesetId - ID of the changeset being discarded\n */\n invalidateOnDiscard(changesetId: string): void {\n for (const key of this.projectionCache.cache.keys()) {\n if (key.startsWith(`${changesetId}:`)) {\n this.projectionCache.cache.delete(key);\n }\n }\n }\n\n /**\n * Clone a layer to create an independent copy.\n * Shallow clones all elements and metadata to avoid mutations.\n * Cloned layer is marked clean to prevent unintended saves.\n *\n * @param layer - The layer to clone\n * @returns New layer with cloned elements and metadata\n *\n * @remarks\n * Cloning strategy:\n * 1. Create new Layer with same name\n * 2. Clone each element: shallow copy attributes object and array references\n * 3. Copy layer metadata if present\n * 4. Mark cloned layer as clean to prevent automatic saves\n * This ensures changes to projection don't affect base layer.\n * Note: Shallow cloning is sufficient since attributes are read-only in projections.\n */\n private cloneLayer(layer: Layer): Layer {\n const cloned = new LayerClass(layer.name);\n\n // Clone all elements (shallow copy - read-only in projection)\n for (const element of layer.listElements()) {\n const elementClone = new ElementClass({\n id: element.id,\n path: element.path,\n spec_node_id: element.spec_node_id,\n type: element.type,\n layer_id: element.layer_id,\n name: element.name,\n description: element.description,\n attributes: { ...element.attributes },\n references: [...(element.references || [])],\n relationships: [...(element.relationships || [])],\n layer: element.layer,\n source_reference: element.source_reference,\n metadata: element.metadata,\n });\n cloned.addElement(elementClone);\n }\n\n // Preserve layer metadata if present\n if (layer.metadata) {\n cloned.metadata = { ...layer.metadata };\n }\n\n // Mark as clean to prevent projection from being saved\n cloned.markClean();\n return cloned;\n }\n\n /**\n * Clean up expired cache entries periodically (every 60 seconds).\n * Removes entries that exceed the maxAge TTL.\n * Runs at most once per minute to limit overhead.\n *\n * @remarks\n * Cleanup is triggered after each projection computation.\n * Early exit if last cleanup was less than 60 seconds ago.\n * Entries are marked for deletion based on age calculation from computedAt timestamp.\n */\n private cleanupExpiredCache(): void {\n const now = Date.now();\n\n // Limit cleanup frequency to at most every 60 seconds\n if (now - this.projectionCache.lastCleanup < 60 * 1000) {\n return;\n }\n\n // Remove entries that have exceeded their TTL\n for (const [key, entry] of this.projectionCache.cache.entries()) {\n const age = now - new Date(entry.computedAt).getTime();\n if (age >= this.projectionCache.maxAge) {\n this.projectionCache.cache.delete(key);\n }\n }\n\n this.projectionCache.lastCleanup = now;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * VirtualProjectionEngine - Computes merged model views by applying staged changes.\n *\n * Creates virtual (temporary) projections of the base model with staged changes applied,\n * without modifying or persisting the base model. Enables safe preview and validation\n * of changes before commit. Includes projection caching with TTL-based expiration.\n *\n * Staging changes are applied in sequence order to compute accurate merged views.\n */\n\nimport type { Model } from \"./model.js\";\nimport type { Layer } from \"./layer.js\";\nimport type { Element } from \"./element.js\";\nimport type { SourceReference, ElementMetadata } from \"../types/index.js\";\nimport type { Manifest } from \"./manifest.js\";\nimport type { Relationships } from \"./relationships.js\";\nimport { Layer as LayerClass } from \"./layer.js\";\nimport { Element as ElementClass } from \"./element.js\";\nimport type { StagedChange, Change } from \"./changeset.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\n\n/**\n * Projected model with isProjection flag to prevent accidental saves.\n * This marker interface ensures that projected models are recognized as temporary\n * and cannot be persisted to disk without explicit commit of the underlying changeset.\n */\nexport interface ProjectedModel {\n manifest: Manifest;\n layers: Map<string, Layer>;\n relationships: Relationships;\n loadedLayerFilter?: string[];\n isProjection: true; // Always true; marks this as a temporary virtual projection\n}\n\n/**\n * Model diff categorizing changeset changes by type.\n * Separates staged changes into additions (new elements), modifications (updated attributes),\n * and deletions (removed elements). Used for preview and diff display operations.\n */\nexport interface ModelDiff {\n additions: Array<{\n elementId: string;\n layerName: string;\n data: Record<string, unknown>; // Complete element state (from change.after)\n }>;\n modifications: Array<{\n elementId: string;\n layerName: string;\n before: Record<string, unknown>; // Previous state (from change.before)\n after: Record<string, unknown>; // New state (from change.after)\n }>;\n deletions: Array<{\n elementId: string;\n layerName: string;\n data: Record<string, unknown>; // Element state before deletion (from change.before)\n }>;\n}\n\n/**\n * Single cached layer projection.\n * Stores the computed projected layer and timestamp for cache expiration.\n */\ninterface CacheEntry {\n layer: Layer;\n computedAt: string; // ISO timestamp for age calculation\n}\n\n/**\n * Cache hit/miss metrics for monitoring cache performance.\n * Tracks successful cache hits, misses, and invalidations per changeset.\n */\ninterface CacheMetrics {\n hits: number; // Number of successful cache hits\n misses: number; // Number of cache misses (required computation)\n invalidations: number; // Number of invalidation operations\n lastInvalidation?: string; // ISO timestamp of last invalidation\n}\n\n/**\n * Projection cache for storing computed layer projections.\n * Uses TTL-based expiration to prevent stale projections.\n * Entries are lazily removed when expired on access or during periodic cleanup.\n */\ninterface ProjectionCache {\n cache: Map<string, CacheEntry>; // Key format: `${changesetId}:${layerName}`\n metrics: Map<string, CacheMetrics>; // Cache metrics keyed by changesetId\n maxAge: number; // Cache entry TTL in milliseconds (5 minutes default)\n lastCleanup: number; // Timestamp of last cleanup to limit cleanup frequency\n}\n\n/**\n * VirtualProjectionEngine - Computes merged model views without persisting.\n * Applies staged changes to a cloned base model to compute projections.\n * Uses caching with TTL-based expiration to optimize repeated projections.\n */\nexport class VirtualProjectionEngine {\n private stagingAreaManager: StagedChangesetStorage;\n private projectionCache: ProjectionCache;\n\n /**\n * Create a new projection engine.\n *\n * @param rootPath - Root path to the model (used by StagedChangesetStorage)\n */\n constructor(rootPath: string) {\n this.stagingAreaManager = new StagedChangesetStorage(rootPath);\n this.projectionCache = {\n cache: new Map(),\n metrics: new Map(),\n maxAge: 5 * 60 * 1000, // 5-minute TTL for cached projections\n lastCleanup: Date.now(),\n };\n }\n\n /**\n * Project a single element by applying staged changes to it.\n * Merges the element's base state with staged changes to compute its projected state.\n *\n * @param baseModel - The base model to project from\n * @param changesetId - ID of the changeset containing staged changes\n * @param elementId - ID of the element to project\n * @returns Projected element with staged changes applied, or null if element is deleted\n * @throws Error if changeset not found\n *\n * @remarks\n * If multiple changes exist for the element, the last change determines final state.\n * For 'delete' operations, returns null.\n * For 'add' operations without base element, creates new element from staged data.\n * For 'update' operations, merges staged changes with base element attributes.\n */\n async projectElement(\n baseModel: Model,\n changesetId: string,\n elementId: string\n ): Promise<Element | null> {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Find all changes for this element across all layers\n const elementChanges = changeset.changes.filter(\n (c: Change) => c.elementId === elementId\n ) as StagedChange[];\n\n if (elementChanges.length === 0) {\n // No changes, return base element if it exists\n return baseModel.getElementById(elementId) || null;\n }\n\n // Get the last change to determine the element's final state\n const lastChange = elementChanges[elementChanges.length - 1];\n\n if (lastChange.type === \"delete\") {\n return null;\n }\n\n // For add or update, construct the projected element\n const baseElement = baseModel.getElementById(elementId);\n const projectedData = lastChange.after || {};\n\n if (baseElement) {\n // Create a new element with projected data merged with base\n const projectedAttributes =\n typeof projectedData.attributes === \"object\" ? projectedData.attributes : {};\n\n const mergedData = {\n id: baseElement.id,\n path: baseElement.path,\n spec_node_id: baseElement.spec_node_id,\n type: typeof projectedData.type === \"string\" ? projectedData.type : baseElement.type,\n layer_id: baseElement.layer_id,\n name: typeof projectedData.name === \"string\" ? projectedData.name : baseElement.name,\n description:\n typeof projectedData.description === \"string\"\n ? projectedData.description\n : baseElement.description,\n attributes: {\n ...baseElement.attributes,\n ...projectedAttributes,\n },\n references: Array.isArray(projectedData.references)\n ? projectedData.references\n : baseElement.references,\n relationships: Array.isArray(projectedData.relationships)\n ? projectedData.relationships\n : baseElement.relationships,\n source_reference: baseElement.source_reference,\n metadata: baseElement.metadata,\n };\n\n return new ElementClass(mergedData);\n } else if (lastChange.type === \"add\") {\n // New element being added\n return new ElementClass({\n id: (lastChange.after?.id as string) || elementId,\n path: (lastChange.after?.path as string) || elementId,\n spec_node_id: (lastChange.after?.spec_node_id as string) || \"\",\n type: (projectedData.type as string) || \"unknown\",\n layer_id: lastChange.layerName,\n name: (projectedData.name as string) || elementId,\n description: (projectedData.description as string) || \"\",\n attributes: (typeof projectedData.attributes === \"object\"\n ? projectedData.attributes\n : {}) as Record<string, unknown>,\n references: Array.isArray(projectedData.references) ? projectedData.references : [],\n relationships: Array.isArray(projectedData.relationships)\n ? projectedData.relationships\n : [],\n source_reference: projectedData.source_reference as SourceReference | undefined,\n metadata: projectedData.metadata as ElementMetadata | undefined,\n layer: lastChange.layerName,\n });\n }\n\n return null;\n }\n\n /**\n * Project a layer by applying staged changes to a clone of the base layer.\n * Checks cache first; computes and caches result if not found or expired.\n *\n * @param baseModel - The base model to project from\n * @param changesetId - ID of the changeset containing staged changes\n * @param layerName - Name of the layer to project\n * @returns Projected layer with staged changes applied\n * @throws Error if changeset or base layer not found\n *\n * @remarks\n * Computation process:\n * 1. Check projection cache (returns if found and not expired)\n * 2. Clone base layer to avoid mutations\n * 3. Load staged changes filtered to this layer\n * 4. Apply changes in sequence number order (add/update/delete)\n * 5. Cache result with timestamp for TTL tracking\n * 6. Trigger periodic cache cleanup\n */\n async projectLayer(baseModel: Model, changesetId: string, layerName: string): Promise<Layer> {\n // Check cache first\n const cacheKey = `${changesetId}:${layerName}`;\n const cached = this.projectionCache.cache.get(cacheKey);\n\n if (cached) {\n const age = Date.now() - new Date(cached.computedAt).getTime();\n if (age < this.projectionCache.maxAge) {\n // Record cache hit\n this.recordCacheHit(changesetId);\n return cached.layer;\n } else {\n // Invalidate expired entry\n this.projectionCache.cache.delete(cacheKey);\n }\n }\n\n // Record cache miss\n this.recordCacheMiss(changesetId);\n\n // Load the committed base layer \u2014 must use getBaseLayer() (not getLayer()) to avoid\n // infinite recursion: getLayer() calls projectLayer(), which would call getLayer() again.\n const baseLayer = await baseModel.getBaseLayer(layerName);\n const projectedLayer = baseLayer\n ? this.cloneLayer(baseLayer)\n : new LayerClass(layerName);\n\n // Load staged changes for this layer\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const layerChanges = (changeset.changes as StagedChange[])\n .filter((c: StagedChange) => c.layerName === layerName)\n .sort((a: StagedChange, b: StagedChange) => a.sequenceNumber - b.sequenceNumber);\n\n // Apply changes in sequence order\n for (const change of layerChanges) {\n switch (change.type) {\n case \"add\":\n if (change.after) {\n const newElement = new ElementClass({\n id: (change.after.id as string) || change.elementId,\n path: (change.after.path as string) || change.elementId,\n spec_node_id: (change.after.spec_node_id as string) || \"\",\n type: (change.after.type as string) || \"unknown\",\n layer_id: layerName,\n name: (change.after.name as string) || change.elementId,\n description: (change.after.description as string) || \"\",\n attributes: (typeof change.after.attributes === \"object\"\n ? change.after.attributes\n : {}) as Record<string, unknown>,\n references: Array.isArray(change.after.references) ? change.after.references : [],\n relationships: Array.isArray(change.after.relationships)\n ? change.after.relationships\n : [],\n source_reference: change.after.source_reference as SourceReference | undefined,\n metadata: change.after.metadata as ElementMetadata | undefined,\n layer: layerName,\n });\n projectedLayer.addElement(newElement);\n }\n break;\n\n case \"update\":\n if (change.after) {\n // Get the existing element to preserve all state\n const existing = projectedLayer.getElement(change.elementId);\n if (existing) {\n // Create updated element with merged attributes\n const afterAttributes =\n typeof change.after.attributes === \"object\" ? change.after.attributes : {};\n\n const mergedAttributes: Record<string, unknown> = {\n ...existing.attributes,\n ...afterAttributes,\n };\n\n const updatedElement = new ElementClass({\n id: existing.id,\n path: existing.path,\n spec_node_id: existing.spec_node_id,\n layer_id: existing.layer_id,\n type: typeof change.after.type === \"string\" ? change.after.type : existing.type,\n name: typeof change.after.name === \"string\" ? change.after.name : existing.name,\n description:\n typeof change.after.description === \"string\"\n ? change.after.description\n : existing.description,\n attributes: mergedAttributes,\n references: Array.isArray(change.after.references)\n ? change.after.references\n : existing.references,\n relationships: Array.isArray(change.after.relationships)\n ? change.after.relationships\n : existing.relationships,\n layer: layerName,\n source_reference: existing.source_reference,\n metadata: existing.metadata,\n });\n\n projectedLayer.updateElement(updatedElement);\n }\n }\n break;\n\n case \"delete\":\n const existingForDelete = projectedLayer.getElement(change.elementId);\n if (existingForDelete) {\n projectedLayer.deleteElement(existingForDelete.path || existingForDelete.id);\n }\n break;\n\n case \"relationship-add\":\n case \"relationship-delete\":\n // Relationship changes are handled at the model level (model.relationships),\n // not the layer level. Skip here; no layer mutation needed.\n break;\n }\n }\n\n // Cache the result\n const cacheEntry: CacheEntry = {\n layer: projectedLayer,\n computedAt: new Date().toISOString(),\n };\n this.projectionCache.cache.set(cacheKey, cacheEntry);\n\n // Periodic cleanup of expired entries\n this.cleanupExpiredCache();\n\n return projectedLayer;\n }\n\n /**\n * Project entire model by applying staged changes to all layers.\n * Creates a complete projected model view that includes both changed and unchanged layers.\n *\n * @param baseModel - The base model to project from\n * @param changesetId - ID of the changeset containing staged changes\n * @returns ProjectedModel with isProjection=true flag (prevents accidental saves)\n * @throws Error if changeset not found\n *\n * @remarks\n * Projection includes:\n * - All layers mentioned in changeset changes\n * - All layers from base model (to have complete model state)\n * - Base model's manifest (unchanged)\n * Returned model has isProjection=true to prevent accidental persistence.\n */\n async projectModel(baseModel: Model, changesetId: string): Promise<ProjectedModel> {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const projectedLayers = new Map<string, Layer>();\n\n // Project all layers mentioned in the changeset\n const layerNames = new Set<string>();\n for (const change of changeset.changes) {\n layerNames.add(change.layerName);\n }\n\n // Also include all base layers to get complete model\n for (const baseLayerName of baseModel.getLayerNames()) {\n layerNames.add(baseLayerName);\n }\n\n // Project each layer\n for (const layerName of layerNames) {\n const projectedLayer = await this.projectLayer(baseModel, changesetId, layerName);\n projectedLayers.set(layerName, projectedLayer);\n }\n\n // Create projected model with isProjection flag\n const projectedModel: ProjectedModel = {\n manifest: baseModel.manifest,\n layers: projectedLayers,\n relationships: baseModel.relationships,\n loadedLayerFilter: baseModel.loadedLayerFilter,\n isProjection: true,\n };\n\n return projectedModel;\n }\n\n /**\n * Compute diff by categorizing changeset changes by type.\n * Extracts the staged changes and organizes them into additions, modifications, and deletions.\n *\n * @param _baseModel - The base model (unused; included for interface compatibility)\n * @param changesetId - ID of the changeset to compute diff for\n * @returns ModelDiff with changes categorized by type\n * @throws Error if changeset not found\n *\n * @remarks\n * Diff is computed directly from changeset changes; does not perform deep comparison.\n * Each change's before/after snapshots are included for detailed display.\n */\n async computeDiff(_baseModel: Model, changesetId: string): Promise<ModelDiff> {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const changes = changeset.changes as StagedChange[];\n\n return {\n additions: changes\n .filter((c: StagedChange) => c.type === \"add\")\n .map((c: StagedChange) => ({\n elementId: c.elementId,\n layerName: c.layerName,\n data: c.after || {},\n })),\n\n modifications: changes\n .filter((c: StagedChange) => c.type === \"update\")\n .map((c: StagedChange) => ({\n elementId: c.elementId,\n layerName: c.layerName,\n before: c.before || {},\n after: c.after || {},\n })),\n\n deletions: changes\n .filter((c: StagedChange) => c.type === \"delete\")\n .map((c: StagedChange) => ({\n elementId: c.elementId,\n layerName: c.layerName,\n data: c.before || {},\n })),\n };\n }\n\n /**\n * Get affected layers for an element in a changeset.\n * Returns all layers that have cached projections and contain changes for the element.\n *\n * @param changesetId - ID of the changeset\n * @param elementId - ID of the element being unstaged\n * @returns Set of affected layer names\n *\n * @remarks\n * When unstaging an element, only the layers where that element was modified\n * need cache invalidation. This enables granular invalidation instead of\n * invalidating the entire changeset cache.\n */\n private async getAffectedLayers(changesetId: string, elementId: string): Promise<Set<string>> {\n const affectedLayers = new Set<string>();\n\n try {\n const changeset = await this.stagingAreaManager.load(changesetId);\n if (!changeset) {\n return affectedLayers;\n }\n\n // Find all layers that have changes for this element\n for (const change of changeset.changes) {\n if (change.elementId === elementId) {\n affectedLayers.add(change.layerName);\n }\n }\n } catch {\n // If changeset can't be loaded, return empty set (no invalidation needed)\n // This handles the case where changeset is deleted or corrupted\n }\n\n return affectedLayers;\n }\n\n /**\n * Record a cache hit for metrics tracking.\n *\n * @param changesetId - ID of the changeset\n */\n private recordCacheHit(changesetId: string): void {\n const metrics = this.projectionCache.metrics.get(changesetId) || {\n hits: 0,\n misses: 0,\n invalidations: 0,\n };\n metrics.hits++;\n this.projectionCache.metrics.set(changesetId, metrics);\n }\n\n /**\n * Record a cache miss for metrics tracking.\n *\n * @param changesetId - ID of the changeset\n */\n private recordCacheMiss(changesetId: string): void {\n const metrics = this.projectionCache.metrics.get(changesetId) || {\n hits: 0,\n misses: 0,\n invalidations: 0,\n };\n metrics.misses++;\n this.projectionCache.metrics.set(changesetId, metrics);\n }\n\n /**\n * Record a cache invalidation for metrics tracking.\n *\n * @param changesetId - ID of the changeset\n */\n private recordInvalidation(changesetId: string): void {\n const metrics = this.projectionCache.metrics.get(changesetId) || {\n hits: 0,\n misses: 0,\n invalidations: 0,\n };\n metrics.invalidations++;\n metrics.lastInvalidation = new Date().toISOString();\n this.projectionCache.metrics.set(changesetId, metrics);\n }\n\n /**\n * Get cache metrics for a changeset (for monitoring and debugging).\n *\n * @param changesetId - ID of the changeset\n * @returns Cache metrics or undefined if no metrics exist\n */\n getCacheMetrics(changesetId: string): CacheMetrics | undefined {\n return this.projectionCache.metrics.get(changesetId);\n }\n\n /**\n * Invalidate projection cache when changes are staged.\n * Removes cached projections to ensure fresh computation next time.\n *\n * @param changesetId - ID of the changeset being modified\n * @param layerName - Optional: specific layer to invalidate (all if omitted)\n *\n * @remarks\n * If layerName is provided, only that layer's projection is invalidated.\n * Otherwise, all projections for the changeset are invalidated.\n */\n invalidateOnStage(changesetId: string, layerName?: string): void {\n if (layerName) {\n // Invalidate specific layer projection\n const cacheKey = `${changesetId}:${layerName}`;\n this.projectionCache.cache.delete(cacheKey);\n this.recordInvalidation(changesetId);\n } else {\n // Invalidate all projections for this changeset\n for (const key of this.projectionCache.cache.keys()) {\n if (key.startsWith(`${changesetId}:`)) {\n this.projectionCache.cache.delete(key);\n }\n }\n this.recordInvalidation(changesetId);\n }\n }\n\n /**\n * Invalidate projection cache when changes are unstaged.\n * Uses layer-specific invalidation for the affected element's layers.\n *\n * @param changesetId - ID of the changeset being modified\n * @param elementId - Optional: ID of element being unstaged (enables granular invalidation)\n *\n * @remarks\n * If elementId is provided, only the layers containing that element are invalidated.\n * This is more efficient than invalidating the entire changeset cache when\n * unstaging elements from a multi-layer changeset.\n * If elementId is omitted, invalidates all projections for the changeset.\n */\n async invalidateOnUnstage(changesetId: string, elementId?: string): Promise<void> {\n if (elementId) {\n // Layer-specific invalidation: only invalidate affected layers\n const affectedLayers = await this.getAffectedLayers(changesetId, elementId);\n for (const layerName of affectedLayers) {\n const cacheKey = `${changesetId}:${layerName}`;\n this.projectionCache.cache.delete(cacheKey);\n }\n if (affectedLayers.size > 0) {\n this.recordInvalidation(changesetId);\n }\n } else {\n // Full invalidation: clear all projections for this changeset\n for (const key of this.projectionCache.cache.keys()) {\n if (key.startsWith(`${changesetId}:`)) {\n this.projectionCache.cache.delete(key);\n }\n }\n this.recordInvalidation(changesetId);\n }\n }\n\n /**\n * Invalidate all projections for a changeset when it's discarded.\n * Clears all cached projections for this changeset to free memory.\n *\n * @param changesetId - ID of the changeset being discarded\n */\n invalidateOnDiscard(changesetId: string): void {\n for (const key of this.projectionCache.cache.keys()) {\n if (key.startsWith(`${changesetId}:`)) {\n this.projectionCache.cache.delete(key);\n }\n }\n }\n\n /**\n * Clone a layer to create an independent copy.\n * Shallow clones all elements and metadata to avoid mutations.\n * Cloned layer is marked clean to prevent unintended saves.\n *\n * @param layer - The layer to clone\n * @returns New layer with cloned elements and metadata\n *\n * @remarks\n * Cloning strategy:\n * 1. Create new Layer with same name\n * 2. Clone each element: shallow copy attributes object and array references\n * 3. Copy layer metadata if present\n * 4. Mark cloned layer as clean to prevent automatic saves\n * This ensures changes to projection don't affect base layer.\n * Note: Shallow cloning is sufficient since attributes are read-only in projections.\n */\n private cloneLayer(layer: Layer): Layer {\n const cloned = new LayerClass(layer.name);\n\n // Clone all elements (shallow copy - read-only in projection)\n for (const element of layer.listElements()) {\n const elementClone = new ElementClass({\n id: element.id,\n path: element.path,\n spec_node_id: element.spec_node_id,\n type: element.type,\n layer_id: element.layer_id,\n name: element.name,\n description: element.description,\n attributes: { ...element.attributes },\n references: [...(element.references || [])],\n relationships: [...(element.relationships || [])],\n layer: element.layer,\n source_reference: element.source_reference,\n metadata: element.metadata,\n });\n cloned.addElement(elementClone);\n }\n\n // Preserve layer metadata if present\n if (layer.metadata) {\n cloned.metadata = { ...layer.metadata };\n }\n\n // Mark as clean to prevent projection from being saved\n cloned.markClean();\n return cloned;\n }\n\n /**\n * Clean up expired cache entries periodically (every 60 seconds).\n * Removes entries that exceed the maxAge TTL.\n * Runs at most once per minute to limit overhead.\n *\n * @remarks\n * Cleanup is triggered after each projection computation.\n * Early exit if last cleanup was less than 60 seconds ago.\n * Entries are marked for deletion based on age calculation from computedAt timestamp.\n */\n private cleanupExpiredCache(): void {\n const now = Date.now();\n\n // Limit cleanup frequency to at most every 60 seconds\n if (now - this.projectionCache.lastCleanup < 60 * 1000) {\n return;\n }\n\n // Remove entries that have exceeded their TTL\n for (const [key, entry] of this.projectionCache.cache.entries()) {\n const age = now - new Date(entry.computedAt).getTime();\n if (age >= this.projectionCache.maxAge) {\n this.projectionCache.cache.delete(key);\n }\n }\n\n this.projectionCache.lastCleanup = now;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -877,7 +877,7 @@ const NODE_TYPE_MOTIVATION_ASSESSMENT = {
877
877
  title: "ObjectSchema",
878
878
  requiredAttributes: ["type"],
879
879
  optionalAttributes: ["properties", "patternProperties", "propertyNames", "required", "additionalProperties", "minProperties", "maxProperties"],
880
- attributeConstraints: { type: { type: "string" }, properties: { type: "object", description: "Named property definitions for the object schema (primary mechanism for defining object structure)" }, patternProperties: { type: "string", description: "Pattern-matched property definitions applied to properties whose names match the given regex" }, propertyNames: { type: "string", description: "Schema applied to all property names of the object instance for name validation" }, required: { type: "string" }, additionalProperties: { type: "string" }, minProperties: { type: "integer" }, maxProperties: { type: "integer" } }
880
+ attributeConstraints: { type: { type: "string" }, properties: { type: "object", description: "Named property definitions for the object schema (primary mechanism for defining object structure)" }, patternProperties: { type: "string", description: "Pattern-matched property definitions applied to properties whose names match the given regex" }, propertyNames: { type: "string", description: "Schema applied to all property names of the object instance for name validation" }, required: { type: "array", items: { type: "string" } }, additionalProperties: { type: "string" }, minProperties: { type: "integer" }, maxProperties: { type: "integer" } }
881
881
  }, NODE_TYPE_DATA_MODEL_REFERENCE = {
882
882
  specNodeId: "data-model.reference",
883
883
  layer: "data-model",