@documentation-robotics/cli 0.1.8 → 0.1.10

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 (120) hide show
  1. package/README.md +18 -17
  2. package/dist/audit/nodes/ai/prompts.js +1 -0
  3. package/dist/audit/nodes/ai/prompts.js.map +2 -2
  4. package/dist/audit/relationships/ai/prompts.js +20 -29
  5. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  6. package/dist/audit/relationships/analysis/gap-analyzer.js.map +1 -1
  7. package/dist/audit/relationships/analysis/layer-templates.js +36 -0
  8. package/dist/audit/relationships/analysis/layer-templates.js.map +2 -2
  9. package/dist/cli.js +8 -8
  10. package/dist/cli.js.map +2 -2
  11. package/dist/commands/reports.js +3 -3
  12. package/dist/commands/reports.js.map +1 -1
  13. package/dist/commands/show.js.map +1 -1
  14. package/dist/commands/upgrade.js +1 -1
  15. package/dist/commands/upgrade.js.map +1 -1
  16. package/dist/commands/validate.js.map +1 -1
  17. package/dist/commands/version.js +1 -1
  18. package/dist/core/layers.js +1 -0
  19. package/dist/core/layers.js.map +2 -2
  20. package/dist/core/migration-registry.js +59 -2
  21. package/dist/core/migration-registry.js.map +2 -2
  22. package/dist/core/model.js +3 -16
  23. package/dist/core/model.js.map +2 -2
  24. package/dist/core/spec-loader.js.map +1 -1
  25. package/dist/core/stats-collector.js +2 -0
  26. package/dist/core/stats-collector.js.map +2 -2
  27. package/dist/export/archimate-exporter.js.map +2 -2
  28. package/dist/export/json-schema-exporter.js.map +1 -1
  29. package/dist/export/markdown-exporter.js.map +1 -1
  30. package/dist/export/markdown-utils.js +1 -0
  31. package/dist/export/markdown-utils.js.map +2 -2
  32. package/dist/export/mermaid-markdown-exporter.js.map +1 -1
  33. package/dist/export/openapi-exporter.js.map +1 -1
  34. package/dist/export/plantuml-exporter.js.map +1 -1
  35. package/dist/export/report-formatters.js +2 -2
  36. package/dist/export/report-formatters.js.map +1 -1
  37. package/dist/export/types.js +1 -0
  38. package/dist/export/types.js.map +2 -2
  39. package/dist/generated/layer-registry.js +44 -31
  40. package/dist/generated/layer-registry.js.map +2 -2
  41. package/dist/generated/layer-types.js +1 -1
  42. package/dist/generated/layer-types.js.map +2 -2
  43. package/dist/generated/node-types.js +45 -0
  44. package/dist/generated/node-types.js.map +2 -2
  45. package/dist/generated/relationship-index.js +201 -19
  46. package/dist/generated/relationship-index.js.map +2 -2
  47. package/dist/import/archimate-importer.js.map +1 -1
  48. package/dist/import/openapi-importer.js.map +1 -1
  49. package/dist/integrations/claude_code/agents/dr-advisor.md +14 -13
  50. package/dist/integrations/claude_code/agents/dr-extractor.md +2 -2
  51. package/dist/integrations/claude_code/commands/dr-info.md +1 -1
  52. package/dist/integrations/claude_code/commands/dr-init.md +3 -3
  53. package/dist/integrations/claude_code/commands/dr-map.md +2 -2
  54. package/dist/integrations/claude_code/commands/dr-model.md +1 -1
  55. package/dist/integrations/claude_code/commands/dr-relate.md +14 -4
  56. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +3 -3
  57. package/dist/integrations/claude_code/docs/QUICK_START.md +1 -1
  58. package/dist/integrations/claude_code/docs/README.md +2 -2
  59. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +2 -2
  60. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +679 -0
  61. package/dist/integrations/claude_code/skills/{dr_08_dataset_layer → dr_09_dataset_layer}/SKILL.md +1 -1
  62. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +2 -2
  63. package/dist/integrations/github_copilot/agents/dr-advisor.md +14 -13
  64. package/dist/integrations/github_copilot/agents/dr-architect.md +15 -14
  65. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +2 -2
  66. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +253 -0
  67. package/dist/reports/model-report-orchestrator.js +3 -3
  68. package/dist/reports/model-report-orchestrator.js.map +1 -1
  69. package/dist/schemas/bundled/analyzers/cbm.json +2 -2
  70. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  71. package/dist/schemas/bundled/api.json +3 -3
  72. package/dist/schemas/bundled/apm.json +3 -3
  73. package/dist/schemas/bundled/application.json +3 -3
  74. package/dist/schemas/bundled/base.json +29 -1
  75. package/dist/schemas/bundled/business.json +1 -1
  76. package/dist/schemas/bundled/data-model.json +3 -3
  77. package/dist/schemas/bundled/data-store.json +3 -3
  78. package/dist/schemas/bundled/manifest.json +20 -13
  79. package/dist/schemas/bundled/motivation.json +1 -1
  80. package/dist/schemas/bundled/navigation.json +3 -3
  81. package/dist/schemas/bundled/product.json +469 -0
  82. package/dist/schemas/bundled/security.json +3 -3
  83. package/dist/schemas/bundled/technology.json +3 -3
  84. package/dist/schemas/bundled/testing.json +3 -3
  85. package/dist/schemas/bundled/ux.json +3 -3
  86. package/dist/server/server.js +4 -15
  87. package/dist/server/server.js.map +2 -2
  88. package/dist/telemetry/index.js +1 -1
  89. package/dist/utils/spec-installer.js +2 -2
  90. package/dist/utils/spec-installer.js.map +1 -1
  91. package/dist/utils/spec-version.js +1 -1
  92. package/dist/utils/spec-version.js.map +2 -2
  93. package/dist/viewer/README.md +6 -6
  94. package/dist/viewer/assets/main-BRPZyc36.css +1 -0
  95. package/dist/viewer/assets/main-BrpLargo.js +81 -0
  96. package/dist/viewer/index.html +2 -2
  97. package/dist/viewer/manifest.json +10 -10
  98. package/dist/viewer/version.json +1 -1
  99. package/package.json +1 -1
  100. package/dist/viewer/assets/main-CY0Xufuj.css +0 -1
  101. package/dist/viewer/assets/main-DtgErnwF.js +0 -81
  102. /package/dist/integrations/claude_code/skills/{dr_03_security_layer → dr_04_security_layer}/SKILL.md +0 -0
  103. /package/dist/integrations/claude_code/skills/{dr_04_application_layer → dr_05_application_layer}/SKILL.md +0 -0
  104. /package/dist/integrations/claude_code/skills/{dr_05_technology_layer → dr_06_technology_layer}/SKILL.md +0 -0
  105. /package/dist/integrations/claude_code/skills/{dr_06_api_layer → dr_07_api_layer}/SKILL.md +0 -0
  106. /package/dist/integrations/claude_code/skills/{dr_07_data_model_layer → dr_08_data_model_layer}/SKILL.md +0 -0
  107. /package/dist/integrations/claude_code/skills/{dr_09_ux_layer → dr_10_ux_layer}/SKILL.md +0 -0
  108. /package/dist/integrations/claude_code/skills/{dr_10_navigation_layer → dr_11_navigation_layer}/SKILL.md +0 -0
  109. /package/dist/integrations/claude_code/skills/{dr_11_apm_layer → dr_12_apm_layer}/SKILL.md +0 -0
  110. /package/dist/integrations/claude_code/skills/{dr_12_testing_layer → dr_13_testing_layer}/SKILL.md +0 -0
  111. /package/dist/integrations/github_copilot/skills/{dr_03_security_layer → dr_04_security_layer}/SKILL.md +0 -0
  112. /package/dist/integrations/github_copilot/skills/{dr_04_application_layer → dr_05_application_layer}/SKILL.md +0 -0
  113. /package/dist/integrations/github_copilot/skills/{dr_05_technology_layer → dr_06_technology_layer}/SKILL.md +0 -0
  114. /package/dist/integrations/github_copilot/skills/{dr_06_api_layer → dr_07_api_layer}/SKILL.md +0 -0
  115. /package/dist/integrations/github_copilot/skills/{dr_07_data_model_layer → dr_08_data_model_layer}/SKILL.md +0 -0
  116. /package/dist/integrations/github_copilot/skills/{dr_08_dataset_layer → dr_09_dataset_layer}/SKILL.md +0 -0
  117. /package/dist/integrations/github_copilot/skills/{dr_09_ux_layer → dr_10_ux_layer}/SKILL.md +0 -0
  118. /package/dist/integrations/github_copilot/skills/{dr_10_navigation_layer → dr_11_navigation_layer}/SKILL.md +0 -0
  119. /package/dist/integrations/github_copilot/skills/{dr_11_apm_layer → dr_12_apm_layer}/SKILL.md +0 -0
  120. /package/dist/integrations/github_copilot/skills/{dr_12_testing_layer → dr_13_testing_layer}/SKILL.md +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/validate.ts"],
