@documentation-robotics/cli 0.2.0 → 0.3.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 (269) hide show
  1. package/README.md +36 -2
  2. package/dist/analysis/relationship-classifier.js +2 -9
  3. package/dist/analysis/relationship-classifier.js.map +2 -2
  4. package/dist/analyzers/call-graph-utils.js +2 -2
  5. package/dist/analyzers/call-graph-utils.js.map +2 -2
  6. package/dist/analyzers/cards/build.js +171 -0
  7. package/dist/analyzers/cards/build.js.map +7 -0
  8. package/dist/analyzers/cards/classify.js +42 -0
  9. package/dist/analyzers/cards/classify.js.map +7 -0
  10. package/dist/analyzers/cards/edge-index.js +44 -0
  11. package/dist/analyzers/cards/edge-index.js.map +7 -0
  12. package/dist/analyzers/cards/facts.js +46 -0
  13. package/dist/analyzers/cards/facts.js.map +7 -0
  14. package/dist/analyzers/cards/graph.js +42 -0
  15. package/dist/analyzers/cards/graph.js.map +7 -0
  16. package/dist/analyzers/cards/index.js +8 -0
  17. package/dist/analyzers/cards/index.js.map +7 -0
  18. package/dist/analyzers/cards/tier0.js +18 -0
  19. package/dist/analyzers/cards/tier0.js.map +7 -0
  20. package/dist/analyzers/cards/types.js +1 -0
  21. package/dist/analyzers/cards/types.js.map +7 -0
  22. package/dist/analyzers/cbm-analyzer.js +195 -171
  23. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  24. package/dist/analyzers/cbm-responses.js +336 -0
  25. package/dist/analyzers/cbm-responses.js.map +7 -0
  26. package/dist/analyzers/edge-relationships.js +45 -0
  27. package/dist/analyzers/edge-relationships.js.map +7 -0
  28. package/dist/analyzers/mapping-loader.js +9 -0
  29. package/dist/analyzers/mapping-loader.js.map +2 -2
  30. package/dist/analyzers/tier0-rules.js +153 -0
  31. package/dist/analyzers/tier0-rules.js.map +7 -0
  32. package/dist/analyzers/types.js.map +1 -1
  33. package/dist/audit/relationships/ai/prompts.js +10 -10
  34. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  35. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  36. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  37. package/dist/cli.js +12 -2
  38. package/dist/cli.js.map +2 -2
  39. package/dist/coding-agents/default-permissions.js +31 -1
  40. package/dist/coding-agents/default-permissions.js.map +2 -2
  41. package/dist/coding-agents/drbot-mcp.js +167 -0
  42. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  43. package/dist/commands/add.js +3 -2
  44. package/dist/commands/add.js.map +2 -2
  45. package/dist/commands/analyzer.js +117 -14
  46. package/dist/commands/analyzer.js.map +2 -2
  47. package/dist/commands/changeset-verify.js +55 -0
  48. package/dist/commands/changeset-verify.js.map +7 -0
  49. package/dist/commands/changeset.js +43 -22
  50. package/dist/commands/changeset.js.map +2 -2
  51. package/dist/commands/delete.js +10 -4
  52. package/dist/commands/delete.js.map +2 -2
  53. package/dist/commands/farm.js +176 -60
  54. package/dist/commands/farm.js.map +2 -2
  55. package/dist/commands/mcp.js +19 -4
  56. package/dist/commands/mcp.js.map +2 -2
  57. package/dist/commands/relationship.js +207 -26
  58. package/dist/commands/relationship.js.map +2 -2
  59. package/dist/commands/update.js +42 -23
  60. package/dist/commands/update.js.map +2 -2
  61. package/dist/commands/upgrade.js +99 -11
  62. package/dist/commands/upgrade.js.map +3 -3
  63. package/dist/commands/version.js +1 -1
  64. package/dist/commands/visualize.js +4 -3
  65. package/dist/commands/visualize.js.map +2 -2
  66. package/dist/core/catalog-migration.js +33 -0
  67. package/dist/core/catalog-migration.js.map +7 -0
  68. package/dist/core/changeset-analysis.js +421 -0
  69. package/dist/core/changeset-analysis.js.map +7 -0
  70. package/dist/core/changeset-base-index.js +153 -0
  71. package/dist/core/changeset-base-index.js.map +7 -0
  72. package/dist/core/changeset-brief-delta.js +102 -0
  73. package/dist/core/changeset-brief-delta.js.map +7 -0
  74. package/dist/core/changeset-graph.js +281 -0
  75. package/dist/core/changeset-graph.js.map +7 -0
  76. package/dist/core/changeset-only-guard.js +32 -0
  77. package/dist/core/changeset-only-guard.js.map +7 -0
  78. package/dist/core/changeset-summary.js +68 -0
  79. package/dist/core/changeset-summary.js.map +7 -0
  80. package/dist/core/changeset-validator.js +66 -20
  81. package/dist/core/changeset-validator.js.map +2 -2
  82. package/dist/core/changeset-verify.js +518 -0
  83. package/dist/core/changeset-verify.js.map +7 -0
  84. package/dist/core/changeset.js +56 -9
  85. package/dist/core/changeset.js.map +2 -2
  86. package/dist/core/element-type-migration.js +145 -0
  87. package/dist/core/element-type-migration.js.map +7 -0
  88. package/dist/core/farm-manifest.js +12 -6
  89. package/dist/core/farm-manifest.js.map +2 -2
  90. package/dist/core/farm-sync-engine.js +1 -1
  91. package/dist/core/farm-sync-engine.js.map +2 -2
  92. package/dist/core/farm-sync-state.js +10 -2
  93. package/dist/core/farm-sync-state.js.map +2 -2
  94. package/dist/core/migration-registry.js +75 -46
  95. package/dist/core/migration-registry.js.map +2 -2
  96. package/dist/core/model-upgrade.js +102 -0
  97. package/dist/core/model-upgrade.js.map +7 -0
  98. package/dist/core/model.js +82 -30
  99. package/dist/core/model.js.map +2 -2
  100. package/dist/core/mutation-handler.js +71 -6
  101. package/dist/core/mutation-handler.js.map +2 -2
  102. package/dist/core/relationship-migration-table.js +6639 -0
  103. package/dist/core/relationship-migration-table.js.map +7 -0
  104. package/dist/core/relationship-migration.js +272 -0
  105. package/dist/core/relationship-migration.js.map +7 -0
  106. package/dist/core/relationship-prune.js +49 -0
  107. package/dist/core/relationship-prune.js.map +7 -0
  108. package/dist/core/relationship-staging.js +68 -0
  109. package/dist/core/relationship-staging.js.map +7 -0
  110. package/dist/core/relationships.js +26 -4
  111. package/dist/core/relationships.js.map +2 -2
  112. package/dist/core/report-data-model.js +7 -13
  113. package/dist/core/report-data-model.js.map +2 -2
  114. package/dist/core/staged-changeset-storage.js +64 -17
  115. package/dist/core/staged-changeset-storage.js.map +2 -2
  116. package/dist/core/staged-source-reference.js +28 -0
  117. package/dist/core/staged-source-reference.js.map +7 -0
  118. package/dist/core/staging-area.js +83 -18
  119. package/dist/core/staging-area.js.map +2 -2
  120. package/dist/core/virtual-projection.js +13 -5
  121. package/dist/core/virtual-projection.js.map +2 -2
  122. package/dist/export/archimate-exporter.js +1 -0
  123. package/dist/export/archimate-exporter.js.map +2 -2
  124. package/dist/generated/compiled-validators.js +2 -2
  125. package/dist/generated/compiled-validators.js.map +2 -2
  126. package/dist/generated/layer-registry.js +16 -16
  127. package/dist/generated/layer-registry.js.map +2 -2
  128. package/dist/generated/node-types.js +186 -168
  129. package/dist/generated/node-types.js.map +2 -2
  130. package/dist/generated/relationship-index.js +2236 -7790
  131. package/dist/generated/relationship-index.js.map +2 -2
  132. package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
  133. package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
  134. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  135. package/dist/integrations/claude_code/commands/dr-map.md +13 -0
  136. package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
  137. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  138. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  139. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  140. package/dist/integrations/claude_code/docs/MCP_SERVER.md +26 -2
  141. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  142. package/dist/integrations/claude_code/docs/README.md +48 -1
  143. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  144. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  145. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  146. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  147. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  148. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  149. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  150. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  151. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  152. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  153. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  154. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  155. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
  156. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
  157. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
  158. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
  159. package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
  160. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  161. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  162. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  163. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  164. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  165. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  166. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  167. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  168. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  169. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  170. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  171. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  172. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
  173. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
  174. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  175. package/dist/integrations/hash-utils.js.map +2 -2
  176. package/dist/mcp/api-key-manager.js +14 -1
  177. package/dist/mcp/api-key-manager.js.map +2 -2
  178. package/dist/mcp/changeset-only.js +34 -0
  179. package/dist/mcp/changeset-only.js.map +7 -0
  180. package/dist/mcp/tool-registry.js +19 -3
  181. package/dist/mcp/tool-registry.js.map +2 -2
  182. package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
  183. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  184. package/dist/mcp/tools/changeset-editing.js +184 -0
  185. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  186. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  187. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  188. package/dist/mcp/tools/changeset-shared.js +31 -0
  189. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  190. package/dist/mcp/tools/changeset-show.js +17 -1
  191. package/dist/mcp/tools/changeset-show.js.map +2 -2
  192. package/dist/mcp/tools/changeset-verify.js +43 -0
  193. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  194. package/dist/mcp/tools/index.js +19 -1
  195. package/dist/mcp/tools/index.js.map +2 -2
  196. package/dist/mcp/tools/model-add.js +6 -5
  197. package/dist/mcp/tools/model-add.js.map +2 -2
  198. package/dist/mcp/tools/model-delete.js +2 -2
  199. package/dist/mcp/tools/model-delete.js.map +2 -2
  200. package/dist/mcp/tools/model-search.js +14 -0
  201. package/dist/mcp/tools/model-search.js.map +2 -2
  202. package/dist/mcp/tools/model-show.js +29 -2
  203. package/dist/mcp/tools/model-show.js.map +2 -2
  204. package/dist/mcp/tools/model-trace.js +93 -30
  205. package/dist/mcp/tools/model-trace.js.map +2 -2
  206. package/dist/mcp/tools/model-update.js +35 -19
  207. package/dist/mcp/tools/model-update.js.map +2 -2
  208. package/dist/mcp/tools/relationship-tools.js +98 -0
  209. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  210. package/dist/mcp/tools/shared.js +25 -2
  211. package/dist/mcp/tools/shared.js.map +2 -2
  212. package/dist/mcp/tools/spec-type.js +90 -0
  213. package/dist/mcp/tools/spec-type.js.map +7 -0
  214. package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
  215. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  216. package/dist/schemas/bundled/api.json +1029 -1658
  217. package/dist/schemas/bundled/apm.json +499 -734
  218. package/dist/schemas/bundled/application.json +735 -371
  219. package/dist/schemas/bundled/base.json +3 -220
  220. package/dist/schemas/bundled/business.json +452 -366
  221. package/dist/schemas/bundled/data-model.json +395 -411
  222. package/dist/schemas/bundled/data-store.json +334 -558
  223. package/dist/schemas/bundled/manifest.json +18 -18
  224. package/dist/schemas/bundled/motivation.json +349 -368
  225. package/dist/schemas/bundled/navigation.json +415 -731
  226. package/dist/schemas/bundled/product.json +325 -159
  227. package/dist/schemas/bundled/security.json +864 -1232
  228. package/dist/schemas/bundled/technology.json +574 -703
  229. package/dist/schemas/bundled/testing.json +718 -1643
  230. package/dist/schemas/bundled/ux.json +739 -1352
  231. package/dist/server/schemas.js +8 -0
  232. package/dist/server/schemas.js.map +2 -2
  233. package/dist/server/server.js +218 -34
  234. package/dist/server/server.js.map +2 -2
  235. package/dist/server-entry.js +2 -1
  236. package/dist/server-entry.js.map +2 -2
  237. package/dist/telemetry/index.js +1 -1
  238. package/dist/utils/ansi-suppressor.js +6 -8
  239. package/dist/utils/ansi-suppressor.js.map +2 -2
  240. package/dist/utils/attribute-patch.js +41 -0
  241. package/dist/utils/attribute-patch.js.map +7 -0
  242. package/dist/utils/error-hints.js +29 -0
  243. package/dist/utils/error-hints.js.map +7 -0
  244. package/dist/utils/errors.js +9 -3
  245. package/dist/utils/errors.js.map +2 -2
  246. package/dist/utils/project-paths.js +26 -1
  247. package/dist/utils/project-paths.js.map +2 -2
  248. package/dist/utils/source-reference.js +14 -12
  249. package/dist/utils/source-reference.js.map +2 -2
  250. package/dist/utils/spec-version.js +2 -2
  251. package/dist/utils/spec-version.js.map +1 -1
  252. package/dist/validators/composed-reference-validator.js +5 -27
  253. package/dist/validators/composed-reference-validator.js.map +2 -2
  254. package/dist/validators/reference-validator.js +0 -7
  255. package/dist/validators/reference-validator.js.map +2 -2
  256. package/dist/validators/relationship-schema-validator.js +3 -3
  257. package/dist/validators/relationship-schema-validator.js.map +2 -2
  258. package/dist/validators/schema-validator.js +27 -9
  259. package/dist/validators/schema-validator.js.map +2 -2
  260. package/dist/validators/types.js.map +1 -1
  261. package/dist/viewer/README.md +6 -6
  262. package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
  263. package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
  264. package/dist/viewer/index.html +2 -2
  265. package/dist/viewer/manifest.json +10 -10
  266. package/dist/viewer/version.json +1 -1
  267. package/package.json +2 -1
  268. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  269. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/farm-manifest.ts"],