4
- "sourcesContent": ["/**\n * Validate the architecture model\n */\n\nimport ansis from \"ansis\";\nimport { promises as fs } from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { Model } from \"../core/model.js\";\nimport { Validator } from \"../validators/validator.js\";\nimport { ValidationFormatter } from \"../validators/validation-formatter.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { RELATIONSHIPS_BY_SOURCE, RELATIONSHIPS_BY_DESTINATION } from \"../generated/relationship-index.js\";\nimport { getActiveSpan } from \"../telemetry/index.js\";\n\n\nexport interface ValidateOptions {\n layers?: string[];\n strict?: boolean;\n verbose?: boolean;\n quiet?: boolean;\n output?: string;\n debug?: boolean;\n model?: string;\n orphans?: boolean;\n // Python CLI compatibility options\n all?: boolean;\n markdown?: boolean;\n schemas?: boolean;\n schema?: boolean; // Alias for schemas\n relationships?: boolean;\n structure?: boolean;\n naming?: boolean;\n references?: boolean;\n}\n\n/**\n * Validate schema synchronization between spec/ and cli/src/schemas/bundled/\n * @throws {Error} if schema synchronization validation fails\n */\n/**\n * Recursively find all JSON schema files in a directory\n * Excludes 'layers' subdirectory since layer instances are not schemas\n */\nasync function findJsonFiles(dir: string, baseDir: string = dir): Promise<string[]> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n // Skip 'layers' directory - layer instances are synced but are not schemas\n if (entry.isDirectory() && entry.name !== \"layers\") {\n const subFiles = await findJsonFiles(fullPath, baseDir);\n files.push(...subFiles);\n } else if (entry.name.endsWith(\".json\") && entry.isFile()) {\n // Store relative path from base directory\n files.push(path.relative(baseDir, fullPath));\n }\n }\n\n return files;\n}\n\nasync function validateSchemaSynchronization(): Promise<void> {\n console.log(\"\");\n console.log(ansis.bold(\"Validating schema synchronization...\"));\n console.log(\"\");\n\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n // Resolve paths relative to project root\n const projectRoot = path.resolve(__dirname, \"../../..\");\n // Compare spec/dist/ (14 compiled flat files) vs dist/schemas/bundled/ (same 14 files)\n // spec/schemas/ contains individual source files; spec/dist/ is the compiled distribution\n const specSchemaDir = path.join(projectRoot, \"spec\", \"dist\");\n const bundledSchemaDir = path.join(__dirname, \"../schemas/bundled\");\n\n // Find all JSON files recursively\n const specSchemaFiles = await findJsonFiles(specSchemaDir);\n const bundledSchemaFiles = await findJsonFiles(bundledSchemaDir);\n\n if (specSchemaFiles.length === 0) {\n throw new Error(\"No schema files found in spec/schemas/\");\n }\n\n let mismatches: string[] = [];\n\n for (const schemaFile of specSchemaFiles) {\n const specPath = path.join(specSchemaDir, schemaFile);\n const bundledPath = path.join(bundledSchemaDir, schemaFile);\n\n try {\n const specContent = await fs.readFile(specPath, \"utf-8\");\n\n try {\n const bundledContent = await fs.readFile(bundledPath, \"utf-8\");\n\n // Normalize JSON to account for formatting differences\n const specParsed = JSON.parse(specContent);\n const bundledParsed = JSON.parse(bundledContent);\n const specNormalized = JSON.stringify(specParsed, null, 2);\n const bundledNormalized = JSON.stringify(bundledParsed, null, 2);\n\n if (specNormalized !== bundledNormalized) {\n mismatches.push(\n ` ${ansis.red(\"\u2717\")} ${schemaFile} - Content differs between spec/ and cli/src/schemas/bundled/`\n );\n } else {\n console.log(` ${ansis.green(\"\u2713\")} ${schemaFile}`);\n }\n } catch (error) {\n if ((error as any).code === \"ENOENT\") {\n mismatches.push(\n ` ${ansis.red(\"\u2717\")} ${schemaFile} - Missing in cli/src/schemas/bundled/`\n );\n } else {\n throw error;\n }\n }\n } catch (error) {\n const message = getErrorMessage(error);\n mismatches.push(` ${ansis.red(\"\u2717\")} ${schemaFile} - Error reading spec schema: ${message}`);\n }\n }\n\n // Check for extra files in bundled that aren't in spec\n for (const bundledFile of bundledSchemaFiles) {\n if (!specSchemaFiles.includes(bundledFile)) {\n mismatches.push(\n ` ${ansis.red(\"\u2717\")} ${bundledFile} - Extra file in cli/src/schemas/bundled/ (not in spec/)`\n );\n }\n }\n\n if (mismatches.length > 0) {\n console.log(\"\");\n console.log(\n ansis.bold(ansis.red(`\u2717 Schema synchronization failed with ${mismatches.length} issue(s):`))\n );\n console.log(\"\");\n for (const mismatch of mismatches) {\n console.log(mismatch);\n }\n console.log(\"\");\n console.log(\n ansis.dim(\"To fix: Run 'npm run build:spec' at repo root, then 'npm run build' in cli/ to sync spec/dist/ \u2192 cli/src/schemas/bundled/\")\n );\n console.log(\"\");\n throw new Error(`Schema synchronization failed with ${mismatches.length} issue(s)`);\n } else {\n console.log(\"\");\n console.log(ansis.green(\"\u2713 All schemas synchronized\"));\n console.log(\"\");\n }\n}\n\n/**\n * Display structured orphan report grouped by layer, with dead-end detection.\n * For each orphan, checks whether valid relationship targets exist in the model.\n */\nasync function validateOrphansOnly(model: Model, outputPath?: string): Promise<void> {\n const stats = ValidationFormatter.calculateStats(model);\n\n if (stats.orphanedElements.length === 0) {\n const msg = \"No orphaned elements found. All elements are connected.\";\n if (outputPath) {\n await fs.writeFile(outputPath, JSON.stringify({ total: 0, byLayer: {} }, null, 2), \"utf-8\");\n console.log(`\\nOrphan report exported to ${outputPath}`);\n } else {\n console.log(ansis.green(`\\n\u2713 ${msg}`));\n }\n return;\n }\n\n // Build set of element types currently present in the model\n const typesInModel = new Set<string>();\n for (const [, layer] of model.layers) {\n for (const element of layer.listElements()) {\n const id = element.path || element.id;\n const parts = id.split(\".\");\n if (parts.length >= 2) typesInModel.add(`${parts[0]}.${parts[1]}`);\n }\n }\n\n // Group orphans by layer, annotated with dead-end status\n const byLayer = new Map<string, Array<{\n id: string;\n type: string;\n status: \"dead-end\" | \"connectable\" | \"no-schema\";\n reachablePredicates: string[];\n reachableTargetTypes: string[];\n needsTypes: string[];\n }>>();\n\n for (const elementId of stats.orphanedElements.sort()) {\n const parts = elementId.split(\".\");\n const layer = parts[0] ?? \"unknown\";\n const specNodeType = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : elementId;\n\n // Outgoing: this element can point to others\n const outgoingRels = RELATIONSHIPS_BY_SOURCE.get(specNodeType) ?? [];\n const allDestTypes = [...new Set(outgoingRels.map(r => r.destinationSpecNodeId))];\n const reachableDestTypes = allDestTypes.filter(t => typesInModel.has(t));\n const reachablePredicates = [...new Set(\n outgoingRels.filter(r => reachableDestTypes.includes(r.destinationSpecNodeId)).map(r => r.predicate)\n )];\n\n // Incoming: other element types in the model can point to this element\n const incomingRels = RELATIONSHIPS_BY_DESTINATION.get(specNodeType) ?? [];\n const reachableIncomingSourceTypes = [...new Set(incomingRels.map(r => r.sourceSpecNodeId))]\n .filter(t => typesInModel.has(t));\n\n const hasOutgoingTargets = reachableDestTypes.length > 0;\n const hasIncomingSources = reachableIncomingSourceTypes.length > 0;\n const hasAnySchemas = outgoingRels.length > 0 || incomingRels.length > 0;\n\n let status: \"dead-end\" | \"connectable\" | \"no-schema\";\n if (!hasAnySchemas) {\n status = \"no-schema\";\n } else if (!hasOutgoingTargets && !hasIncomingSources) {\n status = \"dead-end\";\n } else {\n status = \"connectable\";\n }\n\n if (!byLayer.has(layer)) byLayer.set(layer, []);\n byLayer.get(layer)!.push({\n id: elementId,\n type: specNodeType,\n status,\n reachablePredicates,\n reachableTargetTypes: reachableDestTypes,\n needsTypes: allDestTypes,\n });\n }\n\n if (outputPath) {\n const jsonOut: Record<string, unknown> = {\n total: stats.orphanedElements.length,\n byLayer: Object.fromEntries(\n [...byLayer.entries()].map(([layer, items]) => [layer, items])\n ),\n };\n await fs.writeFile(outputPath, JSON.stringify(jsonOut, null, 2), \"utf-8\");\n console.log(`\\nOrphan report exported to ${outputPath}`);\n return;\n }\n\n console.log(ansis.bold(`\\nOrphaned Elements (${stats.orphanedElements.length})`));\n console.log(ansis.dim(\"Elements with no intra-layer relationships and no cross-layer references\\n\"));\n\n let deadEndCount = 0;\n let connectableCount = 0;\n\n for (const [layerName, items] of byLayer) {\n console.log(ansis.bold(ansis.cyan(` ${layerName} (${items.length})`)));\n for (const item of items) {\n if (item.status === \"no-schema\") {\n console.log(` ${ansis.dim(\"\u25CB\")} ${item.id}`);\n console.log(` ${ansis.dim(\"No outgoing relationship schemas defined for type \" + item.type)}`);\n } else if (item.status === \"dead-end\") {\n deadEndCount++;\n const needsPreview = item.needsTypes.slice(0, 4).join(\", \") +\n (item.needsTypes.length > 4 ? ` +${item.needsTypes.length - 4} more` : \"\");\n console.log(` ${ansis.red(\"\u2717\")} ${item.id} ${ansis.dim(\"(dead-end)\")}`);\n console.log(` ${ansis.dim(\"Needs: \" + needsPreview)}`);\n } else {\n connectableCount++;\n const predsPreview = item.reachablePredicates.slice(0, 3).join(\", \");\n const targetsPreview = item.reachableTargetTypes.slice(0, 3).join(\", \");\n console.log(` ${ansis.yellow(\"\u25CB\")} ${item.id}`);\n console.log(` ${ansis.dim(`\u2192 try: ${predsPreview} \u2192 ${targetsPreview}`)}`);\n }\n }\n console.log();\n }\n\n // Summary line\n const noSchemaCount = stats.orphanedElements.length - connectableCount - deadEndCount;\n const parts: string[] = [];\n if (connectableCount > 0) parts.push(`${connectableCount} connectable`);\n if (deadEndCount > 0) parts.push(`${deadEndCount} dead-end (valid target types missing from model)`);\n if (noSchemaCount > 0) parts.push(`${noSchemaCount} no-schema`);\n console.log(ansis.dim(`Summary: ${parts.length > 0 ? parts.join(\", \") : \"0 actionable\"}`));\n if (connectableCount > 0) {\n console.log(ansis.dim(\"Run /dr-relate --orphans to wire connectable elements\"));\n }\n}\n\nexport async function validateCommand(options: ValidateOptions): Promise<void> {\n // Annotate the root cli.execute span with validate-specific attributes\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.layers\", options.layers?.join(\",\") || \"all\");\n activeSpan.setAttribute(\"validate.strict\", options.strict || false);\n activeSpan.setAttribute(\"validate.verbose\", options.verbose || false);\n activeSpan.setAttribute(\"validate.orphans\", options.orphans || false);\n activeSpan.setAttribute(\"validate.schemas\", options.schemas || options.schema || false);\n activeSpan.setAttribute(\"validate.output\", options.output || \"console\");\n }\n\n try {\n // Handle schema validation flag\n if (options.schemas || options.schema) {\n await validateSchemaSynchronization();\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.mode\", \"schema-sync\");\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n }\n\n // Load model (forward --layers filter so only requested layers are read from disk)\n const model = await Model.load(options.model, options.layers ? { layers: options.layers } : {});\n\n // --orphans mode: focused orphan report, skip full validation\n if (options.orphans) {\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.mode\", \"orphans-only\");\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n const modelForOrphans = activeChangesetId\n ? (await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)) as unknown as typeof model\n : model;\n await validateOrphansOnly(modelForOrphans, options.output);\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n }\n\n // Validate\n const validator = new Validator();\n\n // If an active changeset exists, project staged elements into the model so validators see them\n const activeChangesetId = model.getActiveChangesetId();\n const modelToValidate = activeChangesetId\n ? (await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)) as unknown as typeof model\n : model;\n\n const result = await validator.validateModel(modelToValidate);\n\n // Detect orphaned elements and add them as warnings so they appear in\n // the warning count and are surfaced consistently across all output formats.\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n for (const orphanId of stats.orphanedElements) {\n result.addWarning({\n message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,\n layer: orphanId.split(\".\")[0] ?? \"\",\n elementId: orphanId,\n category: \"orphan\",\n fixSuggestion: \"Add cross-layer references or relationships to connect this element to the rest of the model\",\n });\n }\n\n // Check source traceability in normal mode: elements without source_reference lack the\n // provenance link that dr-sync uses for drift detection. Surface this in every validate\n // run so users discover gaps without having to remember --strict.\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n if (!element.source_reference) {\n result.addWarning({\n message: `Element '${elementId}' has no source reference`,\n layer: layerName,\n elementId,\n fixSuggestion:\n \"Add a source_reference with provenance to link this element to its implementation\",\n });\n }\n }\n }\n\n // Strict-mode additional checks: flag elements missing a description.\n if (options.strict) {\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n\n if (!element.description || element.description.trim() === \"\") {\n result.addWarning({\n message: `Element '${elementId}' has no description`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a description to document what this element represents\",\n });\n }\n }\n }\n }\n\n // Record validation results in span\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.valid\", result.isValid());\n activeSpan.setAttribute(\"validate.error_count\", result.errors.length);\n activeSpan.setAttribute(\"validate.warning_count\", result.warnings.length);\n activeSpan.setAttribute(\"validate.orphan_count\", stats.orphanedElements.length);\n }\n\n // Format and display output\n if (options.output) {\n // Export to file\n let content: string;\n\n if (options.output.endsWith(\".md\") || options.output.endsWith(\".markdown\")) {\n content = ValidationFormatter.toMarkdown(result, modelToValidate);\n } else if (options.output.endsWith(\".json\")) {\n content = JSON.stringify(ValidationFormatter.toJSON(result, modelToValidate), null, 2);\n } else {\n // Default to JSON\n content = JSON.stringify(ValidationFormatter.toJSON(result, modelToValidate), null, 2);\n }\n\n await fs.writeFile(options.output, content, \"utf-8\");\n console.log(`\\nValidation report exported to ${options.output}`);\n\n if (!result.isValid()) {\n throw new Error(\"Validation failed\");\n }\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n }\n\n // Display formatted output\n const formatted = ValidationFormatter.format(result, modelToValidate, {\n verbose: options.verbose,\n quiet: options.quiet,\n });\n\n console.log(formatted);\n\n // Exit with appropriate code\n if (result.isValid()) {\n if (options.strict && result.warnings.length > 0) {\n console.log(ansis.red(\"Strict mode enabled: treating warnings as errors\"));\n throw new Error(\"Validation failed (strict mode)\");\n }\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n } else {\n throw new Error(\"Validation failed\");\n }\n } catch (error) {\n activeSpan?.setAttribute(\"validate.result\", \"error\");\n\n const message = getErrorMessage(error);\n console.error(ansis.red(`Error: ${message}`));\n\n // Always preserve full error details in stderr for debugging\n if (error instanceof Error && error.stack) {\n console.error(ansis.dim(\"\\nFull error details:\"));\n console.error(ansis.dim(error.stack));\n }\n\n // Additional context for debugging\n if (process.env.DEBUG) {\n console.error(ansis.dim(\"\\nDebug mode enabled. Additional context:\"));\n if (error instanceof Error) {\n console.error(ansis.dim(`Error name: ${error.name}`));\n console.error(ansis.dim(`Error cause: ${(error as any).cause || \"none\"}`));\n }\n }\n\n // Re-throw to let root span handle exit\n throw error;\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Validate the architecture model\n */\n\nimport ansis from \"ansis\";\nimport { promises as fs } from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { Model } from \"../core/model.js\";\nimport { Validator } from \"../validators/validator.js\";\nimport { ValidationFormatter } from \"../validators/validation-formatter.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { RELATIONSHIPS_BY_SOURCE, RELATIONSHIPS_BY_DESTINATION } from \"../generated/relationship-index.js\";\nimport { getActiveSpan } from \"../telemetry/index.js\";\n\n\nexport interface ValidateOptions {\n layers?: string[];\n strict?: boolean;\n verbose?: boolean;\n quiet?: boolean;\n output?: string;\n debug?: boolean;\n model?: string;\n orphans?: boolean;\n // Python CLI compatibility options\n all?: boolean;\n markdown?: boolean;\n schemas?: boolean;\n schema?: boolean; // Alias for schemas\n relationships?: boolean;\n structure?: boolean;\n naming?: boolean;\n references?: boolean;\n}\n\n/**\n * Validate schema synchronization between spec/ and cli/src/schemas/bundled/\n * @throws {Error} if schema synchronization validation fails\n */\n/**\n * Recursively find all JSON schema files in a directory\n * Excludes 'layers' subdirectory since layer instances are not schemas\n */\nasync function findJsonFiles(dir: string, baseDir: string = dir): Promise<string[]> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n // Skip 'layers' directory - layer instances are synced but are not schemas\n if (entry.isDirectory() && entry.name !== \"layers\") {\n const subFiles = await findJsonFiles(fullPath, baseDir);\n files.push(...subFiles);\n } else if (entry.name.endsWith(\".json\") && entry.isFile()) {\n // Store relative path from base directory\n files.push(path.relative(baseDir, fullPath));\n }\n }\n\n return files;\n}\n\nasync function validateSchemaSynchronization(): Promise<void> {\n console.log(\"\");\n console.log(ansis.bold(\"Validating schema synchronization...\"));\n console.log(\"\");\n\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n // Resolve paths relative to project root\n const projectRoot = path.resolve(__dirname, \"../../..\");\n // Compare spec/dist/ (15 compiled flat files) vs dist/schemas/bundled/ (same 15 files)\n // spec/schemas/ contains individual source files; spec/dist/ is the compiled distribution\n const specSchemaDir = path.join(projectRoot, \"spec\", \"dist\");\n const bundledSchemaDir = path.join(__dirname, \"../schemas/bundled\");\n\n // Find all JSON files recursively\n const specSchemaFiles = await findJsonFiles(specSchemaDir);\n const bundledSchemaFiles = await findJsonFiles(bundledSchemaDir);\n\n if (specSchemaFiles.length === 0) {\n throw new Error(\"No schema files found in spec/schemas/\");\n }\n\n let mismatches: string[] = [];\n\n for (const schemaFile of specSchemaFiles) {\n const specPath = path.join(specSchemaDir, schemaFile);\n const bundledPath = path.join(bundledSchemaDir, schemaFile);\n\n try {\n const specContent = await fs.readFile(specPath, \"utf-8\");\n\n try {\n const bundledContent = await fs.readFile(bundledPath, \"utf-8\");\n\n // Normalize JSON to account for formatting differences\n const specParsed = JSON.parse(specContent);\n const bundledParsed = JSON.parse(bundledContent);\n const specNormalized = JSON.stringify(specParsed, null, 2);\n const bundledNormalized = JSON.stringify(bundledParsed, null, 2);\n\n if (specNormalized !== bundledNormalized) {\n mismatches.push(\n ` ${ansis.red(\"\u2717\")} ${schemaFile} - Content differs between spec/ and cli/src/schemas/bundled/`\n );\n } else {\n console.log(` ${ansis.green(\"\u2713\")} ${schemaFile}`);\n }\n } catch (error) {\n if ((error as any).code === \"ENOENT\") {\n mismatches.push(\n ` ${ansis.red(\"\u2717\")} ${schemaFile} - Missing in cli/src/schemas/bundled/`\n );\n } else {\n throw error;\n }\n }\n } catch (error) {\n const message = getErrorMessage(error);\n mismatches.push(` ${ansis.red(\"\u2717\")} ${schemaFile} - Error reading spec schema: ${message}`);\n }\n }\n\n // Check for extra files in bundled that aren't in spec\n for (const bundledFile of bundledSchemaFiles) {\n if (!specSchemaFiles.includes(bundledFile)) {\n mismatches.push(\n ` ${ansis.red(\"\u2717\")} ${bundledFile} - Extra file in cli/src/schemas/bundled/ (not in spec/)`\n );\n }\n }\n\n if (mismatches.length > 0) {\n console.log(\"\");\n console.log(\n ansis.bold(ansis.red(`\u2717 Schema synchronization failed with ${mismatches.length} issue(s):`))\n );\n console.log(\"\");\n for (const mismatch of mismatches) {\n console.log(mismatch);\n }\n console.log(\"\");\n console.log(\n ansis.dim(\"To fix: Run 'npm run build:spec' at repo root, then 'npm run build' in cli/ to sync spec/dist/ \u2192 cli/src/schemas/bundled/\")\n );\n console.log(\"\");\n throw new Error(`Schema synchronization failed with ${mismatches.length} issue(s)`);\n } else {\n console.log(\"\");\n console.log(ansis.green(\"\u2713 All schemas synchronized\"));\n console.log(\"\");\n }\n}\n\n/**\n * Display structured orphan report grouped by layer, with dead-end detection.\n * For each orphan, checks whether valid relationship targets exist in the model.\n */\nasync function validateOrphansOnly(model: Model, outputPath?: string): Promise<void> {\n const stats = ValidationFormatter.calculateStats(model);\n\n if (stats.orphanedElements.length === 0) {\n const msg = \"No orphaned elements found. All elements are connected.\";\n if (outputPath) {\n await fs.writeFile(outputPath, JSON.stringify({ total: 0, byLayer: {} }, null, 2), \"utf-8\");\n console.log(`\\nOrphan report exported to ${outputPath}`);\n } else {\n console.log(ansis.green(`\\n\u2713 ${msg}`));\n }\n return;\n }\n\n // Build set of element types currently present in the model\n const typesInModel = new Set<string>();\n for (const [, layer] of model.layers) {\n for (const element of layer.listElements()) {\n const id = element.path || element.id;\n const parts = id.split(\".\");\n if (parts.length >= 2) typesInModel.add(`${parts[0]}.${parts[1]}`);\n }\n }\n\n // Group orphans by layer, annotated with dead-end status\n const byLayer = new Map<string, Array<{\n id: string;\n type: string;\n status: \"dead-end\" | \"connectable\" | \"no-schema\";\n reachablePredicates: string[];\n reachableTargetTypes: string[];\n needsTypes: string[];\n }>>();\n\n for (const elementId of stats.orphanedElements.sort()) {\n const parts = elementId.split(\".\");\n const layer = parts[0] ?? \"unknown\";\n const specNodeType = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : elementId;\n\n // Outgoing: this element can point to others\n const outgoingRels = RELATIONSHIPS_BY_SOURCE.get(specNodeType) ?? [];\n const allDestTypes = [...new Set(outgoingRels.map(r => r.destinationSpecNodeId))];\n const reachableDestTypes = allDestTypes.filter(t => typesInModel.has(t));\n const reachablePredicates = [...new Set(\n outgoingRels.filter(r => reachableDestTypes.includes(r.destinationSpecNodeId)).map(r => r.predicate)\n )];\n\n // Incoming: other element types in the model can point to this element\n const incomingRels = RELATIONSHIPS_BY_DESTINATION.get(specNodeType) ?? [];\n const reachableIncomingSourceTypes = [...new Set(incomingRels.map(r => r.sourceSpecNodeId))]\n .filter(t => typesInModel.has(t));\n\n const hasOutgoingTargets = reachableDestTypes.length > 0;\n const hasIncomingSources = reachableIncomingSourceTypes.length > 0;\n const hasAnySchemas = outgoingRels.length > 0 || incomingRels.length > 0;\n\n let status: \"dead-end\" | \"connectable\" | \"no-schema\";\n if (!hasAnySchemas) {\n status = \"no-schema\";\n } else if (!hasOutgoingTargets && !hasIncomingSources) {\n status = \"dead-end\";\n } else {\n status = \"connectable\";\n }\n\n if (!byLayer.has(layer)) byLayer.set(layer, []);\n byLayer.get(layer)!.push({\n id: elementId,\n type: specNodeType,\n status,\n reachablePredicates,\n reachableTargetTypes: reachableDestTypes,\n needsTypes: allDestTypes,\n });\n }\n\n if (outputPath) {\n const jsonOut: Record<string, unknown> = {\n total: stats.orphanedElements.length,\n byLayer: Object.fromEntries(\n [...byLayer.entries()].map(([layer, items]) => [layer, items])\n ),\n };\n await fs.writeFile(outputPath, JSON.stringify(jsonOut, null, 2), \"utf-8\");\n console.log(`\\nOrphan report exported to ${outputPath}`);\n return;\n }\n\n console.log(ansis.bold(`\\nOrphaned Elements (${stats.orphanedElements.length})`));\n console.log(ansis.dim(\"Elements with no intra-layer relationships and no cross-layer references\\n\"));\n\n let deadEndCount = 0;\n let connectableCount = 0;\n\n for (const [layerName, items] of byLayer) {\n console.log(ansis.bold(ansis.cyan(` ${layerName} (${items.length})`)));\n for (const item of items) {\n if (item.status === \"no-schema\") {\n console.log(` ${ansis.dim(\"\u25CB\")} ${item.id}`);\n console.log(` ${ansis.dim(\"No outgoing relationship schemas defined for type \" + item.type)}`);\n } else if (item.status === \"dead-end\") {\n deadEndCount++;\n const needsPreview = item.needsTypes.slice(0, 4).join(\", \") +\n (item.needsTypes.length > 4 ? ` +${item.needsTypes.length - 4} more` : \"\");\n console.log(` ${ansis.red(\"\u2717\")} ${item.id} ${ansis.dim(\"(dead-end)\")}`);\n console.log(` ${ansis.dim(\"Needs: \" + needsPreview)}`);\n } else {\n connectableCount++;\n const predsPreview = item.reachablePredicates.slice(0, 3).join(\", \");\n const targetsPreview = item.reachableTargetTypes.slice(0, 3).join(\", \");\n console.log(` ${ansis.yellow(\"\u25CB\")} ${item.id}`);\n console.log(` ${ansis.dim(`\u2192 try: ${predsPreview} \u2192 ${targetsPreview}`)}`);\n }\n }\n console.log();\n }\n\n // Summary line\n const noSchemaCount = stats.orphanedElements.length - connectableCount - deadEndCount;\n const parts: string[] = [];\n if (connectableCount > 0) parts.push(`${connectableCount} connectable`);\n if (deadEndCount > 0) parts.push(`${deadEndCount} dead-end (valid target types missing from model)`);\n if (noSchemaCount > 0) parts.push(`${noSchemaCount} no-schema`);\n console.log(ansis.dim(`Summary: ${parts.length > 0 ? parts.join(\", \") : \"0 actionable\"}`));\n if (connectableCount > 0) {\n console.log(ansis.dim(\"Run /dr-relate --orphans to wire connectable elements\"));\n }\n}\n\nexport async function validateCommand(options: ValidateOptions): Promise<void> {\n // Annotate the root cli.execute span with validate-specific attributes\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.layers\", options.layers?.join(\",\") || \"all\");\n activeSpan.setAttribute(\"validate.strict\", options.strict || false);\n activeSpan.setAttribute(\"validate.verbose\", options.verbose || false);\n activeSpan.setAttribute(\"validate.orphans\", options.orphans || false);\n activeSpan.setAttribute(\"validate.schemas\", options.schemas || options.schema || false);\n activeSpan.setAttribute(\"validate.output\", options.output || \"console\");\n }\n\n try {\n // Handle schema validation flag\n if (options.schemas || options.schema) {\n await validateSchemaSynchronization();\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.mode\", \"schema-sync\");\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n }\n\n // Load model (forward --layers filter so only requested layers are read from disk)\n const model = await Model.load(options.model, options.layers ? { layers: options.layers } : {});\n\n // --orphans mode: focused orphan report, skip full validation\n if (options.orphans) {\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.mode\", \"orphans-only\");\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n const modelForOrphans = activeChangesetId\n ? (await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)) as unknown as typeof model\n : model;\n await validateOrphansOnly(modelForOrphans, options.output);\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n }\n\n // Validate\n const validator = new Validator();\n\n // If an active changeset exists, project staged elements into the model so validators see them\n const activeChangesetId = model.getActiveChangesetId();\n const modelToValidate = activeChangesetId\n ? (await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)) as unknown as typeof model\n : model;\n\n const result = await validator.validateModel(modelToValidate);\n\n // Detect orphaned elements and add them as warnings so they appear in\n // the warning count and are surfaced consistently across all output formats.\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n for (const orphanId of stats.orphanedElements) {\n result.addWarning({\n message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,\n layer: orphanId.split(\".\")[0] ?? \"\",\n elementId: orphanId,\n category: \"orphan\",\n fixSuggestion: \"Add cross-layer references or relationships to connect this element to the rest of the model\",\n });\n }\n\n // Check source traceability in normal mode: elements without source_reference lack the\n // provenance link that dr-sync uses for drift detection. Surface this in every validate\n // run so users discover gaps without having to remember --strict.\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n if (!element.source_reference) {\n result.addWarning({\n message: `Element '${elementId}' has no source reference`,\n layer: layerName,\n elementId,\n fixSuggestion:\n \"Add a source_reference with provenance to link this element to its implementation\",\n });\n }\n }\n }\n\n // Strict-mode additional checks: flag elements missing a description.\n if (options.strict) {\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n\n if (!element.description || element.description.trim() === \"\") {\n result.addWarning({\n message: `Element '${elementId}' has no description`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a description to document what this element represents\",\n });\n }\n }\n }\n }\n\n // Record validation results in span\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.valid\", result.isValid());\n activeSpan.setAttribute(\"validate.error_count\", result.errors.length);\n activeSpan.setAttribute(\"validate.warning_count\", result.warnings.length);\n activeSpan.setAttribute(\"validate.orphan_count\", stats.orphanedElements.length);\n }\n\n // Format and display output\n if (options.output) {\n // Export to file\n let content: string;\n\n if (options.output.endsWith(\".md\") || options.output.endsWith(\".markdown\")) {\n content = ValidationFormatter.toMarkdown(result, modelToValidate);\n } else if (options.output.endsWith(\".json\")) {\n content = JSON.stringify(ValidationFormatter.toJSON(result, modelToValidate), null, 2);\n } else {\n // Default to JSON\n content = JSON.stringify(ValidationFormatter.toJSON(result, modelToValidate), null, 2);\n }\n\n await fs.writeFile(options.output, content, \"utf-8\");\n console.log(`\\nValidation report exported to ${options.output}`);\n\n if (!result.isValid()) {\n throw new Error(\"Validation failed\");\n }\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n }\n\n // Display formatted output\n const formatted = ValidationFormatter.format(result, modelToValidate, {\n verbose: options.verbose,\n quiet: options.quiet,\n });\n\n console.log(formatted);\n\n // Exit with appropriate code\n if (result.isValid()) {\n if (options.strict && result.warnings.length > 0) {\n console.log(ansis.red(\"Strict mode enabled: treating warnings as errors\"));\n throw new Error(\"Validation failed (strict mode)\");\n }\n\n if (activeSpan) {\n activeSpan.setAttribute(\"validate.result\", \"success\");\n }\n return;\n } else {\n throw new Error(\"Validation failed\");\n }\n } catch (error) {\n activeSpan?.setAttribute(\"validate.result\", \"error\");\n\n const message = getErrorMessage(error);\n console.error(ansis.red(`Error: ${message}`));\n\n // Always preserve full error details in stderr for debugging\n if (error instanceof Error && error.stack) {\n console.error(ansis.dim(\"\\nFull error details:\"));\n console.error(ansis.dim(error.stack));\n }\n\n // Additional context for debugging\n if (process.env.DEBUG) {\n console.error(ansis.dim(\"\\nDebug mode enabled. Additional context:\"));\n if (error instanceof Error) {\n console.error(ansis.dim(`Error name: ${error.name}`));\n console.error(ansis.dim(`Error cause: ${(error as any).cause || \"none\"}`));\n }\n }\n\n // Re-throw to let root span handle exit\n throw error;\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["parts", "activeChangesetId"]
7
7
  }
@@ -2,7 +2,7 @@ import ansis from "ansis";
2
2
  import { getCliBundledSpecVersion } from "../utils/spec-version.js";
3
3
  import { isTelemetryBuiltIn, isTelemetryConfigured } from "../telemetry/detect.js";
4
4
  import { getSpecReferencePath, getModelPath } from "../utils/project-paths.js";
5
- const gitHash = "f413188", cliVersion = "0.1.8";
5
+ const gitHash = "ee281fb", cliVersion = "0.1.10";
6
6
  async function versionCommand() {
7
7
  const specVersion = getCliBundledSpecVersion(), telemetryBuiltIn = isTelemetryBuiltIn(), telemetryConfigured = await isTelemetryConfigured();
8
8
  let telemetryStatus;
@@ -1,6 +1,7 @@
1
1
  const CANONICAL_LAYER_NAMES = [
2
2
  "motivation",
3
3
  "business",
4
+ "product",
4
5
  "security",
5
6
  "application",
6
7
  "technology",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/layers.ts"],
4
- "sourcesContent": ["/**\n * Canonical layer names and ordering for the 12-layer architecture model.\n * This is the source of truth for layer naming and ordering.\n */\n\nexport const CANONICAL_LAYER_NAMES = [\n 'motivation',\n 'business',\n 'security',\n 'application',\n 'technology',\n 'api',\n 'data-model',\n 'data-store',\n 'ux',\n 'navigation',\n 'apm',\n 'testing'\n] as const;\n\nexport type CanonicalLayerName = (typeof CANONICAL_LAYER_NAMES)[number];\n\n/**\n * Get the numeric order of a layer (1-12)\n */\nexport function getLayerOrder(layerName: string): number {\n const index = CANONICAL_LAYER_NAMES.indexOf(layerName as CanonicalLayerName);\n return index >= 0 ? index + 1 : -1;\n}\n\n/**\n * Get layer name by numeric order (1-12)\n */\nexport function getLayerByOrder(order: number): string | undefined {\n if (order < 1 || order > CANONICAL_LAYER_NAMES.length) {\n return undefined;\n }\n return CANONICAL_LAYER_NAMES[order - 1];\n}\n\n/**\n * Check if a layer name is valid (type predicate for compile-time narrowing)\n */\nexport function isValidLayerName(name: string): name is CanonicalLayerName {\n return CANONICAL_LAYER_NAMES.includes(name as CanonicalLayerName);\n}\n"],
5
- "mappings": "AAKO,MAAM,wBAAwB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAQI,SAAU,cAAc,WAAiB;AAC7C,QAAM,QAAQ,sBAAsB,QAAQ,SAA+B;AAC3E,SAAO,SAAS,IAAI,QAAQ,IAAI;AAClC;AAKM,SAAU,gBAAgB,OAAa;AAC3C,MAAI,UAAQ,KAAK,QAAQ,sBAAsB;AAG/C,WAAO,sBAAsB,QAAQ,CAAC;AACxC;AAKM,SAAU,iBAAiB,MAAY;AAC3C,SAAO,sBAAsB,SAAS,IAA0B;AAClE;",
4
+ "sourcesContent": ["/**\n * Canonical layer names and ordering for the 13-layer architecture model.\n * This is the source of truth for layer naming and ordering.\n */\n\nexport const CANONICAL_LAYER_NAMES = [\n 'motivation',\n 'business',\n 'product',\n 'security',\n 'application',\n 'technology',\n 'api',\n 'data-model',\n 'data-store',\n 'ux',\n 'navigation',\n 'apm',\n 'testing'\n] as const;\n\nexport type CanonicalLayerName = (typeof CANONICAL_LAYER_NAMES)[number];\n\n/**\n * Get the numeric order of a layer (1-13)\n */\nexport function getLayerOrder(layerName: string): number {\n const index = CANONICAL_LAYER_NAMES.indexOf(layerName as CanonicalLayerName);\n return index >= 0 ? index + 1 : -1;\n}\n\n/**\n * Get layer name by numeric order (1-13)\n */\nexport function getLayerByOrder(order: number): string | undefined {\n if (order < 1 || order > CANONICAL_LAYER_NAMES.length) {\n return undefined;\n }\n return CANONICAL_LAYER_NAMES[order - 1];\n}\n\n/**\n * Check if a layer name is valid (type predicate for compile-time narrowing)\n */\nexport function isValidLayerName(name: string): name is CanonicalLayerName {\n return CANONICAL_LAYER_NAMES.includes(name as CanonicalLayerName);\n}\n"],
5
+ "mappings": "AAKO,MAAM,wBAAwB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAQI,SAAU,cAAc,WAAiB;AAC7C,QAAM,QAAQ,sBAAsB,QAAQ,SAA+B;AAC3E,SAAO,SAAS,IAAI,QAAQ,IAAI;AAClC;AAKM,SAAU,gBAAgB,OAAa;AAC3C,MAAI,UAAQ,KAAK,QAAQ,sBAAsB;AAG/C,WAAO,sBAAsB,QAAQ,CAAC;AACxC;AAKM,SAAU,iBAAiB,MAAY;AAC3C,SAAO,sBAAsB,SAAS,IAA0B;AAClE;",
6
6
  "names": []
7
7
  }
@@ -80,6 +80,60 @@ class MigrationRegistry {
80
80
  filesModified: 0,
81
81
  description: "Spec version updated to 0.8.4 (Analyzer Infrastructure)"
82
82
  })
83
+ }), this.migrations.push({
84
+ fromVersion: "0.8.4",
85
+ toVersion: "0.9.0",
86
+ description: "Product Layer Introduction (Spec v0.9.0)",
87
+ apply: async (model) => {
88
+ const fs = await import("fs/promises"), pathModule = await import("path"), modelDir = `${model.rootPath}/documentation-robotics/model`;
89
+ let filesModified = 0;
90
+ try {
91
+ await fs.access(modelDir);
92
+ } catch (error) {
93
+ if (error.code === "ENOENT")
94
+ return {
95
+ migrationsApplied: 1,
96
+ filesModified: 0,
97
+ description: "Spec version updated to 0.9.0 (Product layer introduced; no model directories to migrate)"
98
+ };
99
+ throw error;
100
+ }
101
+ const renameMap = [
102
+ ["12_testing", "13_testing"],
103
+ ["11_apm", "12_apm"],
104
+ ["10_navigation", "11_navigation"],
105
+ ["09_ux", "10_ux"],
106
+ ["08_data-store", "09_data-store"],
107
+ ["07_data-model", "08_data-model"],
108
+ ["06_api", "07_api"],
109
+ ["05_technology", "06_technology"],
110
+ ["04_application", "05_application"],
111
+ ["03_security", "04_security"]
112
+ ], renameErrors = [];
113
+ for (const [oldName, newName] of renameMap) {
114
+ const oldPath = pathModule.join(modelDir, oldName), newPath = pathModule.join(modelDir, newName);
115
+ try {
116
+ await fs.rename(oldPath, newPath), filesModified++;
117
+ } catch (error) {
118
+ if (error.code === "ENOENT")
119
+ continue;
120
+ renameErrors.push(`Failed to rename ${oldName} to ${newName}: ${getErrorMessage(error)}`);
121
+ }
122
+ }
123
+ if (renameErrors.length > 0)
124
+ return {
125
+ migrationsApplied: 1,
126
+ filesModified,
127
+ description: "Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)",
128
+ error: renameErrors.join("; ")
129
+ };
130
+ const productPath = pathModule.join(modelDir, "03_product");
131
+ return await fs.mkdir(productPath, { recursive: !0 }), {
132
+ migrationsApplied: 1,
133
+ filesModified,
134
+ description: "Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)"
135
+ };
136
+ }
83
137
  });
84
138
  }
85
139
  /**
@@ -137,12 +191,15 @@ class MigrationRegistry {
137
191
  else
138
192
  try {
139
193
  const result = await migration.apply(model);
140
- results.applied.push({
194
+ if (results.applied.push({
141
195
  from: migration.fromVersion,
142
196
  to: migration.toVersion,
143
197
  description: migration.description,
144
198
  changes: result
145
- }), results.totalChanges += result.migrationsApplied, model.manifest.specVersion = migration.toVersion;
199
+ }), results.totalChanges += result.migrationsApplied, !result.error)
200
+ model.manifest.specVersion = migration.toVersion;
201
+ else
202
+ throw new Error(result.error);
146
203
  } catch (error) {
147
204
  throw new Error(`Migration ${migration.fromVersion} \u2192 ${migration.toVersion} failed: ${getErrorMessage(error)}`);
148
205
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/migration-registry.ts"],
4
- "sourcesContent": ["/**\n * Migration Registry - Manages version-based model migrations\n *\n * Handles sequential migrations from one spec version to another,\n * supporting the evolution of the architecture model specification.\n */\n\nimport type { Model } from \"./model.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * Represents a single version migration\n */\nexport interface Migration {\n fromVersion: string;\n toVersion: string;\n description: string;\n apply: (model: Model) => Promise<MigrationResult>;\n}\n\n/**\n * Result of applying a migration\n */\nexport interface MigrationResult {\n migrationsApplied: number;\n filesModified: number;\n description: string;\n error?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * Migration Registry managing all available migrations\n */\nexport class MigrationRegistry {\n private migrations: Migration[] = [];\n\n constructor() {\n this.registerMigrations();\n }\n\n /**\n * Register all available migrations\n */\n private registerMigrations(): void {\n // Migration from v0.5.0 to v0.6.0: Relationship Taxonomy\n this.migrations.push({\n fromVersion: \"0.5.0\",\n toVersion: \"0.6.0\",\n description: \"Relationship Taxonomy (Spec v0.6.0)\",\n apply: async () => {\n // This migration is opt-in - the new relationship taxonomy\n // is fully backward compatible with existing models\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.6.0 (Relationship Taxonomy now available)\",\n };\n },\n });\n\n // Migration from v0.6.0 to v0.7.0: Layer Schema Relationship Metadata\n this.migrations.push({\n fromVersion: \"0.6.0\",\n toVersion: \"0.7.0\",\n description: \"Layer Schema Relationship Metadata (Spec v0.7.0)\",\n apply: async () => {\n // This migration updates layer schemas with relationship metadata sections\n // - All 12 layer schemas now include layerMetadata, intraLayerRelationships, crossLayerRelationships\n // - Deprecated link-registry.json (removed in v0.8.0)\n // - Terminology change: \"cross-layer links\" \u2192 \"cross-layer relationships\"\n // Fully backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description:\n \"Spec version updated to 0.7.0 (Layer schemas now include relationship metadata sections)\",\n };\n },\n });\n\n // Migration from v0.7.0 to v0.7.1: Source Code Reference Infrastructure\n this.migrations.push({\n fromVersion: \"0.7.0\",\n toVersion: \"0.7.1\",\n description: \"Source Code Reference Infrastructure (Spec v0.7.1)\",\n apply: async () => {\n // This migration adds source code reference infrastructure\n // - Common schema definitions in spec/schemas/common/\n // - Source references integrated across 10 layers\n // - Backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.7.1 (Source Code Reference Infrastructure)\",\n };\n },\n });\n\n // Migration from v0.7.1 to v0.8.0: JSON Schema-Only Spec Architecture\n this.migrations.push({\n fromVersion: \"0.7.1\",\n toVersion: \"0.8.0\",\n description: \"JSON Schema-Only Spec Architecture (Spec v0.8.0)\",\n apply: async () => {\n // Spec architecture refactored to JSON Schema-only model:\n // - Node type definitions moved to spec/schemas/nodes/{layer}/*.node.schema.json\n // - Relationship definitions moved to spec/schemas/relationships/{layer}/*.relationship.schema.json\n // - Predicates relocated to spec/schemas/base/predicates.json\n // This is a spec architecture change only - model data files are fully backward compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.0 (JSON Schema-Only Spec Architecture)\",\n };\n },\n });\n\n // Migration from v0.8.0 to v0.8.1: Expanded Relationship Coverage and Node Improvements\n this.migrations.push({\n fromVersion: \"0.8.0\",\n toVersion: \"0.8.1\",\n description: \"Expanded Relationship Coverage and Node Improvements (Spec v0.8.1)\",\n apply: async () => {\n // Spec enhancements only - no model data format changes:\n // - 733 new relationship schemas added across all 12 layers\n // - Node type descriptions improved for all 12 layers\n // - Enum-masquerader node types consolidated into parent attributes\n // - Existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.1 (Expanded Relationship Coverage and Node Improvements)\",\n };\n },\n });\n\n // Migration from v0.8.1 to v0.8.2: UUID/Path identifier separation\n this.migrations.push({\n fromVersion: \"0.8.1\",\n toVersion: \"0.8.2\",\n description: \"UUID/Path identifier separation (Spec v0.8.2)\",\n apply: async () => {\n // The path field is now required on all spec nodes (spec-node.schema.json).\n // Existing elements are migrated automatically on load in model.ts (loadLayer):\n // - Elements with slug-format id: slug moved to path, deterministic UUID assigned to id\n // - Elements with UUID id but no path: path derived from layer_id.type.kebab(name)\n // No manual data file changes needed; migration runs transparently on next load/save.\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.2 (UUID/Path identifier separation)\",\n };\n },\n });\n\n // Migration from v0.8.2 to v0.8.3: Inter-layer Relationship Schemas and APM Node Types\n this.migrations.push({\n fromVersion: \"0.8.2\",\n toVersion: \"0.8.3\",\n description: \"Inter-layer Relationship Schemas and APM Node Types (Spec v0.8.3)\",\n apply: async () => {\n // Major expansion of inter-layer relationship coverage and APM layer:\n // - 495 new inter-layer relationship schemas across all 12 layers\n // - Total relationship count increased from 969 to 1,447\n // - 2 new APM node types: apm.alert and apm.dashboard\n // - Backward compatible - existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.3 (Inter-layer Relationship Schemas and APM Node Types)\",\n };\n },\n });\n\n // Migration from v0.8.3 to v0.8.4: Analyzer Infrastructure (CBM Mapping)\n this.migrations.push({\n fromVersion: \"0.8.3\",\n toVersion: \"0.8.4\",\n description: \"Analyzer Infrastructure (Spec v0.8.4)\",\n apply: async () => {\n // Introduces spec/analyzers/ with codebase-memory (CBM) mapping:\n // - New analyzer adapter foundation and full analyzer subcommands\n // - dr analyzer verify with changeset awareness\n // - Claude Code integration commands (/dr-verify, /dr-map, dr-extractor)\n // - No model data format changes; existing models are fully compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.4 (Analyzer Infrastructure)\",\n };\n },\n });\n }\n\n /**\n * Get the latest available specification version\n */\n getLatestVersion(): string {\n if (this.migrations.length === 0) {\n return \"0.7.0\";\n }\n\n // Return the highest toVersion from all migrations\n const versions = this.migrations.map((m) => m.toVersion);\n return versions.sort().reverse()[0];\n }\n\n /**\n * Get the sequence of migrations needed from one version to another\n */\n getMigrationPath(fromVersion: string, toVersion?: string): Migration[] {\n const targetVersion = toVersion || this.getLatestVersion();\n\n if (this.compareVersions(fromVersion, targetVersion) >= 0) {\n return [];\n }\n\n const path: Migration[] = [];\n let current = fromVersion;\n\n // Sort migrations by fromVersion\n const sortedMigrations = [...this.migrations].sort((a, b) =>\n this.compareVersions(a.fromVersion, b.fromVersion)\n );\n\n for (const migration of sortedMigrations) {\n // Check if this migration is in our path\n if (\n this.compareVersions(current, migration.fromVersion) >= 0 &&\n this.compareVersions(current, migration.toVersion) < 0 &&\n this.compareVersions(migration.toVersion, targetVersion) <= 0\n ) {\n path.push(migration);\n current = migration.toVersion;\n }\n }\n\n return path;\n }\n\n /**\n * Check if a model requires migration\n */\n requiresMigration(currentVersion: string): boolean {\n const path = this.getMigrationPath(currentVersion);\n return path.length > 0;\n }\n\n /**\n * Apply all migrations in sequence\n */\n async applyMigrations(\n model: Model,\n options: {\n fromVersion: string;\n toVersion?: string;\n dryRun?: boolean;\n validate?: boolean;\n }\n ): Promise<ApplyMigrationsResult> {\n const path = this.getMigrationPath(options.fromVersion, options.toVersion);\n const targetVersion = options.toVersion || this.getLatestVersion();\n\n if (path.length === 0) {\n return {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n }\n\n const results: ApplyMigrationsResult = {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n\n for (const migration of path) {\n if (!options.dryRun) {\n try {\n const result = await migration.apply(model);\n\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n changes: result,\n });\n\n results.totalChanges += result.migrationsApplied;\n\n // Update model's spec version\n model.manifest.specVersion = migration.toVersion;\n } catch (error) {\n throw new Error(\n `Migration ${migration.fromVersion} \u2192 ${migration.toVersion} failed: ${\n getErrorMessage(error)\n }`\n );\n }\n } else {\n // Dry run - just record what would happen\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n dryRun: true,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Get a summary of migrations that would be applied\n */\n getMigrationSummary(\n fromVersion: string,\n toVersion?: string\n ): {\n currentVersion: string;\n targetVersion: string;\n migrationsNeeded: number;\n migrations: Array<{\n from: string;\n to: string;\n description: string;\n }>;\n } {\n const path = this.getMigrationPath(fromVersion, toVersion);\n const targetVersion = toVersion || this.getLatestVersion();\n\n return {\n currentVersion: fromVersion,\n targetVersion,\n migrationsNeeded: path.length,\n migrations: path.map((m) => ({\n from: m.fromVersion,\n to: m.toVersion,\n description: m.description,\n })),\n };\n }\n\n /**\n * Compare two version strings\n * Returns: -1 if a < b, 0 if a == b, 1 if a > b\n */\n private compareVersions(a: string, b: string): number {\n const aParts = a.split(\".\").map(Number);\n const bParts = b.split(\".\").map(Number);\n\n const maxLength = Math.max(aParts.length, bParts.length);\n\n for (let i = 0; i < maxLength; i++) {\n const aPart = aParts[i] || 0;\n const bPart = bParts[i] || 0;\n\n if (aPart < bPart) return -1;\n if (aPart > bPart) return 1;\n }\n\n return 0;\n }\n}\n\n/**\n * Result of applying multiple migrations\n */\nexport interface ApplyMigrationsResult {\n applied: Array<{\n from: string;\n to: string;\n description: string;\n changes?: MigrationResult;\n dryRun?: boolean;\n }>;\n currentVersion: string;\n targetVersion: string;\n totalChanges: number;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Migration Registry - Manages version-based model migrations\n *\n * Handles sequential migrations from one spec version to another,\n * supporting the evolution of the architecture model specification.\n */\n\nimport type { Model } from \"./model.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * Represents a single version migration\n */\nexport interface Migration {\n fromVersion: string;\n toVersion: string;\n description: string;\n apply: (model: Model) => Promise<MigrationResult>;\n}\n\n/**\n * Result of applying a migration\n */\nexport interface MigrationResult {\n migrationsApplied: number;\n filesModified: number;\n description: string;\n error?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * Migration Registry managing all available migrations\n */\nexport class MigrationRegistry {\n private migrations: Migration[] = [];\n\n constructor() {\n this.registerMigrations();\n }\n\n /**\n * Register all available migrations\n */\n private registerMigrations(): void {\n // Migration from v0.5.0 to v0.6.0: Relationship Taxonomy\n this.migrations.push({\n fromVersion: \"0.5.0\",\n toVersion: \"0.6.0\",\n description: \"Relationship Taxonomy (Spec v0.6.0)\",\n apply: async () => {\n // This migration is opt-in - the new relationship taxonomy\n // is fully backward compatible with existing models\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.6.0 (Relationship Taxonomy now available)\",\n };\n },\n });\n\n // Migration from v0.6.0 to v0.7.0: Layer Schema Relationship Metadata\n this.migrations.push({\n fromVersion: \"0.6.0\",\n toVersion: \"0.7.0\",\n description: \"Layer Schema Relationship Metadata (Spec v0.7.0)\",\n apply: async () => {\n // This migration updates layer schemas with relationship metadata sections\n // - All 12 layer schemas now include layerMetadata, intraLayerRelationships, crossLayerRelationships\n // - Deprecated link-registry.json (removed in v0.8.0)\n // - Terminology change: \"cross-layer links\" \u2192 \"cross-layer relationships\"\n // Fully backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description:\n \"Spec version updated to 0.7.0 (Layer schemas now include relationship metadata sections)\",\n };\n },\n });\n\n // Migration from v0.7.0 to v0.7.1: Source Code Reference Infrastructure\n this.migrations.push({\n fromVersion: \"0.7.0\",\n toVersion: \"0.7.1\",\n description: \"Source Code Reference Infrastructure (Spec v0.7.1)\",\n apply: async () => {\n // This migration adds source code reference infrastructure\n // - Common schema definitions in spec/schemas/common/\n // - Source references integrated across 10 layers\n // - Backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.7.1 (Source Code Reference Infrastructure)\",\n };\n },\n });\n\n // Migration from v0.7.1 to v0.8.0: JSON Schema-Only Spec Architecture\n this.migrations.push({\n fromVersion: \"0.7.1\",\n toVersion: \"0.8.0\",\n description: \"JSON Schema-Only Spec Architecture (Spec v0.8.0)\",\n apply: async () => {\n // Spec architecture refactored to JSON Schema-only model:\n // - Node type definitions moved to spec/schemas/nodes/{layer}/*.node.schema.json\n // - Relationship definitions moved to spec/schemas/relationships/{layer}/*.relationship.schema.json\n // - Predicates relocated to spec/schemas/base/predicates.json\n // This is a spec architecture change only - model data files are fully backward compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.0 (JSON Schema-Only Spec Architecture)\",\n };\n },\n });\n\n // Migration from v0.8.0 to v0.8.1: Expanded Relationship Coverage and Node Improvements\n this.migrations.push({\n fromVersion: \"0.8.0\",\n toVersion: \"0.8.1\",\n description: \"Expanded Relationship Coverage and Node Improvements (Spec v0.8.1)\",\n apply: async () => {\n // Spec enhancements only - no model data format changes:\n // - 733 new relationship schemas added across all 12 layers\n // - Node type descriptions improved for all 12 layers\n // - Enum-masquerader node types consolidated into parent attributes\n // - Existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.1 (Expanded Relationship Coverage and Node Improvements)\",\n };\n },\n });\n\n // Migration from v0.8.1 to v0.8.2: UUID/Path identifier separation\n this.migrations.push({\n fromVersion: \"0.8.1\",\n toVersion: \"0.8.2\",\n description: \"UUID/Path identifier separation (Spec v0.8.2)\",\n apply: async () => {\n // The path field is now required on all spec nodes (spec-node.schema.json).\n // Existing elements are migrated automatically on load in model.ts (loadLayer):\n // - Elements with slug-format id: slug moved to path, deterministic UUID assigned to id\n // - Elements with UUID id but no path: path derived from layer_id.type.kebab(name)\n // No manual data file changes needed; migration runs transparently on next load/save.\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.2 (UUID/Path identifier separation)\",\n };\n },\n });\n\n // Migration from v0.8.2 to v0.8.3: Inter-layer Relationship Schemas and APM Node Types\n this.migrations.push({\n fromVersion: \"0.8.2\",\n toVersion: \"0.8.3\",\n description: \"Inter-layer Relationship Schemas and APM Node Types (Spec v0.8.3)\",\n apply: async () => {\n // Major expansion of inter-layer relationship coverage and APM layer:\n // - 495 new inter-layer relationship schemas across all 12 layers\n // - Total relationship count increased from 969 to 1,447\n // - 2 new APM node types: apm.alert and apm.dashboard\n // - Backward compatible - existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.3 (Inter-layer Relationship Schemas and APM Node Types)\",\n };\n },\n });\n\n // Migration from v0.8.3 to v0.8.4: Analyzer Infrastructure (CBM Mapping)\n this.migrations.push({\n fromVersion: \"0.8.3\",\n toVersion: \"0.8.4\",\n description: \"Analyzer Infrastructure (Spec v0.8.4)\",\n apply: async () => {\n // Introduces spec/analyzers/ with codebase-memory (CBM) mapping:\n // - New analyzer adapter foundation and full analyzer subcommands\n // - dr analyzer verify with changeset awareness\n // - Claude Code integration commands (/dr-verify, /dr-map, dr-extractor)\n // - No model data format changes; existing models are fully compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.4 (Analyzer Infrastructure)\",\n };\n },\n });\n\n // Migration from v0.8.4 to v0.9.0: Product Layer Introduction\n this.migrations.push({\n fromVersion: \"0.8.4\",\n toVersion: \"0.9.0\",\n description: \"Product Layer Introduction (Spec v0.9.0)\",\n apply: async (model: Model) => {\n const fs = await import(\"fs/promises\");\n const pathModule = await import(\"path\");\n\n const modelDir = `${model.rootPath}/documentation-robotics/model`;\n let filesModified = 0;\n\n try {\n // Verify model directory exists\n await fs.access(modelDir);\n } catch (error) {\n const err = error as { code?: string };\n // ENOENT means the directory doesn't exist - nothing to migrate\n if (err.code === \"ENOENT\") {\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description:\n \"Spec version updated to 0.9.0 (Product layer introduced; no model directories to migrate)\",\n };\n }\n // Re-throw other errors (e.g., EACCES for permission denied)\n throw error;\n }\n\n // Rename existing layer directories: shift from 03-12 to 04-13\n // Must do this in reverse order (12\u219213, 11\u219212, ..., 03\u219204) to avoid conflicts\n const renameMap = [\n [\"12_testing\", \"13_testing\"],\n [\"11_apm\", \"12_apm\"],\n [\"10_navigation\", \"11_navigation\"],\n [\"09_ux\", \"10_ux\"],\n [\"08_data-store\", \"09_data-store\"],\n [\"07_data-model\", \"08_data-model\"],\n [\"06_api\", \"07_api\"],\n [\"05_technology\", \"06_technology\"],\n [\"04_application\", \"05_application\"],\n [\"03_security\", \"04_security\"],\n ];\n\n const renameErrors: string[] = [];\n\n for (const [oldName, newName] of renameMap) {\n const oldPath = pathModule.join(modelDir, oldName);\n const newPath = pathModule.join(modelDir, newName);\n\n try {\n await fs.rename(oldPath, newPath);\n filesModified++;\n } catch (error) {\n const err = error as { code?: string };\n // ENOENT is expected when a directory hasn't been created yet\n if (err.code === \"ENOENT\") {\n continue;\n }\n // Track non-ENOENT errors to report them in the result\n renameErrors.push(\n `Failed to rename ${oldName} to ${newName}: ${getErrorMessage(error)}`\n );\n }\n }\n\n // If there were any rename failures, include them in the error field\n if (renameErrors.length > 0) {\n return {\n migrationsApplied: 1,\n filesModified,\n description:\n \"Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)\",\n error: renameErrors.join(\"; \"),\n };\n }\n\n // Create new product layer directory (it will be empty for now)\n // recursive: true handles EEXIST, so no catch needed\n const productPath = pathModule.join(modelDir, \"03_product\");\n await fs.mkdir(productPath, { recursive: true });\n\n return {\n migrationsApplied: 1,\n filesModified,\n description:\n \"Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)\",\n };\n },\n });\n }\n\n /**\n * Get the latest available specification version\n */\n getLatestVersion(): string {\n if (this.migrations.length === 0) {\n return \"0.7.0\";\n }\n\n // Return the highest toVersion from all migrations\n const versions = this.migrations.map((m) => m.toVersion);\n return versions.sort().reverse()[0];\n }\n\n /**\n * Get the sequence of migrations needed from one version to another\n */\n getMigrationPath(fromVersion: string, toVersion?: string): Migration[] {\n const targetVersion = toVersion || this.getLatestVersion();\n\n if (this.compareVersions(fromVersion, targetVersion) >= 0) {\n return [];\n }\n\n const path: Migration[] = [];\n let current = fromVersion;\n\n // Sort migrations by fromVersion\n const sortedMigrations = [...this.migrations].sort((a, b) =>\n this.compareVersions(a.fromVersion, b.fromVersion)\n );\n\n for (const migration of sortedMigrations) {\n // Check if this migration is in our path\n if (\n this.compareVersions(current, migration.fromVersion) >= 0 &&\n this.compareVersions(current, migration.toVersion) < 0 &&\n this.compareVersions(migration.toVersion, targetVersion) <= 0\n ) {\n path.push(migration);\n current = migration.toVersion;\n }\n }\n\n return path;\n }\n\n /**\n * Check if a model requires migration\n */\n requiresMigration(currentVersion: string): boolean {\n const path = this.getMigrationPath(currentVersion);\n return path.length > 0;\n }\n\n /**\n * Apply all migrations in sequence\n */\n async applyMigrations(\n model: Model,\n options: {\n fromVersion: string;\n toVersion?: string;\n dryRun?: boolean;\n validate?: boolean;\n }\n ): Promise<ApplyMigrationsResult> {\n const path = this.getMigrationPath(options.fromVersion, options.toVersion);\n const targetVersion = options.toVersion || this.getLatestVersion();\n\n if (path.length === 0) {\n return {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n }\n\n const results: ApplyMigrationsResult = {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n\n for (const migration of path) {\n if (!options.dryRun) {\n try {\n const result = await migration.apply(model);\n\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n changes: result,\n });\n\n results.totalChanges += result.migrationsApplied;\n\n // Only update model's spec version if migration succeeded (no error field)\n if (!result.error) {\n model.manifest.specVersion = migration.toVersion;\n } else {\n // If migration has errors, throw to prevent partial success state\n throw new Error(result.error);\n }\n } catch (error) {\n throw new Error(\n `Migration ${migration.fromVersion} \u2192 ${migration.toVersion} failed: ${\n getErrorMessage(error)\n }`\n );\n }\n } else {\n // Dry run - just record what would happen\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n dryRun: true,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Get a summary of migrations that would be applied\n */\n getMigrationSummary(\n fromVersion: string,\n toVersion?: string\n ): {\n currentVersion: string;\n targetVersion: string;\n migrationsNeeded: number;\n migrations: Array<{\n from: string;\n to: string;\n description: string;\n }>;\n } {\n const path = this.getMigrationPath(fromVersion, toVersion);\n const targetVersion = toVersion || this.getLatestVersion();\n\n return {\n currentVersion: fromVersion,\n targetVersion,\n migrationsNeeded: path.length,\n migrations: path.map((m) => ({\n from: m.fromVersion,\n to: m.toVersion,\n description: m.description,\n })),\n };\n }\n\n /**\n * Compare two version strings\n * Returns: -1 if a < b, 0 if a == b, 1 if a > b\n */\n private compareVersions(a: string, b: string): number {\n const aParts = a.split(\".\").map(Number);\n const bParts = b.split(\".\").map(Number);\n\n const maxLength = Math.max(aParts.length, bParts.length);\n\n for (let i = 0; i < maxLength; i++) {\n const aPart = aParts[i] || 0;\n const bPart = bParts[i] || 0;\n\n if (aPart < bPart) return -1;\n if (aPart > bPart) return 1;\n }\n\n return 0;\n }\n}\n\n/**\n * Result of applying multiple migrations\n */\nexport interface ApplyMigrationsResult {\n applied: Array<{\n from: string;\n to: string;\n description: string;\n changes?: MigrationResult;\n dryRun?: boolean;\n }>;\n currentVersion: string;\n targetVersion: string;\n totalChanges: number;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -314,22 +314,9 @@ class Model {
314
314
  },
315
315
  documentation: ".dr/README.md",
316
316
  layers: {}
317
- }, layerOrder = [
318
- "motivation",
319
- "business",
320
- "security",
321
- "application",
322
- "technology",
323
- "api",
324
- "data-model",
325
- "data-store",
326
- "ux",
327
- "navigation",
328
- "apm",
329
- "testing"
330
- ];
331
- for (let i = 0; i < layerOrder.length; i++) {
332
- const layerName = layerOrder[i], layer = this.layers.get(layerName), orderNum = String(i + 1).padStart(2, "0");
317
+ };
318
+ for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {
319
+ const layerName = CANONICAL_LAYER_NAMES[i], layer = this.layers.get(layerName), orderNum = String(i + 1).padStart(2, "0");
333
320
  yamlData.layers[layerName] = {
334
321
  order: i + 1,
335
322
  name: layerName.charAt(0).toUpperCase() + layerName.slice(1).replace("-", " "),