4
- "sourcesContent": ["/**\n * Farm Manifest - Manages farm.yaml configuration\n *\n * Represents a farm context containing multiple projects with detached model folders.\n * The farm manifest tracks project registrations and their model folder locations.\n */\n\nimport yaml from \"yaml\";\nimport { readFile, writeFile } from \"../utils/file-io.js\";\n\n/**\n * Represents a project registered in a farm\n */\nexport interface FarmProject {\n name: string;\n source: string; // Path to the codebase folder (relative to farm root)\n model: string; // Path to the model folder (relative to farm root)\n remote?: string; // Optional git remote URL\n branch?: string; // Optional git branch\n}\n\n// Validate project object has required fields and key/name consistency\nfunction validateFarmProject(project: any, projectKey: string): FarmProject {\n if (!project || typeof project !== \"object\") {\n throw new Error(\n `Invalid project entry \"${projectKey}\": expected object, got ${typeof project}`\n );\n }\n\n if (typeof project.name !== \"string\" || project.name === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'name' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (typeof project.source !== \"string\" || project.source === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'source' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (typeof project.model !== \"string\" || project.model === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'model' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (project.name !== projectKey) {\n throw new Error(\n `Project name mismatch: key is \"${projectKey}\" but project.name is \"${project.name}\". ` +\n `They must match for consistent sync state tracking.`\n );\n }\n\n return project as FarmProject;\n}\n\n/**\n * Farm manifest data structure\n */\nexport interface FarmManifestData {\n schema: string; // Schema version (e.g., \"dr-farm-v1\")\n name: string; // Human-readable farm name\n created?: string; // ISO 8601 timestamp\n modified?: string; // ISO 8601 timestamp\n projects: Record<string, FarmProject>; // Projects indexed by name\n platform_view?: boolean; // Optional: Enable platform view mode\n sync?: {\n track_commits?: boolean; // Optional: Track commits during sync\n };\n}\n\n/**\n * FarmManifest class - Load, validate, and save farm.yaml\n */\nexport class FarmManifest {\n schema: string;\n name: string;\n created: string;\n modified: string;\n projects: Map<string, FarmProject>;\n platform_view?: boolean;\n sync?: {\n track_commits?: boolean;\n };\n filePath?: string;\n\n constructor(data: FarmManifestData) {\n this.schema = data.schema;\n this.name = data.name;\n this.created = data.created ?? new Date().toISOString();\n this.modified = data.modified ?? new Date().toISOString();\n this.projects = new Map(Object.entries(data.projects || {}));\n this.platform_view = data.platform_view;\n this.sync = data.sync;\n }\n\n /**\n * Load farm manifest from file\n * @param filePath - Path to farm.yaml\n * @returns FarmManifest instance\n * @throws Error if manifest is malformed or projects lack required fields\n */\n static async load(filePath: string): Promise<FarmManifest> {\n const content = await readFile(filePath);\n const data = yaml.parse(content) as FarmManifestData;\n\n if (!data) {\n throw new Error(\n `Invalid farm manifest: file is empty or contains invalid YAML at ${filePath}`\n );\n }\n\n if (!data.schema) {\n throw new Error(\"Farm manifest must have a 'schema' field\");\n }\n\n if (!data.name) {\n throw new Error(\"Farm manifest must have a 'name' field\");\n }\n\n if (!data.projects) {\n data.projects = {};\n } else {\n // Validate each project has required fields before proceeding\n for (const [key, project] of Object.entries(data.projects)) {\n data.projects[key] = validateFarmProject(project, key);\n }\n }\n\n const manifest = new FarmManifest(data);\n manifest.filePath = filePath;\n return manifest;\n }\n\n /**\n * Create a new farm manifest\n * @param name - Farm name\n * @param options - Optional additional properties\n * @returns FarmManifest instance\n */\n static create(\n name: string,\n options: {\n platform_view?: boolean;\n sync?: {\n track_commits?: boolean;\n };\n } = {}\n ): FarmManifest {\n return new FarmManifest({\n schema: \"dr-farm-v1\",\n name,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n projects: {},\n platform_view: options.platform_view,\n sync: options.sync,\n });\n }\n\n /**\n * Save manifest to file\n * @param filePath - Path where to save farm.yaml\n */\n async save(filePath?: string): Promise<void> {\n const targetPath = filePath || this.filePath;\n if (!targetPath) {\n throw new Error(\"No file path specified for saving farm manifest\");\n }\n\n this.modified = new Date().toISOString();\n const data = this.toJSON();\n const yamlContent = yaml.stringify(data, { lineWidth: 0 });\n\n await writeFile(targetPath, yamlContent);\n this.filePath = targetPath;\n }\n\n // Add a project to the farm; ensures name parameter matches project.name\n addProject(name: string, project: FarmProject): void {\n if (project.name !== name) {\n throw new Error(\n `Project name mismatch: key is \"${name}\" but project.name is \"${project.name}\". ` +\n `They must match for consistent sync state tracking. ` +\n `Downstream code reads project.name for changeset names and sync state paths.`\n );\n }\n\n this.projects.set(name, project);\n this.modified = new Date().toISOString();\n }\n\n /**\n * Remove a project from the farm\n * @param name - Project name to remove\n * @returns true if project was removed, false if not found\n */\n removeProject(name: string): boolean {\n const existed = this.projects.has(name);\n this.projects.delete(name);\n if (existed) {\n this.modified = new Date().toISOString();\n }\n return existed;\n }\n\n /**\n * Get a project by name\n * @param name - Project name\n * @returns Project configuration or undefined\n */\n getProject(name: string): FarmProject | undefined {\n return this.projects.get(name);\n }\n\n /**\n * Get all projects\n * @returns Array of all projects\n */\n getAllProjects(): FarmProject[] {\n return Array.from(this.projects.values());\n }\n\n /**\n * Check if platform-view is enabled on this farm\n * @returns true if platform-view is enabled\n */\n isPlatformViewEnabled(): boolean {\n return this.platform_view === true;\n }\n\n /**\n * Serialize to JSON representation for saving\n */\n toJSON(): FarmManifestData {\n return {\n schema: this.schema,\n name: this.name,\n created: this.created,\n modified: this.modified,\n projects: Object.fromEntries(this.projects),\n ...(this.platform_view !== undefined && { platform_view: this.platform_view }),\n ...(this.sync && { sync: this.sync }),\n };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Farm Manifest - Manages farm.yaml configuration\n *\n * Represents a farm context containing multiple projects with detached model folders.\n * The farm manifest tracks project registrations and their model folder locations.\n */\n\nimport yaml from \"yaml\";\nimport { readFile, writeFile } from \"../utils/file-io.js\";\n\n/**\n * Represents a project registered in a farm\n */\nexport interface FarmProject {\n name: string;\n source: string; // Path to the codebase folder (relative to farm root)\n model: string; // Path to the model folder (relative to farm root)\n remote?: string; // Optional git remote URL\n branch?: string; // Optional git branch\n}\n\n// Validate project object has required fields and key/name consistency\nfunction validateFarmProject(project: unknown, projectKey: string): FarmProject {\n if (!project || typeof project !== \"object\") {\n throw new Error(\n `Invalid project entry \"${projectKey}\": expected object, got ${typeof project}`\n );\n }\n\n const p = project as Record<string, unknown>;\n\n if (typeof p.name !== \"string\" || p.name === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'name' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (typeof p.source !== \"string\" || p.source === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'source' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (typeof p.model !== \"string\" || p.model === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'model' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (p.name !== projectKey) {\n throw new Error(\n `Project name mismatch: key is \"${projectKey}\" but project.name is \"${p.name}\". ` +\n `They must match for consistent sync state tracking.`\n );\n }\n\n const validatedProject: FarmProject = {\n name: p.name,\n source: p.source,\n model: p.model,\n };\n\n if (typeof p.remote === \"string\") {\n validatedProject.remote = p.remote;\n }\n\n if (typeof p.branch === \"string\") {\n validatedProject.branch = p.branch;\n }\n\n return validatedProject;\n}\n\n/**\n * Farm manifest data structure\n */\nexport interface FarmManifestData {\n schema: string; // Schema version (e.g., \"dr-farm-v1\")\n name: string; // Human-readable farm name\n created?: string; // ISO 8601 timestamp\n modified?: string; // ISO 8601 timestamp\n projects: Record<string, FarmProject>; // Projects indexed by name\n platform_view?: boolean; // Optional: Enable platform view mode\n sync?: {\n track_commits?: boolean; // Optional: Track commits during sync\n };\n}\n\n/**\n * FarmManifest class - Load, validate, and save farm.yaml\n */\nexport class FarmManifest {\n schema: string;\n name: string;\n created: string;\n modified: string;\n projects: Map<string, FarmProject>;\n platform_view?: boolean;\n sync?: {\n track_commits?: boolean;\n };\n filePath?: string;\n\n constructor(data: FarmManifestData) {\n this.schema = data.schema;\n this.name = data.name;\n this.created = data.created ?? new Date().toISOString();\n this.modified = data.modified ?? new Date().toISOString();\n this.projects = new Map(Object.entries(data.projects || {}));\n this.platform_view = data.platform_view;\n this.sync = data.sync;\n }\n\n /**\n * Load farm manifest from file\n * @param filePath - Path to farm.yaml\n * @returns FarmManifest instance\n * @throws Error if manifest is malformed or projects lack required fields\n */\n static async load(filePath: string): Promise<FarmManifest> {\n const content = await readFile(filePath);\n const data = yaml.parse(content) as FarmManifestData;\n\n if (!data) {\n throw new Error(\n `Invalid farm manifest: file is empty or contains invalid YAML at ${filePath}`\n );\n }\n\n if (!data.schema) {\n throw new Error(\"Farm manifest must have a 'schema' field\");\n }\n\n if (!data.name) {\n throw new Error(\"Farm manifest must have a 'name' field\");\n }\n\n if (!data.projects) {\n data.projects = {};\n } else {\n // Validate each project has required fields before proceeding\n for (const [key, project] of Object.entries(data.projects)) {\n data.projects[key] = validateFarmProject(project, key);\n }\n }\n\n const manifest = new FarmManifest(data);\n manifest.filePath = filePath;\n return manifest;\n }\n\n /**\n * Create a new farm manifest\n * @param name - Farm name\n * @param options - Optional additional properties\n * @returns FarmManifest instance\n */\n static create(\n name: string,\n options: {\n platform_view?: boolean;\n sync?: {\n track_commits?: boolean;\n };\n } = {}\n ): FarmManifest {\n return new FarmManifest({\n schema: \"dr-farm-v1\",\n name,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n projects: {},\n platform_view: options.platform_view,\n sync: options.sync,\n });\n }\n\n /**\n * Save manifest to file\n * @param filePath - Path where to save farm.yaml\n */\n async save(filePath?: string): Promise<void> {\n const targetPath = filePath || this.filePath;\n if (!targetPath) {\n throw new Error(\"No file path specified for saving farm manifest\");\n }\n\n this.modified = new Date().toISOString();\n const data = this.toJSON();\n const yamlContent = yaml.stringify(data, { lineWidth: 0 });\n\n await writeFile(targetPath, yamlContent);\n this.filePath = targetPath;\n }\n\n // Add a project to the farm; ensures name parameter matches project.name\n addProject(name: string, project: FarmProject): void {\n if (project.name !== name) {\n throw new Error(\n `Project name mismatch: key is \"${name}\" but project.name is \"${project.name}\". ` +\n `They must match for consistent sync state tracking. ` +\n `Downstream code reads project.name for changeset names and sync state paths.`\n );\n }\n\n this.projects.set(name, project);\n this.modified = new Date().toISOString();\n }\n\n /**\n * Remove a project from the farm\n * @param name - Project name to remove\n * @returns true if project was removed, false if not found\n */\n removeProject(name: string): boolean {\n const existed = this.projects.has(name);\n this.projects.delete(name);\n if (existed) {\n this.modified = new Date().toISOString();\n }\n return existed;\n }\n\n /**\n * Get a project by name\n * @param name - Project name\n * @returns Project configuration or undefined\n */\n getProject(name: string): FarmProject | undefined {\n return this.projects.get(name);\n }\n\n /**\n * Get all projects\n * @returns Array of all projects\n */\n getAllProjects(): FarmProject[] {\n return Array.from(this.projects.values());\n }\n\n /**\n * Check if platform-view is enabled on this farm\n * @returns true if platform-view is enabled\n */\n isPlatformViewEnabled(): boolean {\n return this.platform_view === true;\n }\n\n /**\n * Serialize to JSON representation for saving\n */\n toJSON(): FarmManifestData {\n return {\n schema: this.schema,\n name: this.name,\n created: this.created,\n modified: this.modified,\n projects: Object.fromEntries(this.projects),\n ...(this.platform_view !== undefined && { platform_view: this.platform_view }),\n ...(this.sync && { sync: this.sync }),\n };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -157,7 +157,7 @@ class FarmSyncEngine {
157
157
  async generateChangeset(project, diff, mappings, options = {}) {
158
158
  if (!this.model)
159
159
  throw new Error("Model is required for changeset generation");
160
- const projectModelPath = path.join(this.farmRoot, project.model), stagingManager = new StagingAreaManager(projectModelPath, this.model), changesetName = `farm-sync-${project.name}-${Date.now()}`, changeset = await stagingManager.create(changesetName, `Auto-generated from farm sync of ${project.name}`);
160
+ const stagingManager = new StagingAreaManager(this.model.rootPath, this.model), changesetName = `farm-sync-${project.name}-${Date.now()}`, changeset = await stagingManager.create(changesetName, `Auto-generated from farm sync of ${project.name}`);
161
161
  if (!changeset.id)
162
162
  throw new Error("Failed to create changeset - no ID assigned");
163
163
  const warnings = [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/farm-sync-engine.ts"],
4
- "sourcesContent": ["/**\n * Farm Sync Engine - Orchestrates headless sync pipeline\n *\n * Implements:\n * - Git pull for tracked codebases\n * - Diff computation between sync commits\n * - File-to-element mapping via source references\n * - Changeset generation for affected elements\n * - Staged changeset integration\n */\n\nimport path from \"path\";\nimport { execSync, execFileSync } from \"child_process\";\nimport { fileExists, ensureDir } from \"../utils/file-io.js\";\nimport { FarmSyncState } from \"./farm-sync-state.js\";\nimport { Model } from \"./model.js\";\nimport { StagingAreaManager } from \"./staging-area.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport type { FarmProject } from \"./farm-manifest.js\";\n\n/**\n * Options for sync operation\n */\nexport interface SyncOptions {\n dryRun?: boolean; // Preview without creating changeset\n force?: boolean; // Proceed despite ambiguities\n verbose?: boolean; // Detailed output\n}\n\n/**\n * Result of file diff analysis\n */\nexport interface FileDiffResult {\n added: string[];\n modified: string[];\n deleted: string[];\n}\n\n/**\n * Mapping of file path to model elements\n */\nexport interface FileElementMapping {\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number; // 0-100\n sourceRef: {\n file: string;\n symbol?: string;\n };\n }>;\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n status: \"success\" | \"partial\" | \"failed\";\n projectName: string;\n commitsBefore: string;\n commitsAfter: string;\n filesChanged: FileDiffResult;\n elementMappings: FileElementMapping[];\n ambiguities: FileElementMapping[]; // Mappings with low confidence\n changesetId?: string; // ID of generated changeset if applicable\n changeCount: number; // Total staged changes\n notes: string[];\n dryRun?: boolean; // True if this was a dry run (no state changed)\n}\n\n/**\n * FarmSyncEngine - Orchestrates the sync pipeline\n * Model parameter is optional - only required for sync operations, not for pull\n */\nexport class FarmSyncEngine {\n private farmRoot: string;\n private model: Model | null;\n\n constructor(farmRoot: string, model?: Model) {\n this.farmRoot = farmRoot;\n this.model = model || null;\n }\n\n /**\n * Pull latest changes from remote for a codebase\n * @param codebasePath - Relative path to codebase folder\n * @returns Updated commit SHA\n */\n async pullCodebase(codebasePath: string): Promise<string> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n try {\n // Try to fetch latest if remote exists\n try {\n execSync(\"git fetch --prune\", { cwd: fullPath, stdio: \"pipe\" });\n } catch (fetchError) {\n const fetchMsg = getErrorMessage(fetchError);\n // Only treat \"no remote\" as acceptable; re-throw auth, merge, and network errors\n const isNoRemote = fetchMsg.includes(\"No remote repository specified\") ||\n fetchMsg.includes(\"no remote\");\n if (!isNoRemote) {\n throw new Error(`git fetch failed: ${fetchMsg}`);\n }\n }\n\n // Try to pull if remote exists\n try {\n execSync(\"git pull --no-rebase\", { cwd: fullPath, stdio: \"pipe\" });\n } catch (pullError) {\n const pullMsg = getErrorMessage(pullError);\n // Only treat \"no remote\" or \"no tracking information\" as acceptable; re-throw auth, merge, and network errors\n const isAcceptable = pullMsg.includes(\"No remote repository specified\") ||\n pullMsg.includes(\"no remote\") ||\n pullMsg.includes(\"no tracking information\");\n if (!isAcceptable) {\n throw new Error(`git pull failed: ${pullMsg}`);\n }\n }\n\n // Get current HEAD commit\n const commit = execSync(\"git rev-parse HEAD\", { cwd: fullPath, encoding: \"utf-8\" }).trim();\n\n return commit;\n } catch (error) {\n throw new Error(`Failed to pull codebase '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Get the current HEAD commit SHA for a codebase\n */\n async getCurrentCommit(codebasePath: string): Promise<string> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n try {\n const commit = execSync(\"git rev-parse HEAD\", { cwd: fullPath, encoding: \"utf-8\" }).trim();\n return commit;\n } catch (error) {\n throw new Error(`Failed to get current commit for '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Compute diff between two commits\n * Commit SHAs are validated before use to prevent command injection\n */\n async computeDiff(codebasePath: string, fromCommit: string, toCommit: string): Promise<FileDiffResult> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n // Validate commit SHAs to prevent command injection\n const commitShaRegex = /^[0-9a-f]{7,40}$/i;\n if (!commitShaRegex.test(fromCommit) || !commitShaRegex.test(toCommit)) {\n throw new Error(\n `Invalid commit SHA format. Expected 7-40 character hex string. Got: ${fromCommit}, ${toCommit}`\n );\n }\n\n try {\n // Get diff status between commits using execFileSync to avoid shell interpretation\n const diffOutput = execFileSync(\"git\", [\"diff\", \"--name-status\", `${fromCommit}...${toCommit}`], {\n cwd: fullPath,\n encoding: \"utf-8\",\n });\n\n const added: string[] = [];\n const modified: string[] = [];\n const deleted: string[] = [];\n\n for (const line of diffOutput.trim().split(\"\\n\")) {\n if (!line) continue;\n\n const [status, ...fileParts] = line.split(\"\\t\");\n const filePath = fileParts.join(\"\\t\"); // Handle paths with tabs\n\n switch (status) {\n case \"A\":\n added.push(filePath);\n break;\n case \"M\":\n modified.push(filePath);\n break;\n case \"D\":\n deleted.push(filePath);\n break;\n default:\n if (status.startsWith(\"R\")) {\n // Handle renames (R100, R087, R095, etc. - any similarity index)\n // Report as both a delete of the old name and add of the new name\n const [oldPath, newPath] = fileParts;\n deleted.push(oldPath);\n added.push(newPath);\n } else if (status.startsWith(\"C\")) {\n // Handle copies (C100, C087, etc. - any similarity index)\n // Report as add of new file\n added.push(fileParts[1]);\n }\n break;\n // Ignore other statuses\n }\n }\n\n return { added, modified, deleted };\n } catch (error) {\n throw new Error(`Failed to compute diff for '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Map changed files to model elements using source references\n * Looks at each model element's source references and matches against changed files\n * Returns empty results if model is not available\n */\n async mapFilesToElements(): Promise<{ confident: FileElementMapping[]; ambiguous: FileElementMapping[] }> {\n if (!this.model) {\n return { confident: [], ambiguous: [] };\n }\n\n const confident: Map<string, FileElementMapping> = new Map();\n const ambiguous: Map<string, FileElementMapping> = new Map();\n\n // Iterate through all layers and elements\n for (const layer of this.model.layers.values()) {\n for (const element of layer.listElements()) {\n if (!element.source_reference) continue;\n\n const sourceRef = element.source_reference;\n if (!sourceRef.locations || sourceRef.locations.length === 0) continue;\n\n // Match each source location against potential changed files\n for (const location of sourceRef.locations) {\n // Use file path as key\n const filePath = location.file;\n\n if (!confident.has(filePath) && !ambiguous.has(filePath)) {\n const mapping: FileElementMapping = {\n filePath,\n possibleElements: [\n {\n elementId: element.id,\n layer: layer.name,\n confidence: 100, // Exact match from source reference\n sourceRef: location,\n },\n ],\n };\n confident.set(filePath, mapping);\n } else if (confident.has(filePath)) {\n // Multiple elements reference the same file - mark as ambiguous\n const existing = confident.get(filePath)!;\n confident.delete(filePath);\n\n existing.possibleElements.push({\n elementId: element.id,\n layer: layer.name,\n confidence: 100,\n sourceRef: location,\n });\n\n ambiguous.set(filePath, existing);\n } else {\n // Already ambiguous, add another possibility\n const existing = ambiguous.get(filePath)!;\n existing.possibleElements.push({\n elementId: element.id,\n layer: layer.name,\n confidence: 100,\n sourceRef: location,\n });\n }\n }\n }\n }\n\n return {\n confident: Array.from(confident.values()),\n ambiguous: Array.from(ambiguous.values()),\n };\n }\n\n /**\n * Generate a changeset from file-to-element mappings\n * Creates add/update/delete changes based on the diff and mappings\n * Stages changes into the changeset for review before commit\n */\n async generateChangeset(\n project: FarmProject,\n diff: FileDiffResult,\n mappings: { confident: FileElementMapping[]; ambiguous: FileElementMapping[] },\n options: SyncOptions = {}\n ): Promise<{ changesetId: string; changeCount: number; warnings: string[] }> {\n if (!this.model) {\n throw new Error(\"Model is required for changeset generation\");\n }\n\n const projectModelPath = path.join(this.farmRoot, project.model);\n const stagingManager = new StagingAreaManager(projectModelPath, this.model);\n\n // Create a new changeset\n const changesetName = `farm-sync-${project.name}-${Date.now()}`;\n const changeset = await stagingManager.create(changesetName, `Auto-generated from farm sync of ${project.name}`);\n\n if (!changeset.id) {\n throw new Error(\"Failed to create changeset - no ID assigned\");\n }\n\n const warnings: string[] = [];\n let changeCount = 0;\n\n // Create a map of file path -> confident mapping for quick lookup\n const confidentMap = new Map(mappings.confident.map((m) => [m.filePath, m]));\n\n // Process added files\n for (const filePath of diff.added) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"add\",\n elementId: element.elementId,\n layerName: element.layer,\n after: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n timestamp: new Date().toISOString(),\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(\n `File added: ${filePath} -> ${element.elementId} (staged for review, verify content accuracy)`\n );\n changeCount++;\n }\n }\n\n // Process modified files\n for (const filePath of diff.modified) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"update\",\n elementId: element.elementId,\n layerName: element.layer,\n before: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n },\n after: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n timestamp: new Date().toISOString(),\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(`File modified: ${filePath} -> ${element.elementId} (staged for review)`);\n changeCount++;\n }\n }\n\n // Process deleted files\n for (const filePath of diff.deleted) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"delete\",\n elementId: element.elementId,\n layerName: element.layer,\n before: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(`File deleted: ${filePath} -> ${element.elementId} (staged for review)`);\n changeCount++;\n }\n }\n\n // Flag ambiguities\n if (mappings.ambiguous.length > 0 && !options.force) {\n for (const ambiguity of mappings.ambiguous) {\n warnings.push(\n `Ambiguous mapping: ${ambiguity.filePath} -> ${ambiguity.possibleElements.map((e) => e.elementId).join(\", \")} (skipped, use --force to proceed)`\n );\n }\n }\n\n return {\n changesetId: changeset.id,\n changeCount,\n warnings,\n };\n }\n\n /**\n * Execute a complete sync operation for a project\n */\n async syncProject(project: FarmProject, options: SyncOptions = {}): Promise<SyncResult> {\n const codebasePath = project.source;\n const notes: string[] = [];\n\n // Step 1: Get current commit (pull only if not dry run)\n if (options.verbose && !options.dryRun) {\n notes.push(`Pulling latest from ${codebasePath}...`);\n }\n const currentCommit = options.dryRun\n ? await this.getCurrentCommit(codebasePath)\n : await this.pullCodebase(codebasePath);\n if (options.dryRun) {\n notes.push(\"(DRY RUN - skipping git pull)\");\n }\n\n // Step 2: Load sync state\n const syncStatePath = path.join(this.farmRoot, project.model, \".farm-sync.yaml\");\n await ensureDir(path.dirname(syncStatePath));\n const syncState = await FarmSyncState.loadOrCreate(syncStatePath, project.name);\n\n const previousCommit = syncState.lastSyncCommit;\n notes.push(`Current commit: ${currentCommit}`);\n notes.push(`Previous sync commit: ${previousCommit || \"none\"}`);\n\n // Step 3: If no previous sync, this is initial sync - just record state\n if (!previousCommit) {\n notes.push(\"Initial sync - recording baseline only\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: \"success\",\n files_changed: 0,\n elements_affected: 0,\n notes: \"Initial sync, no changes to sync\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"success\",\n projectName: project.name,\n commitsBefore: \"none\",\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: { added: [], modified: [], deleted: [] },\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n // Step 4: Compute diff\n if (options.verbose) {\n notes.push(`Computing diff between ${previousCommit.substring(0, 8)} and ${currentCommit.substring(0, 8)}...`);\n }\n const diff = await this.computeDiff(codebasePath, previousCommit, currentCommit);\n\n // If no changes, return early\n if (diff.added.length === 0 && diff.modified.length === 0 && diff.deleted.length === 0) {\n notes.push(\"No changes detected\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: \"success\",\n files_changed: 0,\n elements_affected: 0,\n notes: \"No changes to sync\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"success\",\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n notes.push(\n `Files changed: ${diff.added.length} added, ${diff.modified.length} modified, ${diff.deleted.length} deleted`\n );\n\n // Step 5: Check that model is available before attempting to map files\n if (!this.model) {\n notes.push(\"CRITICAL: Model is not loaded, cannot map files to elements\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: previousCommit, // Don't advance lastSyncCommit - stay at previous commit\n status: \"failed\",\n files_changed: diff.added.length + diff.modified.length + diff.deleted.length,\n elements_affected: 0,\n notes: \"Model not loaded - file changes not synced. Load the model and retry to process these files.\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"failed\",\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n // Step 6: Map files to elements\n if (options.verbose) {\n notes.push(\"Mapping changed files to model elements...\");\n }\n const mappings = await this.mapFilesToElements();\n\n // Step 7: Record ambiguities in sync state\n if (mappings.ambiguous.length > 0) {\n syncState.recordAmbiguities(\n mappings.ambiguous.map((m) => ({\n filePath: m.filePath,\n possibleElements: m.possibleElements.map((e) => ({\n elementId: e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n }))\n );\n notes.push(`Ambiguities detected: ${mappings.ambiguous.length} files`);\n }\n\n // Step 8: Generate changeset\n let changesetId: string | undefined;\n let changeCount = 0;\n\n if (!options.dryRun) {\n const changesetResult = await this.generateChangeset(project, diff, mappings, options);\n changesetId = changesetResult.changesetId;\n changeCount = changesetResult.changeCount;\n notes.push(...changesetResult.warnings);\n }\n\n // Step 9: Record sync in state (skip for dry run)\n const syncStatus = mappings.ambiguous.length > 0 && !options.force ? \"partial\" : \"success\";\n\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: syncStatus,\n changeset: changesetId,\n files_changed: diff.added.length + diff.modified.length + diff.deleted.length,\n elements_affected: mappings.confident.length + mappings.ambiguous.length,\n notes: notes.join(\"\\n\"),\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: syncStatus,\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: mappings.confident,\n ambiguities: mappings.ambiguous,\n changesetId,\n changeCount,\n notes,\n dryRun: options.dryRun,\n };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Farm Sync Engine - Orchestrates headless sync pipeline\n *\n * Implements:\n * - Git pull for tracked codebases\n * - Diff computation between sync commits\n * - File-to-element mapping via source references\n * - Changeset generation for affected elements\n * - Staged changeset integration\n */\n\nimport path from \"path\";\nimport { execSync, execFileSync } from \"child_process\";\nimport { fileExists, ensureDir } from \"../utils/file-io.js\";\nimport { FarmSyncState } from \"./farm-sync-state.js\";\nimport { Model } from \"./model.js\";\nimport { StagingAreaManager } from \"./staging-area.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport type { FarmProject } from \"./farm-manifest.js\";\n\n/**\n * Options for sync operation\n */\nexport interface SyncOptions {\n dryRun?: boolean; // Preview without creating changeset\n force?: boolean; // Proceed despite ambiguities\n verbose?: boolean; // Detailed output\n}\n\n/**\n * Result of file diff analysis\n */\nexport interface FileDiffResult {\n added: string[];\n modified: string[];\n deleted: string[];\n}\n\n/**\n * Mapping of file path to model elements\n */\nexport interface FileElementMapping {\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number; // 0-100\n sourceRef: {\n file: string;\n symbol?: string;\n };\n }>;\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n status: \"success\" | \"partial\" | \"failed\";\n projectName: string;\n commitsBefore: string;\n commitsAfter: string;\n filesChanged: FileDiffResult;\n elementMappings: FileElementMapping[];\n ambiguities: FileElementMapping[]; // Mappings with low confidence\n changesetId?: string; // ID of generated changeset if applicable\n changeCount: number; // Total staged changes\n notes: string[];\n dryRun?: boolean; // True if this was a dry run (no state changed)\n}\n\n/**\n * FarmSyncEngine - Orchestrates the sync pipeline\n * Model parameter is optional - only required for sync operations, not for pull\n */\nexport class FarmSyncEngine {\n private farmRoot: string;\n private model: Model | null;\n\n constructor(farmRoot: string, model?: Model) {\n this.farmRoot = farmRoot;\n this.model = model || null;\n }\n\n /**\n * Pull latest changes from remote for a codebase\n * @param codebasePath - Relative path to codebase folder\n * @returns Updated commit SHA\n */\n async pullCodebase(codebasePath: string): Promise<string> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n try {\n // Try to fetch latest if remote exists\n try {\n execSync(\"git fetch --prune\", { cwd: fullPath, stdio: \"pipe\" });\n } catch (fetchError) {\n const fetchMsg = getErrorMessage(fetchError);\n // Only treat \"no remote\" as acceptable; re-throw auth, merge, and network errors\n const isNoRemote = fetchMsg.includes(\"No remote repository specified\") ||\n fetchMsg.includes(\"no remote\");\n if (!isNoRemote) {\n throw new Error(`git fetch failed: ${fetchMsg}`);\n }\n }\n\n // Try to pull if remote exists\n try {\n execSync(\"git pull --no-rebase\", { cwd: fullPath, stdio: \"pipe\" });\n } catch (pullError) {\n const pullMsg = getErrorMessage(pullError);\n // Only treat \"no remote\" or \"no tracking information\" as acceptable; re-throw auth, merge, and network errors\n const isAcceptable = pullMsg.includes(\"No remote repository specified\") ||\n pullMsg.includes(\"no remote\") ||\n pullMsg.includes(\"no tracking information\");\n if (!isAcceptable) {\n throw new Error(`git pull failed: ${pullMsg}`);\n }\n }\n\n // Get current HEAD commit\n const commit = execSync(\"git rev-parse HEAD\", { cwd: fullPath, encoding: \"utf-8\" }).trim();\n\n return commit;\n } catch (error) {\n throw new Error(`Failed to pull codebase '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Get the current HEAD commit SHA for a codebase\n */\n async getCurrentCommit(codebasePath: string): Promise<string> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n try {\n const commit = execSync(\"git rev-parse HEAD\", { cwd: fullPath, encoding: \"utf-8\" }).trim();\n return commit;\n } catch (error) {\n throw new Error(`Failed to get current commit for '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Compute diff between two commits\n * Commit SHAs are validated before use to prevent command injection\n */\n async computeDiff(codebasePath: string, fromCommit: string, toCommit: string): Promise<FileDiffResult> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n // Validate commit SHAs to prevent command injection\n const commitShaRegex = /^[0-9a-f]{7,40}$/i;\n if (!commitShaRegex.test(fromCommit) || !commitShaRegex.test(toCommit)) {\n throw new Error(\n `Invalid commit SHA format. Expected 7-40 character hex string. Got: ${fromCommit}, ${toCommit}`\n );\n }\n\n try {\n // Get diff status between commits using execFileSync to avoid shell interpretation\n const diffOutput = execFileSync(\"git\", [\"diff\", \"--name-status\", `${fromCommit}...${toCommit}`], {\n cwd: fullPath,\n encoding: \"utf-8\",\n });\n\n const added: string[] = [];\n const modified: string[] = [];\n const deleted: string[] = [];\n\n for (const line of diffOutput.trim().split(\"\\n\")) {\n if (!line) continue;\n\n const [status, ...fileParts] = line.split(\"\\t\");\n const filePath = fileParts.join(\"\\t\"); // Handle paths with tabs\n\n switch (status) {\n case \"A\":\n added.push(filePath);\n break;\n case \"M\":\n modified.push(filePath);\n break;\n case \"D\":\n deleted.push(filePath);\n break;\n default:\n if (status.startsWith(\"R\")) {\n // Handle renames (R100, R087, R095, etc. - any similarity index)\n // Report as both a delete of the old name and add of the new name\n const [oldPath, newPath] = fileParts;\n deleted.push(oldPath);\n added.push(newPath);\n } else if (status.startsWith(\"C\")) {\n // Handle copies (C100, C087, etc. - any similarity index)\n // Report as add of new file\n added.push(fileParts[1]);\n }\n break;\n // Ignore other statuses\n }\n }\n\n return { added, modified, deleted };\n } catch (error) {\n throw new Error(`Failed to compute diff for '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Map changed files to model elements using source references\n * Looks at each model element's source references and matches against changed files\n * Returns empty results if model is not available\n */\n async mapFilesToElements(): Promise<{ confident: FileElementMapping[]; ambiguous: FileElementMapping[] }> {\n if (!this.model) {\n return { confident: [], ambiguous: [] };\n }\n\n const confident: Map<string, FileElementMapping> = new Map();\n const ambiguous: Map<string, FileElementMapping> = new Map();\n\n // Iterate through all layers and elements\n for (const layer of this.model.layers.values()) {\n for (const element of layer.listElements()) {\n if (!element.source_reference) continue;\n\n const sourceRef = element.source_reference;\n if (!sourceRef.locations || sourceRef.locations.length === 0) continue;\n\n // Match each source location against potential changed files\n for (const location of sourceRef.locations) {\n // Use file path as key\n const filePath = location.file;\n\n if (!confident.has(filePath) && !ambiguous.has(filePath)) {\n const mapping: FileElementMapping = {\n filePath,\n possibleElements: [\n {\n elementId: element.id,\n layer: layer.name,\n confidence: 100, // Exact match from source reference\n sourceRef: location,\n },\n ],\n };\n confident.set(filePath, mapping);\n } else if (confident.has(filePath)) {\n // Multiple elements reference the same file - mark as ambiguous\n const existing = confident.get(filePath)!;\n confident.delete(filePath);\n\n existing.possibleElements.push({\n elementId: element.id,\n layer: layer.name,\n confidence: 100,\n sourceRef: location,\n });\n\n ambiguous.set(filePath, existing);\n } else {\n // Already ambiguous, add another possibility\n const existing = ambiguous.get(filePath)!;\n existing.possibleElements.push({\n elementId: element.id,\n layer: layer.name,\n confidence: 100,\n sourceRef: location,\n });\n }\n }\n }\n }\n\n return {\n confident: Array.from(confident.values()),\n ambiguous: Array.from(ambiguous.values()),\n };\n }\n\n /**\n * Generate a changeset from file-to-element mappings\n * Creates add/update/delete changes based on the diff and mappings\n * Stages changes into the changeset for review before commit\n */\n async generateChangeset(\n project: FarmProject,\n diff: FileDiffResult,\n mappings: { confident: FileElementMapping[]; ambiguous: FileElementMapping[] },\n options: SyncOptions = {}\n ): Promise<{ changesetId: string; changeCount: number; warnings: string[] }> {\n if (!this.model) {\n throw new Error(\"Model is required for changeset generation\");\n }\n\n // The staging area lives under the model's project root. For a model the farm\n // scaffolded that is <farm>/<model>; for one adopted from a codebase it is the codebase\n // itself, so farm.yaml's `model` (the model directory) must not be used here.\n const stagingManager = new StagingAreaManager(this.model.rootPath, this.model);\n\n // Create a new changeset\n const changesetName = `farm-sync-${project.name}-${Date.now()}`;\n const changeset = await stagingManager.create(changesetName, `Auto-generated from farm sync of ${project.name}`);\n\n if (!changeset.id) {\n throw new Error(\"Failed to create changeset - no ID assigned\");\n }\n\n const warnings: string[] = [];\n let changeCount = 0;\n\n // Create a map of file path -> confident mapping for quick lookup\n const confidentMap = new Map(mappings.confident.map((m) => [m.filePath, m]));\n\n // Process added files\n for (const filePath of diff.added) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"add\",\n elementId: element.elementId,\n layerName: element.layer,\n after: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n timestamp: new Date().toISOString(),\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(\n `File added: ${filePath} -> ${element.elementId} (staged for review, verify content accuracy)`\n );\n changeCount++;\n }\n }\n\n // Process modified files\n for (const filePath of diff.modified) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"update\",\n elementId: element.elementId,\n layerName: element.layer,\n before: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n },\n after: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n timestamp: new Date().toISOString(),\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(`File modified: ${filePath} -> ${element.elementId} (staged for review)`);\n changeCount++;\n }\n }\n\n // Process deleted files\n for (const filePath of diff.deleted) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"delete\",\n elementId: element.elementId,\n layerName: element.layer,\n before: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(`File deleted: ${filePath} -> ${element.elementId} (staged for review)`);\n changeCount++;\n }\n }\n\n // Flag ambiguities\n if (mappings.ambiguous.length > 0 && !options.force) {\n for (const ambiguity of mappings.ambiguous) {\n warnings.push(\n `Ambiguous mapping: ${ambiguity.filePath} -> ${ambiguity.possibleElements.map((e) => e.elementId).join(\", \")} (skipped, use --force to proceed)`\n );\n }\n }\n\n return {\n changesetId: changeset.id,\n changeCount,\n warnings,\n };\n }\n\n /**\n * Execute a complete sync operation for a project\n */\n async syncProject(project: FarmProject, options: SyncOptions = {}): Promise<SyncResult> {\n const codebasePath = project.source;\n const notes: string[] = [];\n\n // Step 1: Get current commit (pull only if not dry run)\n if (options.verbose && !options.dryRun) {\n notes.push(`Pulling latest from ${codebasePath}...`);\n }\n const currentCommit = options.dryRun\n ? await this.getCurrentCommit(codebasePath)\n : await this.pullCodebase(codebasePath);\n if (options.dryRun) {\n notes.push(\"(DRY RUN - skipping git pull)\");\n }\n\n // Step 2: Load sync state\n const syncStatePath = path.join(this.farmRoot, project.model, \".farm-sync.yaml\");\n await ensureDir(path.dirname(syncStatePath));\n const syncState = await FarmSyncState.loadOrCreate(syncStatePath, project.name);\n\n const previousCommit = syncState.lastSyncCommit;\n notes.push(`Current commit: ${currentCommit}`);\n notes.push(`Previous sync commit: ${previousCommit || \"none\"}`);\n\n // Step 3: If no previous sync, this is initial sync - just record state\n if (!previousCommit) {\n notes.push(\"Initial sync - recording baseline only\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: \"success\",\n files_changed: 0,\n elements_affected: 0,\n notes: \"Initial sync, no changes to sync\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"success\",\n projectName: project.name,\n commitsBefore: \"none\",\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: { added: [], modified: [], deleted: [] },\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n // Step 4: Compute diff\n if (options.verbose) {\n notes.push(`Computing diff between ${previousCommit.substring(0, 8)} and ${currentCommit.substring(0, 8)}...`);\n }\n const diff = await this.computeDiff(codebasePath, previousCommit, currentCommit);\n\n // If no changes, return early\n if (diff.added.length === 0 && diff.modified.length === 0 && diff.deleted.length === 0) {\n notes.push(\"No changes detected\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: \"success\",\n files_changed: 0,\n elements_affected: 0,\n notes: \"No changes to sync\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"success\",\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n notes.push(\n `Files changed: ${diff.added.length} added, ${diff.modified.length} modified, ${diff.deleted.length} deleted`\n );\n\n // Step 5: Check that model is available before attempting to map files\n if (!this.model) {\n notes.push(\"CRITICAL: Model is not loaded, cannot map files to elements\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: previousCommit, // Don't advance lastSyncCommit - stay at previous commit\n status: \"failed\",\n files_changed: diff.added.length + diff.modified.length + diff.deleted.length,\n elements_affected: 0,\n notes: \"Model not loaded - file changes not synced. Load the model and retry to process these files.\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"failed\",\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n // Step 6: Map files to elements\n if (options.verbose) {\n notes.push(\"Mapping changed files to model elements...\");\n }\n const mappings = await this.mapFilesToElements();\n\n // Step 7: Record ambiguities in sync state\n if (mappings.ambiguous.length > 0) {\n syncState.recordAmbiguities(\n mappings.ambiguous.map((m) => ({\n filePath: m.filePath,\n possibleElements: m.possibleElements.map((e) => ({\n elementId: e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n }))\n );\n notes.push(`Ambiguities detected: ${mappings.ambiguous.length} files`);\n }\n\n // Step 8: Generate changeset\n let changesetId: string | undefined;\n let changeCount = 0;\n\n if (!options.dryRun) {\n const changesetResult = await this.generateChangeset(project, diff, mappings, options);\n changesetId = changesetResult.changesetId;\n changeCount = changesetResult.changeCount;\n notes.push(...changesetResult.warnings);\n }\n\n // Step 9: Record sync in state (skip for dry run)\n const syncStatus = mappings.ambiguous.length > 0 && !options.force ? \"partial\" : \"success\";\n\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: syncStatus,\n changeset: changesetId,\n files_changed: diff.added.length + diff.modified.length + diff.deleted.length,\n elements_affected: mappings.confident.length + mappings.ambiguous.length,\n notes: notes.join(\"\\n\"),\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: syncStatus,\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: mappings.confident,\n ambiguities: mappings.ambiguous,\n changesetId,\n changeCount,\n notes,\n dryRun: options.dryRun,\n };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,12 @@
1
1
  import yaml from "yaml";
2
2
  import { readFile, writeFile, fileExists } from "../utils/file-io.js";
3
+ class FarmSyncStateNotFoundError extends Error {
4
+ code = "FARM_SYNC_STATE_NOT_FOUND";
5
+ filePath;
6
+ constructor(filePath) {
7
+ super(`Farm sync state file not found: ${filePath}`), this.name = "FarmSyncStateNotFoundError", this.filePath = filePath;
8
+ }
9
+ }
3
10
  class FarmSyncState {
4
11
  projectName;
5
12
  lastSyncCommit;
@@ -24,7 +31,7 @@ class FarmSyncState {
24
31
  */
25
32
  static async load(filePath) {
26
33
  if (!await fileExists(filePath))
27
- throw new Error(`Farm sync state file not found: ${filePath}`);
34
+ throw new FarmSyncStateNotFoundError(filePath);
28
35
  const content = await readFile(filePath), rawData = yaml.parse(content);
29
36
  if (!rawData)
30
37
  throw new Error(`Invalid farm sync state: file is empty or contains invalid YAML at ${filePath}`);
@@ -154,6 +161,7 @@ class FarmSyncState {
154
161
  }
155
162
  }
156
163
  export {
157
- FarmSyncState
164
+ FarmSyncState,
165
+ FarmSyncStateNotFoundError
158
166
  };
159
167
  //# sourceMappingURL=farm-sync-state.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/farm-sync-state.ts"],
4
- "sourcesContent": ["/**\n * Farm Sync State - Manages .farm-sync.yaml per project\n *\n * Tracks sync history, last sync commit, and timestamps for each project.\n */\n\nimport yaml from \"yaml\";\nimport { readFile, writeFile, fileExists } from \"../utils/file-io.js\";\n\n/**\n * Represents a single sync record\n */\nexport interface SyncRecord {\n timestamp: string; // ISO 8601 timestamp\n commit: string; // Commit SHA where sync occurred\n changeset?: string; // ID of generated changeset\n files_changed?: number; // Number of files that changed\n elements_affected?: number; // Number of model elements affected\n status: \"success\" | \"partial\" | \"failed\"; // Status of the sync\n notes?: string; // Optional notes about the sync\n}\n\n/**\n * Farm sync state for a single project\n */\nexport interface FarmSyncStateData {\n project_name: string;\n last_sync_commit?: string; // Last successful sync commit SHA\n last_sync_timestamp?: string; // ISO 8601 timestamp\n sync_history: SyncRecord[]; // Complete history of syncs\n ambiguities?: Array<{\n file_path: string;\n possible_elements: Array<{\n element_id: string;\n layer: string;\n confidence: number; // 0-100 confidence score\n }>;\n }>; // Ambiguous file-to-element mappings flagged for review\n}\n\n/**\n * FarmSyncState class - Load, validate, and save farm sync state per project\n */\nexport class FarmSyncState {\n projectName: string;\n lastSyncCommit?: string;\n lastSyncTimestamp?: string;\n syncHistory: SyncRecord[];\n ambiguities: Array<{\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number;\n }>;\n }>;\n filePath?: string;\n\n constructor(data: FarmSyncStateData) {\n this.projectName = data.project_name;\n this.lastSyncCommit = data.last_sync_commit;\n this.lastSyncTimestamp = data.last_sync_timestamp;\n this.syncHistory = data.sync_history || [];\n this.ambiguities = data.ambiguities?.map(a => ({\n filePath: a.file_path,\n possibleElements: a.possible_elements.map(e => ({\n elementId: e.element_id,\n layer: e.layer,\n confidence: e.confidence,\n })),\n })) || [];\n }\n\n /**\n * Load farm sync state from file\n * @param filePath - Path to .farm-sync.yaml\n * @returns FarmSyncState instance\n */\n static async load(filePath: string): Promise<FarmSyncState> {\n if (!(await fileExists(filePath))) {\n throw new Error(`Farm sync state file not found: ${filePath}`);\n }\n\n const content = await readFile(filePath);\n const rawData = yaml.parse(content) as any;\n\n if (!rawData) {\n throw new Error(\n `Invalid farm sync state: file is empty or contains invalid YAML at ${filePath}`\n );\n }\n\n // Handle both old (camelCase) and new (snake_case) formats for backward compatibility\n const data: FarmSyncStateData = {\n project_name: rawData.project_name || rawData.projectName,\n last_sync_commit: rawData.last_sync_commit || rawData.lastSyncCommit,\n last_sync_timestamp: rawData.last_sync_timestamp || rawData.lastSyncTimestamp,\n sync_history: (rawData.sync_history || rawData.syncHistory || []).map((record: any) => ({\n timestamp: record.timestamp,\n commit: record.commit,\n changeset: record.changeset || record.changesetId,\n files_changed: record.files_changed || record.filesChanged,\n elements_affected: record.elements_affected || record.elementsAffected,\n status: record.status,\n notes: record.notes,\n })),\n ambiguities: (rawData.ambiguities || []).map((a: any) => ({\n file_path: a.file_path || a.filePath,\n possible_elements: (a.possible_elements || a.possibleElements || []).map((e: any) => ({\n element_id: e.element_id || e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n })),\n };\n\n if (!data.project_name) {\n throw new Error(\"Farm sync state must have a 'project_name' field\");\n }\n\n const state = new FarmSyncState(data);\n state.filePath = filePath;\n return state;\n }\n\n /**\n * Create a new farm sync state\n * @param projectName - Name of the project\n * @param options - Optional initial state\n * @returns FarmSyncState instance\n */\n static create(\n projectName: string,\n options: {\n lastSyncCommit?: string;\n lastSyncTimestamp?: string;\n } = {}\n ): FarmSyncState {\n return new FarmSyncState({\n project_name: projectName,\n last_sync_commit: options.lastSyncCommit,\n last_sync_timestamp: options.lastSyncTimestamp,\n sync_history: [],\n ambiguities: [],\n });\n }\n\n /**\n * Load or create farm sync state\n * @param filePath - Path to .farm-sync.yaml\n * @param projectName - Project name (used if creating new)\n * @returns FarmSyncState instance\n */\n static async loadOrCreate(filePath: string, projectName: string): Promise<FarmSyncState> {\n if (await fileExists(filePath)) {\n return this.load(filePath);\n }\n return this.create(projectName);\n }\n\n /**\n * Save state to file\n * @param filePath - Path where to save .farm-sync.yaml\n */\n async save(filePath?: string): Promise<void> {\n const targetPath = filePath || this.filePath;\n if (!targetPath) {\n throw new Error(\"No file path specified for saving farm sync state\");\n }\n\n const data = this.toJSON();\n const yamlContent = yaml.stringify(data, { lineWidth: 0 });\n\n await writeFile(targetPath, yamlContent);\n this.filePath = targetPath;\n }\n\n /**\n * Record a sync (successful or failed)\n */\n recordSync(record: Omit<SyncRecord, \"status\"> & { status?: \"success\" | \"partial\" | \"failed\" }): void {\n const status = record.status || \"success\";\n\n // Update lastSyncCommit on successful and partial syncs to avoid reprocessing confident files.\n // Failed syncs do not advance the pointer (will retry from same point).\n // Partial syncs advance because confident files have been processed and staged;\n // ambiguous files are tracked separately for manual review.\n if (status === \"success\" || status === \"partial\") {\n this.lastSyncCommit = record.commit;\n this.lastSyncTimestamp = record.timestamp;\n }\n\n this.syncHistory.push({\n timestamp: record.timestamp,\n commit: record.commit,\n status,\n changeset: record.changeset,\n files_changed: record.files_changed,\n elements_affected: record.elements_affected,\n notes: record.notes,\n });\n\n // Keep history trimmed to last 100 syncs\n if (this.syncHistory.length > 100) {\n this.syncHistory = this.syncHistory.slice(-100);\n }\n }\n\n /**\n * Record ambiguous file-to-element mappings for review\n */\n recordAmbiguities(\n ambiguities: Array<{\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number;\n }>;\n }>\n ): void {\n this.ambiguities = ambiguities;\n }\n\n /**\n * Clear ambiguities after review\n */\n clearAmbiguities(): void {\n this.ambiguities = [];\n }\n\n /**\n * Get the last sync record if any\n */\n getLastSync(): SyncRecord | undefined {\n return this.syncHistory.length > 0 ? this.syncHistory[this.syncHistory.length - 1] : undefined;\n }\n\n /**\n * Serialize to JSON representation for saving\n */\n toJSON(): FarmSyncStateData {\n return {\n project_name: this.projectName,\n last_sync_commit: this.lastSyncCommit,\n last_sync_timestamp: this.lastSyncTimestamp,\n sync_history: this.syncHistory.map(record => ({\n timestamp: record.timestamp,\n commit: record.commit,\n changeset: record.changeset,\n files_changed: record.files_changed,\n elements_affected: record.elements_affected,\n status: record.status,\n notes: record.notes,\n })),\n ...(this.ambiguities.length > 0 && {\n ambiguities: this.ambiguities.map(a => ({\n file_path: a.filePath,\n possible_elements: a.possibleElements.map(e => ({\n element_id: e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n }))\n }),\n };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Farm Sync State - Manages .farm-sync.yaml per project\n *\n * Tracks sync history, last sync commit, and timestamps for each project.\n */\n\nimport yaml from \"yaml\";\nimport { readFile, writeFile, fileExists } from \"../utils/file-io.js\";\n\n/**\n * Represents a single sync record\n */\nexport interface SyncRecord {\n timestamp: string; // ISO 8601 timestamp\n commit: string; // Commit SHA where sync occurred\n changeset?: string; // ID of generated changeset\n files_changed?: number; // Number of files that changed\n elements_affected?: number; // Number of model elements affected\n status: \"success\" | \"partial\" | \"failed\"; // Status of the sync\n notes?: string; // Optional notes about the sync\n}\n\n/**\n * Farm sync state for a single project\n */\nexport interface FarmSyncStateData {\n project_name: string;\n last_sync_commit?: string; // Last successful sync commit SHA\n last_sync_timestamp?: string; // ISO 8601 timestamp\n sync_history: SyncRecord[]; // Complete history of syncs\n ambiguities?: Array<{\n file_path: string;\n possible_elements: Array<{\n element_id: string;\n layer: string;\n confidence: number; // 0-100 confidence score\n }>;\n }>; // Ambiguous file-to-element mappings flagged for review\n}\n\n/**\n * Thrown by FarmSyncState.load when the .farm-sync.yaml file does not exist.\n * Callers that treat \"never synced\" differently from other failures check `code`\n * (or `instanceof`) rather than matching the message text.\n */\nexport class FarmSyncStateNotFoundError extends Error {\n readonly code = \"FARM_SYNC_STATE_NOT_FOUND\";\n readonly filePath: string;\n\n constructor(filePath: string) {\n super(`Farm sync state file not found: ${filePath}`);\n this.name = \"FarmSyncStateNotFoundError\";\n this.filePath = filePath;\n }\n}\n\n/**\n * FarmSyncState class - Load, validate, and save farm sync state per project\n */\nexport class FarmSyncState {\n projectName: string;\n lastSyncCommit?: string;\n lastSyncTimestamp?: string;\n syncHistory: SyncRecord[];\n ambiguities: Array<{\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number;\n }>;\n }>;\n filePath?: string;\n\n constructor(data: FarmSyncStateData) {\n this.projectName = data.project_name;\n this.lastSyncCommit = data.last_sync_commit;\n this.lastSyncTimestamp = data.last_sync_timestamp;\n this.syncHistory = data.sync_history || [];\n this.ambiguities = data.ambiguities?.map(a => ({\n filePath: a.file_path,\n possibleElements: a.possible_elements.map(e => ({\n elementId: e.element_id,\n layer: e.layer,\n confidence: e.confidence,\n })),\n })) || [];\n }\n\n /**\n * Load farm sync state from file\n * @param filePath - Path to .farm-sync.yaml\n * @returns FarmSyncState instance\n */\n static async load(filePath: string): Promise<FarmSyncState> {\n if (!(await fileExists(filePath))) {\n throw new FarmSyncStateNotFoundError(filePath);\n }\n\n const content = await readFile(filePath);\n const rawData = yaml.parse(content) as any;\n\n if (!rawData) {\n throw new Error(\n `Invalid farm sync state: file is empty or contains invalid YAML at ${filePath}`\n );\n }\n\n // Handle both old (camelCase) and new (snake_case) formats for backward compatibility\n const data: FarmSyncStateData = {\n project_name: rawData.project_name || rawData.projectName,\n last_sync_commit: rawData.last_sync_commit || rawData.lastSyncCommit,\n last_sync_timestamp: rawData.last_sync_timestamp || rawData.lastSyncTimestamp,\n sync_history: (rawData.sync_history || rawData.syncHistory || []).map((record: any) => ({\n timestamp: record.timestamp,\n commit: record.commit,\n changeset: record.changeset || record.changesetId,\n files_changed: record.files_changed || record.filesChanged,\n elements_affected: record.elements_affected || record.elementsAffected,\n status: record.status,\n notes: record.notes,\n })),\n ambiguities: (rawData.ambiguities || []).map((a: any) => ({\n file_path: a.file_path || a.filePath,\n possible_elements: (a.possible_elements || a.possibleElements || []).map((e: any) => ({\n element_id: e.element_id || e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n })),\n };\n\n if (!data.project_name) {\n throw new Error(\"Farm sync state must have a 'project_name' field\");\n }\n\n const state = new FarmSyncState(data);\n state.filePath = filePath;\n return state;\n }\n\n /**\n * Create a new farm sync state\n * @param projectName - Name of the project\n * @param options - Optional initial state\n * @returns FarmSyncState instance\n */\n static create(\n projectName: string,\n options: {\n lastSyncCommit?: string;\n lastSyncTimestamp?: string;\n } = {}\n ): FarmSyncState {\n return new FarmSyncState({\n project_name: projectName,\n last_sync_commit: options.lastSyncCommit,\n last_sync_timestamp: options.lastSyncTimestamp,\n sync_history: [],\n ambiguities: [],\n });\n }\n\n /**\n * Load or create farm sync state\n * @param filePath - Path to .farm-sync.yaml\n * @param projectName - Project name (used if creating new)\n * @returns FarmSyncState instance\n */\n static async loadOrCreate(filePath: string, projectName: string): Promise<FarmSyncState> {\n if (await fileExists(filePath)) {\n return this.load(filePath);\n }\n return this.create(projectName);\n }\n\n /**\n * Save state to file\n * @param filePath - Path where to save .farm-sync.yaml\n */\n async save(filePath?: string): Promise<void> {\n const targetPath = filePath || this.filePath;\n if (!targetPath) {\n throw new Error(\"No file path specified for saving farm sync state\");\n }\n\n const data = this.toJSON();\n const yamlContent = yaml.stringify(data, { lineWidth: 0 });\n\n await writeFile(targetPath, yamlContent);\n this.filePath = targetPath;\n }\n\n /**\n * Record a sync (successful or failed)\n */\n recordSync(record: Omit<SyncRecord, \"status\"> & { status?: \"success\" | \"partial\" | \"failed\" }): void {\n const status = record.status || \"success\";\n\n // Update lastSyncCommit on successful and partial syncs to avoid reprocessing confident files.\n // Failed syncs do not advance the pointer (will retry from same point).\n // Partial syncs advance because confident files have been processed and staged;\n // ambiguous files are tracked separately for manual review.\n if (status === \"success\" || status === \"partial\") {\n this.lastSyncCommit = record.commit;\n this.lastSyncTimestamp = record.timestamp;\n }\n\n this.syncHistory.push({\n timestamp: record.timestamp,\n commit: record.commit,\n status,\n changeset: record.changeset,\n files_changed: record.files_changed,\n elements_affected: record.elements_affected,\n notes: record.notes,\n });\n\n // Keep history trimmed to last 100 syncs\n if (this.syncHistory.length > 100) {\n this.syncHistory = this.syncHistory.slice(-100);\n }\n }\n\n /**\n * Record ambiguous file-to-element mappings for review\n */\n recordAmbiguities(\n ambiguities: Array<{\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number;\n }>;\n }>\n ): void {\n this.ambiguities = ambiguities;\n }\n\n /**\n * Clear ambiguities after review\n */\n clearAmbiguities(): void {\n this.ambiguities = [];\n }\n\n /**\n * Get the last sync record if any\n */\n getLastSync(): SyncRecord | undefined {\n return this.syncHistory.length > 0 ? this.syncHistory[this.syncHistory.length - 1] : undefined;\n }\n\n /**\n * Serialize to JSON representation for saving\n */\n toJSON(): FarmSyncStateData {\n return {\n project_name: this.projectName,\n last_sync_commit: this.lastSyncCommit,\n last_sync_timestamp: this.lastSyncTimestamp,\n sync_history: this.syncHistory.map(record => ({\n timestamp: record.timestamp,\n commit: record.commit,\n changeset: record.changeset,\n files_changed: record.files_changed,\n elements_affected: record.elements_affected,\n status: record.status,\n notes: record.notes,\n })),\n ...(this.ambiguities.length > 0 && {\n ambiguities: this.ambiguities.map(a => ({\n file_path: a.filePath,\n possible_elements: a.possibleElements.map(e => ({\n element_id: e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n }))\n }),\n };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { getErrorMessage } from "../utils/errors.js";
2
+ import { applyCatalogMigrations } from "./catalog-migration.js";
2
3
  class MigrationRegistry {
3
4
  migrations = [];
4
5
  constructor() {
@@ -137,55 +138,29 @@ class MigrationRegistry {
137
138
  }), this.migrations.push({
138
139
  fromVersion: "0.9.0",
139
140
  toVersion: "0.10.0",
140
- description: "Data Model objectschema.required type change (Spec v0.10.0)",
141
+ description: "Data Model objectschema.required type change, element type renames and relationship catalog rewrites (Spec v0.10.0)",
141
142
  apply: async (model) => {
142
- const fs = await import("fs/promises"), pathModule = await import("path"), YAML = await import("yaml"), modelDir = `${model.rootPath}/documentation-robotics/model`, dataModelDir = pathModule.join(modelDir, "08_data-model");
143
- let filesModified = 0;
144
- try {
145
- await fs.access(dataModelDir);
146
- } catch (error) {
147
- if (error.code === "ENOENT")
148
- return {
149
- migrationsApplied: 1,
150
- filesModified: 0,
151
- description: "Spec version updated to 0.10.0 (objectschema.required type changed from string to array; no model data to migrate)"
152
- };
153
- throw error;
154
- }
155
- const files = await fs.readdir(dataModelDir), migrationErrors = [];
156
- for (const file of files) {
157
- if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
158
- continue;
159
- const filePath = pathModule.join(dataModelDir, file);
160
- try {
161
- const content = await fs.readFile(filePath, "utf-8");
162
- let modified = !1;
163
- const doc = YAML.parse(content);
164
- if (!doc || typeof doc != "object")
165
- continue;
166
- for (const [, element] of Object.entries(doc)) {
167
- if (!element || typeof element != "object" || element.spec_node_id !== "data-model.objectschema")
168
- continue;
169
- const attributes = element.attributes;
170
- if (attributes && typeof attributes == "object") {
171
- const required = attributes.required;
172
- required && typeof required == "string" && (attributes.required = required.split(",").map((item) => item.trim()).filter((item) => item.length > 0), modified = !0);
173
- }
174
- }
175
- modified && (await fs.writeFile(filePath, YAML.stringify(doc), "utf-8"), filesModified++);
176
- } catch (error) {
177
- migrationErrors.push(`Failed to migrate ${file}: ${getErrorMessage(error)}`);
178
- }
179
- }
180
- return migrationErrors.length > 0 ? {
143
+ const required = await migrateObjectSchemaRequired(model), catalog = await applyCatalogMigrations(model), errors = [...required.errors, ...catalog.errors];
144
+ return {
181
145
  migrationsApplied: 1,
182
- filesModified,
183
- description: "Spec version updated to 0.10.0 (objectschema.required converted from string to array)",
184
- error: migrationErrors.join("; ")
185
- } : {
146
+ filesModified: required.filesModified + catalog.filesModified,
147
+ description: `Spec version updated to 0.10.0 (objectschema.required converted from string to array; ${catalog.summaries.join("; ")})`,
148
+ ...errors.length > 0 && { error: errors.join("; ") },
149
+ details: catalog.details
150
+ };
151
+ }
152
+ }), this.migrations.push({
153
+ fromVersion: "0.10.0",
154
+ toVersion: "0.11.0",
155
+ description: "Relationship catalog rewrites from the layer rules pass (Spec v0.11.0)",
156
+ apply: async (model) => {
157
+ const catalog = await applyCatalogMigrations(model);
158
+ return {
186
159
  migrationsApplied: 1,
187
- filesModified,
188
- description: "Spec version updated to 0.10.0 (objectschema.required converted from string to array)"
160
+ filesModified: catalog.filesModified,
161
+ description: `Spec version updated to 0.11.0 (${catalog.summaries.join("; ")})`,
162
+ ...catalog.errors.length > 0 && { error: catalog.errors.join("; ") },
163
+ details: catalog.details
189
164
  };
190
165
  }
191
166
  });
@@ -291,6 +266,60 @@ class MigrationRegistry {
291
266
  return 0;
292
267
  }
293
268
  }
269
+ async function migrateObjectSchemaRequired(model) {
270
+ const result = await migrateObjectSchemaRequiredFiles(model);
271
+ return migrateLoadedObjectSchemaRequired(model), result;
272
+ }
273
+ function requiredAsArray(required) {
274
+ return !required || typeof required != "string" ? null : required.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
275
+ }
276
+ function migrateLoadedObjectSchemaRequired(model) {
277
+ const layer = model.layers.get("data-model");
278
+ if (layer)
279
+ for (const element of layer.listElements()) {
280
+ if (element.spec_node_id !== "data-model.objectschema")
281
+ continue;
282
+ const required = requiredAsArray(element.attributes.required);
283
+ required && (element.attributes = { ...element.attributes, required }, layer.updateElement(element));
284
+ }
285
+ }
286
+ async function migrateObjectSchemaRequiredFiles(model) {
287
+ const fs = await import("fs/promises"), pathModule = await import("path"), YAML = await import("yaml"), dataModelDir = pathModule.join(model.rootPath, "documentation-robotics/model/08_data-model");
288
+ let filesModified = 0;
289
+ try {
290
+ await fs.access(dataModelDir);
291
+ } catch (error) {
292
+ if (error.code === "ENOENT")
293
+ return { filesModified: 0, errors: [] };
294
+ throw error;
295
+ }
296
+ const files = await fs.readdir(dataModelDir), errors = [];
297
+ for (const file of files) {
298
+ if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
299
+ continue;
300
+ const filePath = pathModule.join(dataModelDir, file);
301
+ try {
302
+ const content = await fs.readFile(filePath, "utf-8");
303
+ let modified = !1;
304
+ const doc = YAML.parse(content);
305
+ if (!doc || typeof doc != "object")
306
+ continue;
307
+ for (const [, element] of Object.entries(doc)) {
308
+ if (!element || typeof element != "object" || element.spec_node_id !== "data-model.objectschema")
309
+ continue;
310
+ const attributes = element.attributes;
311
+ if (attributes && typeof attributes == "object") {
312
+ const required = requiredAsArray(attributes.required);
313
+ required && (attributes.required = required, modified = !0);
314
+ }
315
+ }
316
+ modified && (await fs.writeFile(filePath, YAML.stringify(doc), "utf-8"), filesModified++);
317
+ } catch (error) {
318
+ errors.push(`Failed to migrate ${file}: ${getErrorMessage(error)}`);
319
+ }
320
+ }
321
+ return { filesModified, errors };
322
+ }
294
323
  export {
295
324
  MigrationRegistry
296
325
  };
@@ -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 // 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 // Migration from v0.9.0 to v0.10.0: Data Model objectschema.required type change\n this.migrations.push({\n fromVersion: \"0.9.0\",\n toVersion: \"0.10.0\",\n description: \"Data Model objectschema.required type change (Spec v0.10.0)\",\n apply: async (model: Model) => {\n const fs = await import(\"fs/promises\");\n const pathModule = await import(\"path\");\n const YAML = await import(\"yaml\");\n\n const modelDir = `${model.rootPath}/documentation-robotics/model`;\n const dataModelDir = pathModule.join(modelDir, \"08_data-model\");\n let filesModified = 0;\n\n try {\n // Verify data model directory exists\n await fs.access(dataModelDir);\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.10.0 (objectschema.required type changed from string to array; no model data to migrate)\",\n };\n }\n // Re-throw other errors\n throw error;\n }\n\n // Scan all YAML files in the data model directory\n const files = await fs.readdir(dataModelDir);\n const migrationErrors: string[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".yaml\") && !file.endsWith(\".yml\")) {\n continue;\n }\n\n const filePath = pathModule.join(dataModelDir, file);\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n let modified = false;\n const doc = YAML.parse(content);\n\n if (!doc || typeof doc !== \"object\") {\n continue;\n }\n\n // Iterate through all elements in the file\n for (const [, element] of Object.entries(doc)) {\n if (\n !element ||\n typeof element !== \"object\" ||\n (element as Record<string, unknown>).spec_node_id !== \"data-model.objectschema\"\n ) {\n continue;\n }\n\n const elem = element as Record<string, unknown>;\n const attributes = elem.attributes as Record<string, unknown> | undefined;\n\n if (attributes && typeof attributes === \"object\") {\n const required = attributes.required;\n\n // Check if required field exists and is a string\n if (required && typeof required === \"string\") {\n // Convert comma-separated string to array\n attributes.required = required\n .split(\",\")\n .map((item: string) => item.trim())\n .filter((item: string) => item.length > 0);\n modified = true;\n }\n }\n }\n\n // Write back if modified\n if (modified) {\n await fs.writeFile(filePath, YAML.stringify(doc), \"utf-8\");\n filesModified++;\n }\n } catch (error) {\n migrationErrors.push(\n `Failed to migrate ${file}: ${getErrorMessage(error)}`\n );\n }\n }\n\n if (migrationErrors.length > 0) {\n return {\n migrationsApplied: 1,\n filesModified,\n description:\n \"Spec version updated to 0.10.0 (objectschema.required converted from string to array)\",\n error: migrationErrors.join(\"; \"),\n };\n }\n\n return {\n migrationsApplied: 1,\n filesModified,\n description:\n \"Spec version updated to 0.10.0 (objectschema.required converted from string to array)\",\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 using proper version comparison\n const versions = this.migrations.map((m) => m.toVersion);\n return versions.reduce((max, current) => {\n return this.compareVersions(current, max) > 0 ? current : max;\n });\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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",
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\";\nimport { applyCatalogMigrations } from \"./catalog-migration.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 // Migration from v0.9.0 to v0.10.0: objectschema.required type change and relationship catalog rewrites\n this.migrations.push({\n fromVersion: \"0.9.0\",\n toVersion: \"0.10.0\",\n description:\n \"Data Model objectschema.required type change, element type renames and relationship catalog rewrites (Spec v0.10.0)\",\n apply: async (model: Model) => {\n const required = await migrateObjectSchemaRequired(model);\n\n // Element type renames, then relationship rewrites in relationships.yaml and in staged\n // changesets: shared with `dr upgrade` on a model already at the current version\n const catalog = await applyCatalogMigrations(model);\n const errors = [...required.errors, ...catalog.errors];\n\n return {\n migrationsApplied: 1,\n filesModified: required.filesModified + catalog.filesModified,\n description: `Spec version updated to 0.10.0 (objectschema.required converted from string to array; ${catalog.summaries.join(\"; \")})`,\n ...(errors.length > 0 && { error: errors.join(\"; \") }),\n details: catalog.details,\n };\n },\n });\n\n // Migration from v0.10.0 to v0.11.0: the layer rules pass retired relationship types and\n // predicates; CLI 0.2.0 stamped models 0.10.0 with the catalog from before it\n this.migrations.push({\n fromVersion: \"0.10.0\",\n toVersion: \"0.11.0\",\n description: \"Relationship catalog rewrites from the layer rules pass (Spec v0.11.0)\",\n apply: async (model: Model) => {\n const catalog = await applyCatalogMigrations(model);\n return {\n migrationsApplied: 1,\n filesModified: catalog.filesModified,\n description: `Spec version updated to 0.11.0 (${catalog.summaries.join(\"; \")})`,\n ...(catalog.errors.length > 0 && { error: catalog.errors.join(\"; \") }),\n details: catalog.details,\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 using proper version comparison\n const versions = this.migrations.map((m) => m.toVersion);\n return versions.reduce((max, current) => {\n return this.compareVersions(current, max) > 0 ? current : max;\n });\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\n/**\n * Spec v0.10.0: data-model.objectschema `required` changed from a comma-separated string to an\n * array of property names. Converts every string value in 08_data-model/*.yaml, and in the\n * data-model layer when the model has it loaded: `dr upgrade` saves the loaded layers after\n * migrating, which would otherwise write the old strings back over the converted files.\n */\nasync function migrateObjectSchemaRequired(\n model: Model\n): Promise<{ filesModified: number; errors: string[] }> {\n const result = await migrateObjectSchemaRequiredFiles(model);\n migrateLoadedObjectSchemaRequired(model);\n return result;\n}\n\n/** A comma-separated `required` string as an array of property names; null for any other value */\nfunction requiredAsArray(required: unknown): string[] | null {\n if (!required || typeof required !== \"string\") {\n return null;\n }\n return required\n .split(\",\")\n .map((item) => item.trim())\n .filter((item) => item.length > 0);\n}\n\nfunction migrateLoadedObjectSchemaRequired(model: Model): void {\n // The committed layer: getLayer() would merge in an active changeset's staged changes\n const layer = model.layers.get(\"data-model\");\n if (!layer) {\n return;\n }\n for (const element of layer.listElements()) {\n if (element.spec_node_id !== \"data-model.objectschema\") {\n continue;\n }\n const required = requiredAsArray(element.attributes.required);\n if (required) {\n element.attributes = { ...element.attributes, required };\n layer.updateElement(element);\n }\n }\n}\n\nasync function migrateObjectSchemaRequiredFiles(\n model: Model\n): Promise<{ filesModified: number; errors: string[] }> {\n const fs = await import(\"fs/promises\");\n const pathModule = await import(\"path\");\n const YAML = await import(\"yaml\");\n\n const dataModelDir = pathModule.join(model.rootPath, \"documentation-robotics/model/08_data-model\");\n let filesModified = 0;\n\n try {\n await fs.access(dataModelDir);\n } catch (error) {\n // ENOENT means the directory doesn't exist - nothing to migrate\n if ((error as { code?: string }).code === \"ENOENT\") {\n return { filesModified: 0, errors: [] };\n }\n throw error;\n }\n\n const files = await fs.readdir(dataModelDir);\n const errors: string[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".yaml\") && !file.endsWith(\".yml\")) {\n continue;\n }\n\n const filePath = pathModule.join(dataModelDir, file);\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n let modified = false;\n const doc = YAML.parse(content);\n\n if (!doc || typeof doc !== \"object\") {\n continue;\n }\n\n for (const [, element] of Object.entries(doc)) {\n if (\n !element ||\n typeof element !== \"object\" ||\n (element as Record<string, unknown>).spec_node_id !== \"data-model.objectschema\"\n ) {\n continue;\n }\n\n const attributes = (element as Record<string, unknown>).attributes as\n | Record<string, unknown>\n | undefined;\n\n if (attributes && typeof attributes === \"object\") {\n const required = requiredAsArray(attributes.required);\n if (required) {\n attributes.required = required;\n modified = true;\n }\n }\n }\n\n if (modified) {\n await fs.writeFile(filePath, YAML.stringify(doc), \"utf-8\");\n filesModified++;\n }\n } catch (error) {\n errors.push(`Failed to migrate ${file}: ${getErrorMessage(error)}`);\n }\n }\n\n return { filesModified, errors };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }