@documentation-robotics/cli 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +38 -0
  2. package/dist/analyzers/verify-engine.js +22 -19
  3. package/dist/analyzers/verify-engine.js.map +2 -2
  4. package/dist/cli.js +101 -27
  5. package/dist/cli.js.map +3 -3
  6. package/dist/coding-agents/agents/claude-code.js +3 -1
  7. package/dist/coding-agents/agents/claude-code.js.map +2 -2
  8. package/dist/coding-agents/claude-code-client.js +2 -1
  9. package/dist/coding-agents/claude-code-client.js.map +2 -2
  10. package/dist/coding-agents/copilot-client.js +17 -12
  11. package/dist/coding-agents/copilot-client.js.map +2 -2
  12. package/dist/coding-agents/default-permissions.js +122 -0
  13. package/dist/coding-agents/default-permissions.js.map +7 -0
  14. package/dist/commands/add.js +10 -6
  15. package/dist/commands/add.js.map +2 -2
  16. package/dist/commands/analyzer.js +18 -10
  17. package/dist/commands/analyzer.js.map +2 -2
  18. package/dist/commands/audit.js +6 -6
  19. package/dist/commands/audit.js.map +2 -2
  20. package/dist/commands/changeset.js +238 -145
  21. package/dist/commands/changeset.js.map +2 -2
  22. package/dist/commands/delete.js +45 -18
  23. package/dist/commands/delete.js.map +3 -3
  24. package/dist/commands/element.js +3 -2
  25. package/dist/commands/element.js.map +2 -2
  26. package/dist/commands/farm.js +657 -0
  27. package/dist/commands/farm.js.map +7 -0
  28. package/dist/commands/init.js +11 -1
  29. package/dist/commands/init.js.map +2 -2
  30. package/dist/commands/mcp.js +107 -0
  31. package/dist/commands/mcp.js.map +7 -0
  32. package/dist/commands/relationship.js +53 -30
  33. package/dist/commands/relationship.js.map +2 -2
  34. package/dist/commands/reports.js +1 -1
  35. package/dist/commands/reports.js.map +2 -2
  36. package/dist/commands/update.js +19 -6
  37. package/dist/commands/update.js.map +2 -2
  38. package/dist/commands/validate.js +41 -3
  39. package/dist/commands/validate.js.map +2 -2
  40. package/dist/commands/version.js +2 -2
  41. package/dist/commands/version.js.map +2 -2
  42. package/dist/config.js +83 -0
  43. package/dist/config.js.map +7 -0
  44. package/dist/core/annotation-store.js +138 -0
  45. package/dist/core/annotation-store.js.map +7 -0
  46. package/dist/core/farm-manifest.js +134 -0
  47. package/dist/core/farm-manifest.js.map +7 -0
  48. package/dist/core/farm-sync-engine.js +346 -0
  49. package/dist/core/farm-sync-engine.js.map +7 -0
  50. package/dist/core/farm-sync-state.js +159 -0
  51. package/dist/core/farm-sync-state.js.map +7 -0
  52. package/dist/core/manifest.js +6 -3
  53. package/dist/core/manifest.js.map +2 -2
  54. package/dist/core/migration-registry.js +55 -1
  55. package/dist/core/migration-registry.js.map +2 -2
  56. package/dist/core/model.js +177 -16
  57. package/dist/core/model.js.map +3 -3
  58. package/dist/core/virtual-projection.js +1 -0
  59. package/dist/core/virtual-projection.js.map +2 -2
  60. package/dist/generated/node-types.js +1 -1
  61. package/dist/generated/node-types.js.map +2 -2
  62. package/dist/generated/relationship-index.js +45 -9
  63. package/dist/generated/relationship-index.js.map +2 -2
  64. package/dist/integrations/claude_code/docs/MCP_SERVER.md +55 -0
  65. package/dist/integrations/claude_code/docs/README.md +4 -0
  66. package/dist/mcp/api-key-manager.js +98 -0
  67. package/dist/mcp/api-key-manager.js.map +7 -0
  68. package/dist/mcp/http-transport.js +176 -0
  69. package/dist/mcp/http-transport.js.map +7 -0
  70. package/dist/mcp/resource-registry.js +97 -0
  71. package/dist/mcp/resource-registry.js.map +7 -0
  72. package/dist/mcp/tool-registry.js +44 -0
  73. package/dist/mcp/tool-registry.js.map +7 -0
  74. package/dist/mcp/tools/annotation-create.js +39 -0
  75. package/dist/mcp/tools/annotation-create.js.map +7 -0
  76. package/dist/mcp/tools/annotation-delete.js +29 -0
  77. package/dist/mcp/tools/annotation-delete.js.map +7 -0
  78. package/dist/mcp/tools/annotation-get.js +29 -0
  79. package/dist/mcp/tools/annotation-get.js.map +7 -0
  80. package/dist/mcp/tools/annotation-list.js +24 -0
  81. package/dist/mcp/tools/annotation-list.js.map +7 -0
  82. package/dist/mcp/tools/annotation-reply.js +34 -0
  83. package/dist/mcp/tools/annotation-reply.js.map +7 -0
  84. package/dist/mcp/tools/annotation-shared.js +10 -0
  85. package/dist/mcp/tools/annotation-shared.js.map +7 -0
  86. package/dist/mcp/tools/annotation-update.js +39 -0
  87. package/dist/mcp/tools/annotation-update.js.map +7 -0
  88. package/dist/mcp/tools/changeset-list.js +33 -0
  89. package/dist/mcp/tools/changeset-list.js.map +7 -0
  90. package/dist/mcp/tools/changeset-show.js +39 -0
  91. package/dist/mcp/tools/changeset-show.js.map +7 -0
  92. package/dist/mcp/tools/chat-launch.js +50 -0
  93. package/dist/mcp/tools/chat-launch.js.map +7 -0
  94. package/dist/mcp/tools/chat-status.js +26 -0
  95. package/dist/mcp/tools/chat-status.js.map +7 -0
  96. package/dist/mcp/tools/index.js +47 -0
  97. package/dist/mcp/tools/index.js.map +7 -0
  98. package/dist/mcp/tools/model-add.js +106 -0
  99. package/dist/mcp/tools/model-add.js.map +7 -0
  100. package/dist/mcp/tools/model-delete.js +99 -0
  101. package/dist/mcp/tools/model-delete.js.map +7 -0
  102. package/dist/mcp/tools/model-export.js +72 -0
  103. package/dist/mcp/tools/model-export.js.map +7 -0
  104. package/dist/mcp/tools/model-info.js +51 -0
  105. package/dist/mcp/tools/model-info.js.map +7 -0
  106. package/dist/mcp/tools/model-list.js +53 -0
  107. package/dist/mcp/tools/model-list.js.map +7 -0
  108. package/dist/mcp/tools/model-reload.js +34 -0
  109. package/dist/mcp/tools/model-reload.js.map +7 -0
  110. package/dist/mcp/tools/model-search.js +88 -0
  111. package/dist/mcp/tools/model-search.js.map +7 -0
  112. package/dist/mcp/tools/model-show.js +41 -0
  113. package/dist/mcp/tools/model-show.js.map +7 -0
  114. package/dist/mcp/tools/model-stats.js +22 -0
  115. package/dist/mcp/tools/model-stats.js.map +7 -0
  116. package/dist/mcp/tools/model-trace.js +55 -0
  117. package/dist/mcp/tools/model-trace.js.map +7 -0
  118. package/dist/mcp/tools/model-update.js +105 -0
  119. package/dist/mcp/tools/model-update.js.map +7 -0
  120. package/dist/mcp/tools/model-validate.js +78 -0
  121. package/dist/mcp/tools/model-validate.js.map +7 -0
  122. package/dist/mcp/tools/shared.js +103 -0
  123. package/dist/mcp/tools/shared.js.map +7 -0
  124. package/dist/reports/model-layer-report-generator.js +5 -3
  125. package/dist/reports/model-layer-report-generator.js.map +2 -2
  126. package/dist/reports/model-readme-generator.js +116 -0
  127. package/dist/reports/model-readme-generator.js.map +7 -0
  128. package/dist/reports/model-report-data.js +37 -1
  129. package/dist/reports/model-report-data.js.map +2 -2
  130. package/dist/reports/model-report-orchestrator.js +29 -6
  131. package/dist/reports/model-report-orchestrator.js.map +2 -2
  132. package/dist/reports/model-report-utils.js +9 -0
  133. package/dist/reports/model-report-utils.js.map +7 -0
  134. package/dist/schemas/bundled/analyzers/cbm.json +2 -2
  135. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  136. package/dist/schemas/bundled/api.json +1 -1
  137. package/dist/schemas/bundled/apm.json +1 -1
  138. package/dist/schemas/bundled/application.json +12 -1
  139. package/dist/schemas/bundled/base.json +1 -1
  140. package/dist/schemas/bundled/business.json +1 -1
  141. package/dist/schemas/bundled/data-model.json +38 -2
  142. package/dist/schemas/bundled/data-store.json +1 -1
  143. package/dist/schemas/bundled/manifest.json +4 -4
  144. package/dist/schemas/bundled/motivation.json +1 -1
  145. package/dist/schemas/bundled/navigation.json +1 -1
  146. package/dist/schemas/bundled/product.json +1 -1
  147. package/dist/schemas/bundled/security.json +1 -1
  148. package/dist/schemas/bundled/technology.json +1 -1
  149. package/dist/schemas/bundled/testing.json +1 -1
  150. package/dist/schemas/bundled/ux.json +1 -1
  151. package/dist/server/server.js +22 -3
  152. package/dist/server/server.js.map +2 -2
  153. package/dist/telemetry/config.js +2 -40
  154. package/dist/telemetry/config.js.map +2 -2
  155. package/dist/telemetry/index.js +1 -1
  156. package/dist/types/index.js.map +2 -2
  157. package/dist/utils/ansi-suppressor.js +25 -0
  158. package/dist/utils/ansi-suppressor.js.map +7 -0
  159. package/dist/utils/color-utils.js +34 -0
  160. package/dist/utils/color-utils.js.map +7 -0
  161. package/dist/utils/element-reference-shape.js +15 -1
  162. package/dist/utils/element-reference-shape.js.map +3 -3
  163. package/dist/utils/errors.js +69 -10
  164. package/dist/utils/errors.js.map +2 -2
  165. package/dist/utils/globals.js +17 -1
  166. package/dist/utils/globals.js.map +2 -2
  167. package/dist/utils/project-paths.js +24 -2
  168. package/dist/utils/project-paths.js.map +2 -2
  169. package/dist/utils/reference-path-parser.js +49 -0
  170. package/dist/utils/reference-path-parser.js.map +7 -0
  171. package/dist/utils/source-reference.js +5 -0
  172. package/dist/utils/source-reference.js.map +2 -2
  173. package/dist/utils/spec-version.js +10 -2
  174. package/dist/utils/spec-version.js.map +2 -2
  175. package/dist/validators/composed-reference-validator.js +262 -0
  176. package/dist/validators/composed-reference-validator.js.map +7 -0
  177. package/dist/validators/reference-validator.js +59 -8
  178. package/dist/validators/reference-validator.js.map +3 -3
  179. package/dist/validators/relationship-schema-validator.js +30 -2
  180. package/dist/validators/relationship-schema-validator.js.map +3 -3
  181. package/package.json +6 -3
@@ -1,6 +1,6 @@
1
1
  import { fileExists } from "./file-io.js";
2
- import { join, dirname } from "path";
3
- const MAX_SEARCH_DEPTH = 5, PROJECT_FOLDER_NAME = "documentation-robotics", SPEC_FOLDER_NAME = ".dr";
2
+ import { join, dirname, resolve } from "path";
3
+ const MAX_SEARCH_DEPTH = 5, PROJECT_FOLDER_NAME = "documentation-robotics", SPEC_FOLDER_NAME = ".dr", FARM_FILE_NAME = "farm.yaml";
4
4
  async function findProjectRoot(startPath) {
5
5
  let currentPath = startPath || process.cwd();
6
6
  for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth++) {
@@ -35,7 +35,29 @@ async function getSpecReferencePath(startPath) {
35
35
  async function isInDRProject(startPath) {
36
36
  return await findProjectRoot(startPath) !== null;
37
37
  }
38
+ async function findFarmRoot(startPath) {
39
+ if (process.env.DR_FARM_PATH) {
40
+ const envPath = resolve(process.env.DR_FARM_PATH), farmFilePath = join(envPath, FARM_FILE_NAME);
41
+ if (await fileExists(farmFilePath))
42
+ return envPath;
43
+ throw new Error(`Invalid DR_FARM_PATH: ${process.env.DR_FARM_PATH}
44
+ Expected farm.yaml at: ${farmFilePath}
45
+ Ensure DR_FARM_PATH points to a directory containing a farm.yaml file.`);
46
+ }
47
+ let currentPath = startPath || process.cwd();
48
+ for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth++) {
49
+ const farmFilePath = join(currentPath, FARM_FILE_NAME);
50
+ if (await fileExists(farmFilePath))
51
+ return currentPath;
52
+ const parentPath = dirname(currentPath);
53
+ if (parentPath === currentPath)
54
+ break;
55
+ currentPath = parentPath;
56
+ }
57
+ return null;
58
+ }
38
59
  export {
60
+ findFarmRoot,
39
61
  findProjectRoot,
40
62
  getDocumentationRobotsPath,
41
63
  getModelPath,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/project-paths.ts"],
4
- "sourcesContent": ["/**\n * Project Path Utilities\n *\n * Handles finding project root and related paths in the DR project structure:\n *\n * my-project/\n * \u251C\u2500\u2500 .dr/ # Reference spec (ephemeral, git-ignored)\n * \u2502 \u251C\u2500\u2500 manifest.json # Installed spec version\n * \u2502 \u2514\u2500\u2500 schemas/\n * \u2514\u2500\u2500 documentation-robotics/ # Project model (permanent, git-committed)\n * \u251C\u2500\u2500 model/\n * \u2502 \u251C\u2500\u2500 manifest.yaml # Model spec version\n * \u2502 \u2514\u2500\u2500 layers/\n * \u251C\u2500\u2500 annotations/\n * \u2514\u2500\u2500 changesets/\n */\n\nimport { fileExists } from \"./file-io.js\";\nimport { join, dirname } from \"path\";\n\nconst MAX_SEARCH_DEPTH = 5;\nconst PROJECT_FOLDER_NAME = \"documentation-robotics\";\nconst SPEC_FOLDER_NAME = \".dr\";\n\n/**\n * Find the project root by searching for the documentation-robotics/ folder\n *\n * Searches upward from the current working directory up to MAX_SEARCH_DEPTH levels.\n * Returns the directory containing the documentation_robotics/ folder.\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to project root, or null if not found\n *\n * @example\n * // From /home/user/my-project/documentation-robotics/model/01_motivation\n * // Returns: /home/user/my-project\n * const root = await findProjectRoot();\n */\nexport async function findProjectRoot(startPath?: string): Promise<string | null> {\n let currentPath = startPath || process.cwd();\n\n for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth++) {\n const projectPath = join(currentPath, PROJECT_FOLDER_NAME);\n\n if (await fileExists(projectPath)) {\n return currentPath;\n }\n\n // Move up one directory\n const parentPath = dirname(currentPath);\n\n // Check if we've reached the root\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n }\n\n return null;\n}\n\n/**\n * Get the path to the documentation-robotics/ folder\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to documentation-robotics/ folder, or null if not found\n */\nexport async function getDocumentationRobotsPath(startPath?: string): Promise<string | null> {\n const root = await findProjectRoot(startPath);\n return root ? join(root, PROJECT_FOLDER_NAME) : null;\n}\n\n/**\n * Get the path to the model directory (documentation-robotics/model/)\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to model directory, or null if not found\n */\nexport async function getModelPath(startPath?: string): Promise<string | null> {\n const drPath = await getDocumentationRobotsPath(startPath);\n if (!drPath) return null;\n\n const modelPath = join(drPath, \"model\");\n return (await fileExists(modelPath)) ? modelPath : null;\n}\n\n/**\n * Get the path to the .dr/ folder (spec reference directory)\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to .dr/ folder, or null if not found\n */\nexport async function getSpecReferencePath(startPath?: string): Promise<string | null> {\n const root = await findProjectRoot(startPath);\n if (!root) return null;\n\n const specPath = join(root, SPEC_FOLDER_NAME);\n return (await fileExists(specPath)) ? specPath : null;\n}\n\n/**\n * Check if currently in a DR project\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns True if documentation-robotics/ folder exists in hierarchy\n */\nexport async function isInDRProject(startPath?: string): Promise<boolean> {\n return (await findProjectRoot(startPath)) !== null;\n}\n"],
5
- "mappings": "AAiBA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,eAAe;AAE9B,MAAM,mBAAmB,GACnB,sBAAsB,0BACtB,mBAAmB;AAgBzB,eAAsB,gBAAgB,WAAkB;AACtD,MAAI,cAAc,aAAa,QAAQ,IAAG;AAE1C,WAAS,QAAQ,GAAG,QAAQ,kBAAkB,SAAS;AACrD,UAAM,cAAc,KAAK,aAAa,mBAAmB;AAEzD,QAAI,MAAM,WAAW,WAAW;AAC9B,aAAO;AAIT,UAAM,aAAa,QAAQ,WAAW;AAGtC,QAAI,eAAe;AACjB;AAGF,kBAAc;EAChB;AAEA,SAAO;AACT;AAQA,eAAsB,2BAA2B,WAAkB;AACjE,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,SAAO,OAAO,KAAK,MAAM,mBAAmB,IAAI;AAClD;AAQA,eAAsB,aAAa,WAAkB;AACnD,QAAM,SAAS,MAAM,2BAA2B,SAAS;AACzD,MAAI,CAAC;AAAQ,WAAO;AAEpB,QAAM,YAAY,KAAK,QAAQ,OAAO;AACtC,SAAQ,MAAM,WAAW,SAAS,IAAK,YAAY;AACrD;AAQA,eAAsB,qBAAqB,WAAkB;AAC3D,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,MAAI,CAAC;AAAM,WAAO;AAElB,QAAM,WAAW,KAAK,MAAM,gBAAgB;AAC5C,SAAQ,MAAM,WAAW,QAAQ,IAAK,WAAW;AACnD;AAQA,eAAsB,cAAc,WAAkB;AACpD,SAAQ,MAAM,gBAAgB,SAAS,MAAO;AAChD;",
4
+ "sourcesContent": ["/**\n * Project Path Utilities\n *\n * Handles finding project root and related paths in the DR project structure:\n *\n * my-project/\n * \u251C\u2500\u2500 .dr/ # Reference spec (ephemeral, git-ignored)\n * \u2502 \u251C\u2500\u2500 manifest.json # Installed spec version\n * \u2502 \u2514\u2500\u2500 schemas/\n * \u2514\u2500\u2500 documentation-robotics/ # Project model (permanent, git-committed)\n * \u251C\u2500\u2500 model/\n * \u2502 \u251C\u2500\u2500 manifest.yaml # Model spec version\n * \u2502 \u2514\u2500\u2500 layers/\n * \u251C\u2500\u2500 annotations/\n * \u2514\u2500\u2500 changesets/\n */\n\nimport { fileExists } from \"./file-io.js\";\nimport { join, dirname, resolve } from \"path\";\n\nconst MAX_SEARCH_DEPTH = 5;\nconst PROJECT_FOLDER_NAME = \"documentation-robotics\";\nconst SPEC_FOLDER_NAME = \".dr\";\nconst FARM_FILE_NAME = \"farm.yaml\";\n\n/**\n * Find the project root by searching for the documentation-robotics/ folder\n *\n * Searches upward from the current working directory up to MAX_SEARCH_DEPTH levels.\n * Returns the directory containing the documentation_robotics/ folder.\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to project root, or null if not found\n *\n * @example\n * // From /home/user/my-project/documentation-robotics/model/01_motivation\n * // Returns: /home/user/my-project\n * const root = await findProjectRoot();\n */\nexport async function findProjectRoot(startPath?: string): Promise<string | null> {\n let currentPath = startPath || process.cwd();\n\n for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth++) {\n const projectPath = join(currentPath, PROJECT_FOLDER_NAME);\n\n if (await fileExists(projectPath)) {\n return currentPath;\n }\n\n // Move up one directory\n const parentPath = dirname(currentPath);\n\n // Check if we've reached the root\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n }\n\n return null;\n}\n\n/**\n * Get the path to the documentation-robotics/ folder\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to documentation-robotics/ folder, or null if not found\n */\nexport async function getDocumentationRobotsPath(startPath?: string): Promise<string | null> {\n const root = await findProjectRoot(startPath);\n return root ? join(root, PROJECT_FOLDER_NAME) : null;\n}\n\n/**\n * Get the path to the model directory (documentation-robotics/model/)\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to model directory, or null if not found\n */\nexport async function getModelPath(startPath?: string): Promise<string | null> {\n const drPath = await getDocumentationRobotsPath(startPath);\n if (!drPath) return null;\n\n const modelPath = join(drPath, \"model\");\n return (await fileExists(modelPath)) ? modelPath : null;\n}\n\n/**\n * Get the path to the .dr/ folder (spec reference directory)\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to .dr/ folder, or null if not found\n */\nexport async function getSpecReferencePath(startPath?: string): Promise<string | null> {\n const root = await findProjectRoot(startPath);\n if (!root) return null;\n\n const specPath = join(root, SPEC_FOLDER_NAME);\n return (await fileExists(specPath)) ? specPath : null;\n}\n\n/**\n * Check if currently in a DR project\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns True if documentation-robotics/ folder exists in hierarchy\n */\nexport async function isInDRProject(startPath?: string): Promise<boolean> {\n return (await findProjectRoot(startPath)) !== null;\n}\n\n/**\n * Find the farm root by searching for the farm.yaml file\n *\n * Resolution order:\n * 1. DR_FARM_PATH environment variable (if set, must point to a directory with farm.yaml)\n * 2. Directory walk upward from startPath (default: process.cwd()) up to MAX_SEARCH_DEPTH levels\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to farm root, or null if not found\n *\n * @example\n * // From /home/user/architecture-farm/service-a-model/documentation-robotics/model\n * // Returns: /home/user/architecture-farm (if farm.yaml exists there)\n * const farmRoot = await findFarmRoot();\n *\n * @example\n * // With DR_FARM_PATH environment variable set\n * // Returns: /path/specified/in/env (if farm.yaml exists there)\n * const farmRoot = await findFarmRoot();\n */\nexport async function findFarmRoot(startPath?: string): Promise<string | null> {\n // Check DR_FARM_PATH environment variable first\n if (process.env.DR_FARM_PATH) {\n const envPath = resolve(process.env.DR_FARM_PATH);\n const farmFilePath = join(envPath, FARM_FILE_NAME);\n if (await fileExists(farmFilePath)) {\n return envPath;\n }\n // If DR_FARM_PATH is set but invalid, throw a clear error rather than silently returning null\n throw new Error(\n `Invalid DR_FARM_PATH: ${process.env.DR_FARM_PATH}\\n` +\n `Expected farm.yaml at: ${farmFilePath}\\n` +\n `Ensure DR_FARM_PATH points to a directory containing a farm.yaml file.`\n );\n }\n\n let currentPath = startPath || process.cwd();\n\n for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth++) {\n const farmFilePath = join(currentPath, FARM_FILE_NAME);\n\n if (await fileExists(farmFilePath)) {\n return currentPath;\n }\n\n // Move up one directory\n const parentPath = dirname(currentPath);\n\n // Check if we've reached the root\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n }\n\n return null;\n}\n"],
5
+ "mappings": "AAiBA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,SAAS,eAAe;AAEvC,MAAM,mBAAmB,GACnB,sBAAsB,0BACtB,mBAAmB,OACnB,iBAAiB;AAgBvB,eAAsB,gBAAgB,WAAkB;AACtD,MAAI,cAAc,aAAa,QAAQ,IAAG;AAE1C,WAAS,QAAQ,GAAG,QAAQ,kBAAkB,SAAS;AACrD,UAAM,cAAc,KAAK,aAAa,mBAAmB;AAEzD,QAAI,MAAM,WAAW,WAAW;AAC9B,aAAO;AAIT,UAAM,aAAa,QAAQ,WAAW;AAGtC,QAAI,eAAe;AACjB;AAGF,kBAAc;EAChB;AAEA,SAAO;AACT;AAQA,eAAsB,2BAA2B,WAAkB;AACjE,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,SAAO,OAAO,KAAK,MAAM,mBAAmB,IAAI;AAClD;AAQA,eAAsB,aAAa,WAAkB;AACnD,QAAM,SAAS,MAAM,2BAA2B,SAAS;AACzD,MAAI,CAAC;AAAQ,WAAO;AAEpB,QAAM,YAAY,KAAK,QAAQ,OAAO;AACtC,SAAQ,MAAM,WAAW,SAAS,IAAK,YAAY;AACrD;AAQA,eAAsB,qBAAqB,WAAkB;AAC3D,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,MAAI,CAAC;AAAM,WAAO;AAElB,QAAM,WAAW,KAAK,MAAM,gBAAgB;AAC5C,SAAQ,MAAM,WAAW,QAAQ,IAAK,WAAW;AACnD;AAQA,eAAsB,cAAc,WAAkB;AACpD,SAAQ,MAAM,gBAAgB,SAAS,MAAO;AAChD;AAsBA,eAAsB,aAAa,WAAkB;AAEnD,MAAI,QAAQ,IAAI,cAAc;AAC5B,UAAM,UAAU,QAAQ,QAAQ,IAAI,YAAY,GAC1C,eAAe,KAAK,SAAS,cAAc;AACjD,QAAI,MAAM,WAAW,YAAY;AAC/B,aAAO;AAGT,UAAM,IAAI,MACR,yBAAyB,QAAQ,IAAI,YAAY;yBACvB,YAAY;uEACkC;EAE5E;AAEA,MAAI,cAAc,aAAa,QAAQ,IAAG;AAE1C,WAAS,QAAQ,GAAG,QAAQ,kBAAkB,SAAS;AACrD,UAAM,eAAe,KAAK,aAAa,cAAc;AAErD,QAAI,MAAM,WAAW,YAAY;AAC/B,aAAO;AAIT,UAAM,aAAa,QAAQ,WAAW;AAGtC,QAAI,eAAe;AACjB;AAGF,kBAAc;EAChB;AAEA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,49 @@
1
+ class ReferencePathParseError extends Error {
2
+ kind = "malformed-qualified-path";
3
+ constructor(message) {
4
+ super(message), this.name = "ReferencePathParseError", Object.setPrototypeOf(this, ReferencePathParseError.prototype);
5
+ }
6
+ }
7
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, QUALIFIED_PREFIX_PATTERN = /^@([a-z0-9][a-z0-9_-]*)\//i, ELEMENT_PATH_PATTERN = /^[a-z_][a-z0-9_-]*(\.[a-z_][a-z0-9_-]*)+$/;
8
+ function parseReferencePath(path) {
9
+ if (!path || typeof path != "string" || path.trim() === "")
10
+ throw createParseError("Empty reference path");
11
+ const trimmedPath = path.trim(), qualifiedMatch = trimmedPath.match(QUALIFIED_PREFIX_PATTERN);
12
+ if (qualifiedMatch) {
13
+ const modelName = qualifiedMatch[1].toLowerCase(), segment = trimmedPath.substring(qualifiedMatch[0].length);
14
+ if (!segment || segment.trim() === "")
15
+ throw createParseError("Qualified reference missing segment after '@{model-name}/'");
16
+ if (UUID_PATTERN.test(segment))
17
+ throw createParseError("Qualified references cannot reference UUIDs; use '@{model-name}/{layer}.{type}.{name}' format");
18
+ if (!ELEMENT_PATH_PATTERN.test(segment))
19
+ throw createParseError(`Qualified reference has invalid element path: '${segment}'. Use format '@{model-name}/{layer}.{type}.{name}'`);
20
+ return {
21
+ modelName,
22
+ segment,
23
+ isQualified: !0
24
+ };
25
+ }
26
+ if (UUID_PATTERN.test(trimmedPath))
27
+ return {
28
+ modelName: void 0,
29
+ segment: trimmedPath,
30
+ isQualified: !1
31
+ };
32
+ if (ELEMENT_PATH_PATTERN.test(trimmedPath))
33
+ return {
34
+ modelName: void 0,
35
+ segment: trimmedPath,
36
+ isQualified: !1
37
+ };
38
+ throw createParseError(`Invalid reference path format: '${trimmedPath}'. Use one of: '{layer}.{type}.{name}', UUID, or '@{model-name}/{layer}.{type}.{name}'`);
39
+ }
40
+ function createParseError(message) {
41
+ return new ReferencePathParseError(message);
42
+ }
43
+ export {
44
+ QUALIFIED_PREFIX_PATTERN,
45
+ ReferencePathParseError,
46
+ UUID_PATTERN,
47
+ parseReferencePath
48
+ };
49
+ //# sourceMappingURL=reference-path-parser.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/reference-path-parser.ts"],
4
+ "sourcesContent": ["/**\n * Reference path parser for qualified and unqualified element references.\n *\n * Supports parsing reference targets in the following formats:\n * - Unqualified: {layer}.{type}.{name} (e.g., motivation.goal.increase-revenue)\n * - Unqualified UUID: UUIDv4 format\n * - Qualified: @{model-name}/{layer}.{type}.{name} (e.g., @auth-service/api.operation.authenticate)\n *\n * The parser distinguishes between parse-level errors (malformed qualified paths)\n * and reference resolution errors (unknown model or broken reference).\n */\n\nexport interface ParsedReferencePath {\n /** Model qualifier if present (without @ or /) */\n modelName?: string;\n /** The underlying {layer}.{type}.{name} segment or UUID */\n segment: string;\n /** Whether this is a qualified reference */\n isQualified: boolean;\n}\n\nexport class ReferencePathParseError extends Error {\n readonly kind = \"malformed-qualified-path\" as const;\n\n constructor(message: string) {\n super(message);\n this.name = \"ReferencePathParseError\";\n Object.setPrototypeOf(this, ReferencePathParseError.prototype);\n }\n}\n\nexport const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nexport const QUALIFIED_PREFIX_PATTERN = /^@([a-z0-9][a-z0-9_-]*)\\//i;\nconst ELEMENT_PATH_PATTERN = /^[a-z_][a-z0-9_-]*(\\.[a-z_][a-z0-9_-]*)+$/;\n\n/**\n * Parse a reference path, extracting model qualifier and underlying segment.\n *\n * @param path The reference path string to parse\n * @returns Parsed reference information on success\n * @throws ReferencePathParseError if the path is malformed\n *\n * @example\n * // Unqualified path\n * parseReferencePath(\"motivation.goal.increase-revenue\")\n * // => { modelName: undefined, segment: \"motivation.goal.increase-revenue\", isQualified: false }\n *\n * // Qualified path\n * parseReferencePath(\"@auth-service/api.operation.authenticate\")\n * // => { modelName: \"auth-service\", segment: \"api.operation.authenticate\", isQualified: true }\n *\n * // UUID\n * parseReferencePath(\"550e8400-e29b-41d4-a716-446655440000\")\n * // => { modelName: undefined, segment: \"550e8400-e29b-41d4-a716-446655440000\", isQualified: false }\n *\n * // Qualified UUID (not yet supported, will throw)\n * parseReferencePath(\"@model/550e8400-e29b-41d4-a716-446655440000\")\n * // => throws ReferencePathParseError\n */\nexport function parseReferencePath(path: string): ParsedReferencePath {\n if (!path || typeof path !== \"string\" || path.trim() === \"\") {\n throw createParseError(\"Empty reference path\");\n }\n\n const trimmedPath = path.trim();\n\n // Check for qualified prefix (@model-name/)\n const qualifiedMatch = trimmedPath.match(QUALIFIED_PREFIX_PATTERN);\n\n if (qualifiedMatch) {\n const modelName = qualifiedMatch[1].toLowerCase();\n const segment = trimmedPath.substring(qualifiedMatch[0].length);\n\n // Validate segment format\n if (!segment || segment.trim() === \"\") {\n throw createParseError(\n \"Qualified reference missing segment after '@{model-name}/'\"\n );\n }\n\n // Qualified paths must use dot-separated format, not UUIDs\n if (UUID_PATTERN.test(segment)) {\n throw createParseError(\n \"Qualified references cannot reference UUIDs; use '@{model-name}/{layer}.{type}.{name}' format\"\n );\n }\n\n // Validate segment is a valid element path\n if (!ELEMENT_PATH_PATTERN.test(segment)) {\n throw createParseError(\n `Qualified reference has invalid element path: '${segment}'. Use format '@{model-name}/{layer}.{type}.{name}'`\n );\n }\n\n return {\n modelName,\n segment,\n isQualified: true,\n };\n }\n\n // Unqualified reference\n // Accept either UUID or dot-separated element path\n if (UUID_PATTERN.test(trimmedPath)) {\n return {\n modelName: undefined,\n segment: trimmedPath,\n isQualified: false,\n };\n }\n\n if (ELEMENT_PATH_PATTERN.test(trimmedPath)) {\n return {\n modelName: undefined,\n segment: trimmedPath,\n isQualified: false,\n };\n }\n\n // Invalid format\n throw createParseError(\n `Invalid reference path format: '${trimmedPath}'. ` +\n \"Use one of: '{layer}.{type}.{name}', UUID, or '@{model-name}/{layer}.{type}.{name}'\"\n );\n}\n\n\nfunction createParseError(message: string): ReferencePathParseError {\n return new ReferencePathParseError(message);\n}\n"],
5
+ "mappings": "AAqBM,MAAO,gCAAgC,MAAK;EACvC,OAAO;EAEhB,YAAY,SAAe;AACzB,UAAM,OAAO,GACb,KAAK,OAAO,2BACZ,OAAO,eAAe,MAAM,wBAAwB,SAAS;EAC/D;;AAGK,MAAM,eAAe,mEACf,2BAA2B,8BAClC,uBAAuB;AA0BvB,SAAU,mBAAmB,MAAY;AAC7C,MAAI,CAAC,QAAQ,OAAO,QAAS,YAAY,KAAK,KAAI,MAAO;AACvD,UAAM,iBAAiB,sBAAsB;AAG/C,QAAM,cAAc,KAAK,KAAI,GAGvB,iBAAiB,YAAY,MAAM,wBAAwB;AAEjE,MAAI,gBAAgB;AAClB,UAAM,YAAY,eAAe,CAAC,EAAE,YAAW,GACzC,UAAU,YAAY,UAAU,eAAe,CAAC,EAAE,MAAM;AAG9D,QAAI,CAAC,WAAW,QAAQ,KAAI,MAAO;AACjC,YAAM,iBACJ,4DAA4D;AAKhE,QAAI,aAAa,KAAK,OAAO;AAC3B,YAAM,iBACJ,+FAA+F;AAKnG,QAAI,CAAC,qBAAqB,KAAK,OAAO;AACpC,YAAM,iBACJ,kDAAkD,OAAO,qDAAqD;AAIlH,WAAO;MACL;MACA;MACA,aAAa;;EAEjB;AAIA,MAAI,aAAa,KAAK,WAAW;AAC/B,WAAO;MACL,WAAW;MACX,SAAS;MACT,aAAa;;AAIjB,MAAI,qBAAqB,KAAK,WAAW;AACvC,WAAO;MACL,WAAW;MACX,SAAS;MACT,aAAa;;AAKjB,QAAM,iBACJ,mCAAmC,WAAW,wFACyC;AAE3F;AAGA,SAAS,iBAAiB,SAAe;AACvC,SAAO,IAAI,wBAAwB,OAAO;AAC5C;",
6
+ "names": []
7
+ }
@@ -4,6 +4,11 @@ function validateSourceReferenceOptions(options) {
4
4
  process.stderr.isTTY && console.warn("Warning: no source provenance specified. Add --source-file and --source-provenance to maintain traceability for drift detection and /dr-sync workflows.");
5
5
  return;
6
6
  }
7
+ if (options.sourceSymbol && !options.sourceFile)
8
+ throw new CLIError("--source-symbol requires --source-file", 1, [
9
+ "Provide the source file that contains the symbol",
10
+ 'Example: --source-file "src/auth.ts" --source-symbol "validateToken"'
11
+ ]);
7
12
  if (!options.sourceProvenance)
8
13
  throw new CLIError("--source-provenance is required when specifying source reference", 1, [
9
14
  "Specify provenance type: extracted, manual, inferred, or generated"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/source-reference.ts"],
4
- "sourcesContent": ["/**\n * Source Reference Utilities\n * Shared validation and builder functions for source reference options\n */\n\nimport type { SourceReference, ProvenanceType } from \"../types/index.js\";\nimport { CLIError } from \"./errors.js\";\n\nexport interface SourceReferenceOptions {\n sourceFile?: string;\n sourceSymbol?: string;\n sourceProvenance?: string;\n sourceRepoRemote?: string;\n sourceRepoCommit?: string;\n}\n\n/**\n * Validate source reference options\n */\nexport function validateSourceReferenceOptions(options: SourceReferenceOptions): void {\n const hasSourceOptions =\n options.sourceFile ||\n options.sourceSymbol ||\n options.sourceProvenance ||\n options.sourceRepoRemote ||\n options.sourceRepoCommit;\n\n if (!hasSourceOptions) {\n if (process.stderr.isTTY) {\n console.warn(\n \"Warning: no source provenance specified. Add --source-file and --source-provenance to maintain traceability for drift detection and /dr-sync workflows.\"\n );\n }\n return;\n }\n\n // Require provenance first so we can use it to determine file requirements\n if (!options.sourceProvenance) {\n throw new CLIError(\"--source-provenance is required when specifying source reference\", 1, [\n \"Specify provenance type: extracted, manual, inferred, or generated\",\n ]);\n }\n\n // For inferred and generated provenance, source file is optional (no code exists yet)\n const fileOptionalProvenances = [\"inferred\", \"generated\"];\n if (!options.sourceFile && !fileOptionalProvenances.includes(options.sourceProvenance)) {\n throw new CLIError(\"--source-file is required when specifying source reference\", 1, [\n \"Provide source file path relative to repository root\",\n 'Example: --source-file \"src/services/auth.ts\"',\n \"Note: --source-file is optional when --source-provenance is 'inferred' or 'generated'\",\n ]);\n }\n\n // Validate provenance enum\n const validProvenance = [\"extracted\", \"manual\", \"inferred\", \"generated\"];\n if (!validProvenance.includes(options.sourceProvenance)) {\n throw new CLIError(`Invalid --source-provenance value: \"${options.sourceProvenance}\"`, 1, [\n `Must be one of: ${validProvenance.join(\", \")}`,\n ]);\n }\n\n // Validate commit SHA format if provided\n if (options.sourceRepoCommit && !/^[0-9a-f]{40}$/.test(options.sourceRepoCommit)) {\n throw new CLIError(`Invalid --source-repo-commit value: \"${options.sourceRepoCommit}\"`, 1, [\n \"Must be exactly 40 hexadecimal characters (full Git commit SHA)\",\n ]);\n }\n\n // Require both or neither repo context fields\n if (options.sourceRepoRemote && !options.sourceRepoCommit) {\n throw new CLIError(\n \"--source-repo-commit is required when --source-repo-remote is provided\",\n 1,\n [\"Provide both remote URL and commit SHA for repository context\"]\n );\n }\n\n if (options.sourceRepoCommit && !options.sourceRepoRemote) {\n throw new CLIError(\n \"--source-repo-remote is required when --source-repo-commit is provided\",\n 1,\n [\"Provide both remote URL and commit SHA for repository context\"]\n );\n }\n}\n\n/**\n * Build SourceReference object from options\n */\nexport function buildSourceReference(options: SourceReferenceOptions): SourceReference | undefined {\n if (!options.sourceProvenance) {\n return undefined;\n }\n\n const reference: SourceReference = {\n provenance: options.sourceProvenance as ProvenanceType,\n };\n\n if (options.sourceFile) {\n reference.locations = [\n {\n file: options.sourceFile,\n ...(options.sourceSymbol && { symbol: options.sourceSymbol }),\n },\n ];\n }\n\n if (options.sourceRepoRemote && options.sourceRepoCommit) {\n reference.repository = {\n url: options.sourceRepoRemote,\n commit: options.sourceRepoCommit,\n };\n }\n\n return reference;\n}\n"],
5
- "mappings": "AAMA,SAAS,gBAAgB;AAanB,SAAU,+BAA+B,SAA+B;AAQ5E,MAAI,EANF,QAAQ,cACR,QAAQ,gBACR,QAAQ,oBACR,QAAQ,oBACR,QAAQ,mBAEa;AACrB,IAAI,QAAQ,OAAO,SACjB,QAAQ,KACN,yJAAyJ;AAG7J;EACF;AAGA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,SAAS,oEAAoE,GAAG;MACxF;KACD;AAIH,QAAM,0BAA0B,CAAC,YAAY,WAAW;AACxD,MAAI,CAAC,QAAQ,cAAc,CAAC,wBAAwB,SAAS,QAAQ,gBAAgB;AACnF,UAAM,IAAI,SAAS,8DAA8D,GAAG;MAClF;MACA;MACA;KACD;AAIH,QAAM,kBAAkB,CAAC,aAAa,UAAU,YAAY,WAAW;AACvE,MAAI,CAAC,gBAAgB,SAAS,QAAQ,gBAAgB;AACpD,UAAM,IAAI,SAAS,uCAAuC,QAAQ,gBAAgB,KAAK,GAAG;MACxF,mBAAmB,gBAAgB,KAAK,IAAI,CAAC;KAC9C;AAIH,MAAI,QAAQ,oBAAoB,CAAC,iBAAiB,KAAK,QAAQ,gBAAgB;AAC7E,UAAM,IAAI,SAAS,wCAAwC,QAAQ,gBAAgB,KAAK,GAAG;MACzF;KACD;AAIH,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,0EACA,GACA,CAAC,+DAA+D,CAAC;AAIrE,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,0EACA,GACA,CAAC,+DAA+D,CAAC;AAGvE;AAKM,SAAU,qBAAqB,SAA+B;AAClE,MAAI,CAAC,QAAQ;AACX;AAGF,QAAM,YAA6B;IACjC,YAAY,QAAQ;;AAGtB,SAAI,QAAQ,eACV,UAAU,YAAY;IACpB;MACE,MAAM,QAAQ;MACd,GAAI,QAAQ,gBAAgB,EAAE,QAAQ,QAAQ,aAAY;;MAK5D,QAAQ,oBAAoB,QAAQ,qBACtC,UAAU,aAAa;IACrB,KAAK,QAAQ;IACb,QAAQ,QAAQ;MAIb;AACT;",
4
+ "sourcesContent": ["/**\n * Source Reference Utilities\n * Shared validation and builder functions for source reference options\n */\n\nimport type { SourceReference, ProvenanceType } from \"../types/index.js\";\nimport { CLIError } from \"./errors.js\";\n\nexport interface SourceReferenceOptions {\n sourceFile?: string;\n sourceSymbol?: string;\n sourceProvenance?: string;\n sourceRepoRemote?: string;\n sourceRepoCommit?: string;\n}\n\n/**\n * Validate source reference options\n */\nexport function validateSourceReferenceOptions(options: SourceReferenceOptions): void {\n const hasSourceOptions =\n options.sourceFile ||\n options.sourceSymbol ||\n options.sourceProvenance ||\n options.sourceRepoRemote ||\n options.sourceRepoCommit;\n\n if (!hasSourceOptions) {\n if (process.stderr.isTTY) {\n console.warn(\n \"Warning: no source provenance specified. Add --source-file and --source-provenance to maintain traceability for drift detection and /dr-sync workflows.\"\n );\n }\n return;\n }\n\n // Require source-symbol to have source-file (both or neither is not acceptable here)\n if (options.sourceSymbol && !options.sourceFile) {\n throw new CLIError(\"--source-symbol requires --source-file\", 1, [\n \"Provide the source file that contains the symbol\",\n 'Example: --source-file \"src/auth.ts\" --source-symbol \"validateToken\"',\n ]);\n }\n\n // Require provenance first so we can use it to determine file requirements\n if (!options.sourceProvenance) {\n throw new CLIError(\"--source-provenance is required when specifying source reference\", 1, [\n \"Specify provenance type: extracted, manual, inferred, or generated\",\n ]);\n }\n\n // For inferred and generated provenance, source file is optional (no code exists yet)\n const fileOptionalProvenances = [\"inferred\", \"generated\"];\n if (!options.sourceFile && !fileOptionalProvenances.includes(options.sourceProvenance)) {\n throw new CLIError(\"--source-file is required when specifying source reference\", 1, [\n \"Provide source file path relative to repository root\",\n 'Example: --source-file \"src/services/auth.ts\"',\n \"Note: --source-file is optional when --source-provenance is 'inferred' or 'generated'\",\n ]);\n }\n\n // Validate provenance enum\n const validProvenance = [\"extracted\", \"manual\", \"inferred\", \"generated\"];\n if (!validProvenance.includes(options.sourceProvenance)) {\n throw new CLIError(`Invalid --source-provenance value: \"${options.sourceProvenance}\"`, 1, [\n `Must be one of: ${validProvenance.join(\", \")}`,\n ]);\n }\n\n // Validate commit SHA format if provided\n if (options.sourceRepoCommit && !/^[0-9a-f]{40}$/.test(options.sourceRepoCommit)) {\n throw new CLIError(`Invalid --source-repo-commit value: \"${options.sourceRepoCommit}\"`, 1, [\n \"Must be exactly 40 hexadecimal characters (full Git commit SHA)\",\n ]);\n }\n\n // Require both or neither repo context fields\n if (options.sourceRepoRemote && !options.sourceRepoCommit) {\n throw new CLIError(\n \"--source-repo-commit is required when --source-repo-remote is provided\",\n 1,\n [\"Provide both remote URL and commit SHA for repository context\"]\n );\n }\n\n if (options.sourceRepoCommit && !options.sourceRepoRemote) {\n throw new CLIError(\n \"--source-repo-remote is required when --source-repo-commit is provided\",\n 1,\n [\"Provide both remote URL and commit SHA for repository context\"]\n );\n }\n}\n\n/**\n * Build SourceReference object from options\n */\nexport function buildSourceReference(options: SourceReferenceOptions): SourceReference | undefined {\n if (!options.sourceProvenance) {\n return undefined;\n }\n\n const reference: SourceReference = {\n provenance: options.sourceProvenance as ProvenanceType,\n };\n\n if (options.sourceFile) {\n reference.locations = [\n {\n file: options.sourceFile,\n ...(options.sourceSymbol && { symbol: options.sourceSymbol }),\n },\n ];\n }\n\n if (options.sourceRepoRemote && options.sourceRepoCommit) {\n reference.repository = {\n url: options.sourceRepoRemote,\n commit: options.sourceRepoCommit,\n };\n }\n\n return reference;\n}\n"],
5
+ "mappings": "AAMA,SAAS,gBAAgB;AAanB,SAAU,+BAA+B,SAA+B;AAQ5E,MAAI,EANF,QAAQ,cACR,QAAQ,gBACR,QAAQ,oBACR,QAAQ,oBACR,QAAQ,mBAEa;AACrB,IAAI,QAAQ,OAAO,SACjB,QAAQ,KACN,yJAAyJ;AAG7J;EACF;AAGA,MAAI,QAAQ,gBAAgB,CAAC,QAAQ;AACnC,UAAM,IAAI,SAAS,0CAA0C,GAAG;MAC9D;MACA;KACD;AAIH,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,SAAS,oEAAoE,GAAG;MACxF;KACD;AAIH,QAAM,0BAA0B,CAAC,YAAY,WAAW;AACxD,MAAI,CAAC,QAAQ,cAAc,CAAC,wBAAwB,SAAS,QAAQ,gBAAgB;AACnF,UAAM,IAAI,SAAS,8DAA8D,GAAG;MAClF;MACA;MACA;KACD;AAIH,QAAM,kBAAkB,CAAC,aAAa,UAAU,YAAY,WAAW;AACvE,MAAI,CAAC,gBAAgB,SAAS,QAAQ,gBAAgB;AACpD,UAAM,IAAI,SAAS,uCAAuC,QAAQ,gBAAgB,KAAK,GAAG;MACxF,mBAAmB,gBAAgB,KAAK,IAAI,CAAC;KAC9C;AAIH,MAAI,QAAQ,oBAAoB,CAAC,iBAAiB,KAAK,QAAQ,gBAAgB;AAC7E,UAAM,IAAI,SAAS,wCAAwC,QAAQ,gBAAgB,KAAK,GAAG;MACzF;KACD;AAIH,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,0EACA,GACA,CAAC,+DAA+D,CAAC;AAIrE,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,0EACA,GACA,CAAC,+DAA+D,CAAC;AAGvE;AAKM,SAAU,qBAAqB,SAA+B;AAClE,MAAI,CAAC,QAAQ;AACX;AAGF,QAAM,YAA6B;IACjC,YAAY,QAAQ;;AAGtB,SAAI,QAAQ,eACV,UAAU,YAAY;IACpB;MACE,MAAM,QAAQ;MACd,GAAI,QAAQ,gBAAgB,EAAE,QAAQ,QAAQ,aAAY;;MAK5D,QAAQ,oBAAoB,QAAQ,qBACtC,UAAU,aAAa;IACrB,KAAK,QAAQ;IACb,QAAQ,QAAQ;MAIb;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,16 @@
1
1
  import { readJSON, fileExists } from "./file-io.js";
2
2
  import { join } from "path";
3
- const CLI_VERSION = "0.1.12", BUNDLED_SPEC_VERSION = "0.9.0";
3
+ import { createRequire } from "node:module";
4
+ const BUNDLED_SPEC_VERSION = "0.10.0";
5
+ function resolveDevVersion() {
6
+ try {
7
+ return createRequire(import.meta.url)("../../package.json").version ?? "0.0.0";
8
+ } catch (error) {
9
+ return console.error(`Failed to resolve dev-mode CLI version from package.json, falling back to "0.0.0": ${error instanceof Error ? error.message : String(error)}`), "0.0.0";
10
+ }
11
+ }
4
12
  function getCliVersion() {
5
- return CLI_VERSION;
13
+ return "0.2.0";
6
14
  }
7
15
  function getCliBundledSpecVersion() {
8
16
  return BUNDLED_SPEC_VERSION;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/spec-version.ts"],
4
- "sourcesContent": ["/**\n * Spec Version Utilities\n *\n * Manages spec version information for the CLI\n */\n\nimport { readJSON, fileExists } from \"./file-io.js\";\nimport { join } from \"path\";\n\n/**\n * CLI version\n * This should match the version in ../package.json\n * Updated during release process\n */\nconst CLI_VERSION = \"0.1.12\";\n\n/**\n * Bundled spec version\n * This should match the version in ../../../spec/VERSION\n * Updated during build process\n */\nconst BUNDLED_SPEC_VERSION = \"0.9.0\";\n\n/**\n * Get the CLI version\n *\n * @returns CLI version string\n */\nexport function getCliVersion(): string {\n return CLI_VERSION;\n}\n\n/**\n * Get the CLI's bundled spec version\n *\n * This is the version of the DR specification that is bundled with this CLI.\n * It represents what version of the spec this CLI supports.\n *\n * @returns Bundled spec version string\n */\nexport function getCliBundledSpecVersion(): string {\n return BUNDLED_SPEC_VERSION;\n}\n\n/**\n * Read spec version from .dr/manifest.json (installed spec reference)\n *\n * @param drPath - Path to .dr/ directory\n * @returns Spec version from .dr/manifest.json, or null if not found\n */\nexport async function getInstalledSpecVersion(drPath: string): Promise<string | null> {\n const manifestPath = join(drPath, \"manifest.json\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n const manifest = await readJSON<{ specVersion?: string }>(manifestPath);\n return manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Read spec version from documentation_robotics/model/manifest.yaml\n *\n * @param modelPath - Path to model directory\n * @returns Spec version from model manifest, or null if not found\n */\nexport async function getModelSpecVersion(modelPath: string): Promise<string | null> {\n const manifestPath = join(modelPath, \"manifest.yaml\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n // Use YAML parser\n const yaml = await import(\"yaml\");\n const fs = await import(\"fs/promises\");\n const content = await fs.readFile(manifestPath, \"utf-8\");\n const manifest = yaml.parse(content) as { spec_version?: string; specVersion?: string };\n // Check snake_case first (Python CLI format), then camelCase (legacy)\n return manifest.spec_version || manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n"],
5
- "mappings": "AAMA,SAAS,UAAU,kBAAkB;AACrC,SAAS,YAAY;AAOrB,MAAM,cAAc,UAOd,uBAAuB;AAOvB,SAAU,gBAAa;AAC3B,SAAO;AACT;AAUM,SAAU,2BAAwB;AACtC,SAAO;AACT;AAQA,eAAsB,wBAAwB,QAAc;AAC1D,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,CAAE,MAAM,WAAW,YAAY;AACjC,WAAO;AAGT,MAAI;AAEF,YADiB,MAAM,SAAmC,YAAY,GACtD,eAAe;EACjC,QAAQ;AACN,WAAO;EACT;AACF;AAQA,eAAsB,oBAAoB,WAAiB;AACzD,QAAM,eAAe,KAAK,WAAW,eAAe;AAEpD,MAAI,CAAE,MAAM,WAAW,YAAY;AACjC,WAAO;AAGT,MAAI;AAEF,UAAM,OAAO,MAAM,OAAO,MAAM,GAE1B,UAAU,OADL,MAAM,OAAO,aAAa,GACZ,SAAS,cAAc,OAAO,GACjD,WAAW,KAAK,MAAM,OAAO;AAEnC,WAAO,SAAS,gBAAgB,SAAS,eAAe;EAC1D,QAAQ;AACN,WAAO;EACT;AACF;",
4
+ "sourcesContent": ["/**\n * Spec Version Utilities\n *\n * Manages spec version information for the CLI\n */\n\nimport { readJSON, fileExists } from \"./file-io.js\";\nimport { join } from \"path\";\nimport { createRequire } from \"node:module\";\n\n// Declare build-time constant (substituted by esbuild)\ndeclare const CLI_VERSION: string;\n\n/**\n * Bundled spec version\n * This should match the version in ../../../spec/VERSION\n * Updated during build process\n */\nconst BUNDLED_SPEC_VERSION = \"0.10.0\";\n\n// Dev-mode fallback (e.g. `tsx src/cli.ts`) that reads package.json directly, so it can't drift.\nfunction resolveDevVersion(): string {\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch (error) {\n console.error(\n `Failed to resolve dev-mode CLI version from package.json, falling back to \"0.0.0\": ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return \"0.0.0\";\n }\n}\n\n/**\n * Get the CLI version\n *\n * @returns CLI version string\n */\nexport function getCliVersion(): string {\n return typeof CLI_VERSION !== \"undefined\" ? CLI_VERSION : resolveDevVersion();\n}\n\n/**\n * Get the CLI's bundled spec version\n *\n * This is the version of the DR specification that is bundled with this CLI.\n * It represents what version of the spec this CLI supports.\n *\n * @returns Bundled spec version string\n */\nexport function getCliBundledSpecVersion(): string {\n return BUNDLED_SPEC_VERSION;\n}\n\n/**\n * Read spec version from .dr/manifest.json (installed spec reference)\n *\n * @param drPath - Path to .dr/ directory\n * @returns Spec version from .dr/manifest.json, or null if not found\n */\nexport async function getInstalledSpecVersion(drPath: string): Promise<string | null> {\n const manifestPath = join(drPath, \"manifest.json\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n const manifest = await readJSON<{ specVersion?: string }>(manifestPath);\n return manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Read spec version from documentation_robotics/model/manifest.yaml\n *\n * @param modelPath - Path to model directory\n * @returns Spec version from model manifest, or null if not found\n */\nexport async function getModelSpecVersion(modelPath: string): Promise<string | null> {\n const manifestPath = join(modelPath, \"manifest.yaml\");\n\n if (!(await fileExists(manifestPath))) {\n return null;\n }\n\n try {\n // Use YAML parser\n const yaml = await import(\"yaml\");\n const fs = await import(\"fs/promises\");\n const content = await fs.readFile(manifestPath, \"utf-8\");\n const manifest = yaml.parse(content) as { spec_version?: string; specVersion?: string };\n // Check snake_case first (Python CLI format), then camelCase (legacy)\n return manifest.spec_version || manifest.specVersion || null;\n } catch {\n return null;\n }\n}\n"],
5
+ "mappings": "AAMA,SAAS,UAAU,kBAAkB;AACrC,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAU9B,MAAM,uBAAuB;AAG7B,SAAS,oBAAiB;AACxB,MAAI;AAGF,WAFgB,cAAc,YAAY,GAAG,EACzB,oBAAoB,EAC7B,WAAW;EACxB,SAAS,OAAO;AACd,mBAAQ,MACN,sFACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CACvD,EAAE,GAEG;EACT;AACF;AAOM,SAAU,gBAAa;AAC3B,SAA4C;AAC9C;AAUM,SAAU,2BAAwB;AACtC,SAAO;AACT;AAQA,eAAsB,wBAAwB,QAAc;AAC1D,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,CAAE,MAAM,WAAW,YAAY;AACjC,WAAO;AAGT,MAAI;AAEF,YADiB,MAAM,SAAmC,YAAY,GACtD,eAAe;EACjC,QAAQ;AACN,WAAO;EACT;AACF;AAQA,eAAsB,oBAAoB,WAAiB;AACzD,QAAM,eAAe,KAAK,WAAW,eAAe;AAEpD,MAAI,CAAE,MAAM,WAAW,YAAY;AACjC,WAAO;AAGT,MAAI;AAEF,UAAM,OAAO,MAAM,OAAO,MAAM,GAE1B,UAAU,OADL,MAAM,OAAO,aAAa,GACZ,SAAS,cAAc,OAAO,GACjD,WAAW,KAAK,MAAM,OAAO;AAEnC,WAAO,SAAS,gBAAgB,SAAS,eAAe;EAC1D,QAAQ;AACN,WAAO;EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,262 @@
1
+ import { ValidationResult } from "./types.js";
2
+ import { Validator } from "./validator.js";
3
+ import { parseReferencePath, ReferencePathParseError } from "../utils/reference-path-parser.js";
4
+ import { getErrorMessage } from "../utils/errors.js";
5
+ import { promises as fs } from "fs";
6
+ import path from "path";
7
+ import { parse as parseYAML } from "yaml";
8
+ import { FarmManifest } from "../core/farm-manifest.js";
9
+ class ComposedReferenceValidator {
10
+ modelPathOverrides;
11
+ resolvedModels = /* @__PURE__ */ new Map();
12
+ referencedModels = /* @__PURE__ */ new Set();
13
+ resolutionDiagnostics = new ValidationResult();
14
+ constructor(modelPathOverrides = {}) {
15
+ this.modelPathOverrides = modelPathOverrides;
16
+ }
17
+ /**
18
+ * Create a ComposedReferenceValidator from a farm root
19
+ * Automatically builds model-path overrides from the farm's project entries
20
+ * @param farmRoot - Path to the farm root directory
21
+ * @returns ComposedReferenceValidator instance configured for the farm
22
+ */
23
+ static async fromFarm(farmRoot) {
24
+ const farmYamlPath = path.join(farmRoot, "farm.yaml"), manifest = await FarmManifest.load(farmYamlPath), modelPathOverrides = {};
25
+ for (const project of manifest.getAllProjects()) {
26
+ const modelFullPath = path.join(farmRoot, project.model);
27
+ modelPathOverrides[project.name] = modelFullPath;
28
+ }
29
+ return new ComposedReferenceValidator(modelPathOverrides);
30
+ }
31
+ async validateModel(model) {
32
+ const result = await new Validator().validateModel(model), declaredModels = /* @__PURE__ */ new Map();
33
+ for (const modelName of Object.keys(model.manifest.models || {}))
34
+ declaredModels.set(modelName.toLowerCase(), modelName);
35
+ this.referencedModels.clear(), this.resolvedModels.clear(), this.resolutionDiagnostics = new ValidationResult(), this.collectReferencedModels(model);
36
+ for (const declaredName of declaredModels.values())
37
+ await this.resolveExternalModel(declaredName);
38
+ result.merge(this.resolutionDiagnostics);
39
+ for (const [lowerModelName, declaredName] of declaredModels) {
40
+ const isReferenced = this.referencedModels.has(lowerModelName);
41
+ if (!this.resolvedModels.has(declaredName))
42
+ isReferenced ? result.addError({
43
+ layer: "manifest",
44
+ message: `External model '${declaredName}' is referenced in qualified paths but could not be resolved from the configured paths`,
45
+ category: "reference",
46
+ fixSuggestion: `Ensure the model exists on disk, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`
47
+ }) : result.addWarning({
48
+ layer: "manifest",
49
+ message: `External model '${declaredName}' is declared but not referenced in any qualified paths and could not be found on disk`,
50
+ category: "reference",
51
+ fixSuggestion: `Remove the declaration if not needed, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`
52
+ });
53
+ else if (isReferenced) {
54
+ const validationResult = this.validateQualifiedReferencesAgainstModel(model, declaredName);
55
+ result.merge(validationResult, `[Composed/${declaredName}]`);
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+ collectReferencedModels(model) {
61
+ for (const [, layer] of model.layers)
62
+ for (const element of layer.listElements())
63
+ for (const ref of element.references || [])
64
+ if (ref.target.startsWith("@"))
65
+ try {
66
+ const parsed = parseReferencePath(ref.target);
67
+ parsed.modelName && this.referencedModels.add(parsed.modelName.toLowerCase());
68
+ } catch (error) {
69
+ if (error instanceof ReferencePathParseError)
70
+ continue;
71
+ throw error;
72
+ }
73
+ }
74
+ async resolveExternalModel(modelName) {
75
+ let modelPath = this.modelPathOverrides[modelName];
76
+ if (!modelPath) {
77
+ const lowerModelName = modelName.toLowerCase();
78
+ for (const [key, overridePath] of Object.entries(this.modelPathOverrides))
79
+ if (key.toLowerCase() === lowerModelName) {
80
+ modelPath = overridePath;
81
+ break;
82
+ }
83
+ }
84
+ if (modelPath)
85
+ try {
86
+ let rootStats;
87
+ try {
88
+ rootStats = await fs.stat(modelPath);
89
+ } catch (err) {
90
+ if (err?.code !== "ENOENT")
91
+ throw err;
92
+ }
93
+ if (rootStats && !rootStats.isDirectory()) {
94
+ this.resolutionDiagnostics.addWarning({
95
+ layer: "manifest",
96
+ message: `External model '${modelName}' path is not a directory: ${modelPath}`,
97
+ category: "reference",
98
+ fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root containing documentation-robotics/model/`
99
+ });
100
+ return;
101
+ }
102
+ let modelDir = path.join(modelPath, "documentation-robotics", "model"), stats;
103
+ try {
104
+ stats = await fs.stat(modelDir);
105
+ } catch (err) {
106
+ if (err?.code !== "ENOENT")
107
+ throw err;
108
+ }
109
+ if (!stats) {
110
+ modelDir = path.join(modelPath, "model");
111
+ try {
112
+ stats = await fs.stat(modelDir);
113
+ } catch (err) {
114
+ if (err?.code !== "ENOENT")
115
+ throw err;
116
+ }
117
+ }
118
+ if (!stats) {
119
+ modelDir = modelPath;
120
+ try {
121
+ stats = await fs.stat(modelDir);
122
+ } catch (err) {
123
+ if (err?.code !== "ENOENT")
124
+ throw err;
125
+ }
126
+ }
127
+ if (!stats) {
128
+ this.addResolutionDiagnostic(modelName, new Error("Model directory not found"), modelPath, !0);
129
+ return;
130
+ }
131
+ if (!stats.isDirectory()) {
132
+ this.resolutionDiagnostics.addWarning({
133
+ layer: "manifest",
134
+ message: `External model '${modelName}' path is not a directory: ${modelDir}`,
135
+ category: "reference",
136
+ fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root or model directory`
137
+ });
138
+ return;
139
+ }
140
+ const elementIds = /* @__PURE__ */ new Set();
141
+ let entries = [];
142
+ try {
143
+ entries = await fs.readdir(modelDir, { withFileTypes: !0 });
144
+ } catch (error) {
145
+ this.addResolutionDiagnostic(modelName, error, modelPath);
146
+ return;
147
+ }
148
+ for (const entry of entries)
149
+ if (entry.isDirectory() && /^\d+_/.test(entry.name)) {
150
+ const layerPath = path.join(modelDir, entry.name);
151
+ await this.collectElementsFromLayer(layerPath, elementIds);
152
+ }
153
+ this.resolvedModels.set(modelName, {
154
+ modelName,
155
+ elementIds
156
+ });
157
+ } catch (error) {
158
+ this.addResolutionDiagnostic(modelName, error, modelPath);
159
+ }
160
+ }
161
+ async collectElementsFromLayer(layerPath, elementIds) {
162
+ try {
163
+ const entries = await fs.readdir(layerPath, { withFileTypes: !0 });
164
+ for (const entry of entries)
165
+ if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")))
166
+ try {
167
+ const content = await fs.readFile(path.join(layerPath, entry.name), "utf-8");
168
+ this.extractElementPathsFromYAML(content, elementIds, layerPath, entry.name);
169
+ } catch (error) {
170
+ const filePath = path.join(layerPath, entry.name);
171
+ this.resolutionDiagnostics.addWarning({
172
+ layer: "manifest",
173
+ message: `Failed to read YAML file at ${filePath}: ${getErrorMessage(error)}`,
174
+ category: "reference"
175
+ });
176
+ continue;
177
+ }
178
+ } catch (error) {
179
+ this.resolutionDiagnostics.addWarning({
180
+ layer: "manifest",
181
+ message: `Failed to read layer directory at ${layerPath}: ${getErrorMessage(error)}`,
182
+ category: "reference"
183
+ });
184
+ }
185
+ }
186
+ extractElementPathsFromYAML(content, elementIds, layerPath, fileName) {
187
+ try {
188
+ const parsed = parseYAML(content);
189
+ if (!parsed) {
190
+ this.resolutionDiagnostics.addWarning({
191
+ layer: "manifest",
192
+ message: `YAML file at ${path.join(layerPath, fileName)} parsed to empty content`,
193
+ category: "reference"
194
+ });
195
+ return;
196
+ }
197
+ const items = [];
198
+ if (parsed && typeof parsed == "object" && "elements" in parsed) {
199
+ const elements = parsed.elements;
200
+ Array.isArray(elements) && items.push(...elements);
201
+ } else if (Array.isArray(parsed))
202
+ items.push(...parsed);
203
+ else if (parsed && typeof parsed == "object" && !Array.isArray(parsed)) {
204
+ const values = Object.values(parsed);
205
+ values.length > 0 && values[0] && typeof values[0] == "object" && ("path" in values[0] || "id" in values[0]) ? items.push(...values) : items.push(parsed);
206
+ }
207
+ for (const item of items)
208
+ if (item && typeof item == "object") {
209
+ const elementPath = item.path || item.id;
210
+ elementPath && typeof elementPath == "string" && elementIds.add(elementPath.trim());
211
+ }
212
+ } catch (error) {
213
+ this.resolutionDiagnostics.addWarning({
214
+ layer: "manifest",
215
+ message: `Failed to parse YAML file at ${path.join(layerPath, fileName)}: ${getErrorMessage(error)}`,
216
+ category: "reference"
217
+ });
218
+ }
219
+ }
220
+ validateQualifiedReferencesAgainstModel(model, modelName) {
221
+ const result = new ValidationResult(), resolvedModel = this.resolvedModels.get(modelName);
222
+ if (!resolvedModel)
223
+ return result;
224
+ for (const [sourceLayerName, layer] of model.layers)
225
+ for (const element of layer.listElements()) {
226
+ const elementId = element.path || element.id;
227
+ for (const ref of element.references || [])
228
+ if (ref.target.startsWith("@"))
229
+ try {
230
+ const parsed = parseReferencePath(ref.target);
231
+ if (parsed.modelName?.toLowerCase() !== modelName.toLowerCase())
232
+ continue;
233
+ resolvedModel.elementIds.has(parsed.segment) || result.addError({
234
+ layer: sourceLayerName,
235
+ elementId,
236
+ message: `Broken qualified reference: target '${parsed.segment}' does not exist in external model '${modelName}'`,
237
+ category: "reference",
238
+ fixSuggestion: `Create element '${parsed.segment}' in model '${modelName}' or remove this reference`
239
+ });
240
+ } catch (error) {
241
+ if (error instanceof ReferencePathParseError)
242
+ continue;
243
+ throw error;
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+ addResolutionDiagnostic(modelName, error, modelPath, isPathError = !1) {
249
+ const errorMsg = getErrorMessage(error), code = error?.code;
250
+ let message = "", fixSuggestion = "";
251
+ code === "EACCES" ? (message = `Permission denied accessing external model '${modelName}' at ${modelPath}. Check file permissions.`, fixSuggestion = `Ensure the model path is readable, or use '--model-path ${modelName}=/path/to/alternative-location' to specify another location`) : code === "EIO" ? (message = `I/O error reading external model '${modelName}' at ${modelPath}. There may be a filesystem or device issue.`, fixSuggestion = `Check the filesystem for errors, or provide an alternative model path via '--model-path ${modelName}=/path/to/alternative-location'`) : code === "ENOENT" || isPathError ? (message = `Could not find external model '${modelName}' \u2014 expected at ${modelPath}/documentation-robotics/model or ${modelPath}/model`, fixSuggestion = `Ensure the model exists at the specified path, or use '--model-path ${modelName}=/path/to/project-root' to specify its location`) : (message = `Failed to resolve external model '${modelName}' at ${modelPath}: ${errorMsg}`, fixSuggestion = `Check the path and filesystem permissions, or use '--model-path ${modelName}=/path/to/project-root' to specify a different location`), this.resolutionDiagnostics.addWarning({
252
+ layer: "manifest",
253
+ message,
254
+ category: "reference",
255
+ fixSuggestion
256
+ });
257
+ }
258
+ }
259
+ export {
260
+ ComposedReferenceValidator
261
+ };
262
+ //# sourceMappingURL=composed-reference-validator.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/validators/composed-reference-validator.ts"],
4
+ "sourcesContent": ["import { ValidationResult } from \"./types.js\";\nimport type { Model } from \"../core/model.js\";\nimport { Validator } from \"./validator.js\";\nimport { parseReferencePath, ReferencePathParseError } from \"../utils/reference-path-parser.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { promises as fs, Dirent, Stats } from \"fs\";\nimport path from \"path\";\nimport { parse as parseYAML } from \"yaml\";\nimport { FarmManifest } from \"../core/farm-manifest.js\";\n\ninterface ResolvedExternalModel {\n modelName: string;\n elementIds: Set<string>;\n}\n\nexport class ComposedReferenceValidator {\n private modelPathOverrides: Record<string, string>;\n private resolvedModels: Map<string, ResolvedExternalModel> = new Map();\n private referencedModels: Set<string> = new Set();\n private resolutionDiagnostics: ValidationResult = new ValidationResult();\n\n constructor(modelPathOverrides: Record<string, string> = {}) {\n this.modelPathOverrides = modelPathOverrides;\n }\n\n /**\n * Create a ComposedReferenceValidator from a farm root\n * Automatically builds model-path overrides from the farm's project entries\n * @param farmRoot - Path to the farm root directory\n * @returns ComposedReferenceValidator instance configured for the farm\n */\n static async fromFarm(farmRoot: string): Promise<ComposedReferenceValidator> {\n const farmYamlPath = path.join(farmRoot, \"farm.yaml\");\n const manifest = await FarmManifest.load(farmYamlPath);\n\n const modelPathOverrides: Record<string, string> = {};\n\n // Build model-path overrides for each project in the farm\n for (const project of manifest.getAllProjects()) {\n // For detached model layouts, point directly to the model folder.\n // The validator will look for manifest.yaml within this folder.\n // If the model is co-located (inside codebase), it will also find it\n // via the documentation-robotics/model/ path search.\n const modelFullPath = path.join(farmRoot, project.model);\n modelPathOverrides[project.name] = modelFullPath;\n }\n\n return new ComposedReferenceValidator(modelPathOverrides);\n }\n\n async validateModel(model: Model): Promise<ValidationResult> {\n const fullValidator = new Validator();\n const result = await fullValidator.validateModel(model);\n\n const declaredModels = new Map<string, string>();\n // Map from lowercase model name to declared model name (preserving original casing)\n for (const modelName of Object.keys(model.manifest.models || {})) {\n declaredModels.set(modelName.toLowerCase(), modelName);\n }\n\n // Clear all state to ensure fresh validation on reuse\n this.referencedModels.clear();\n this.resolvedModels.clear();\n this.resolutionDiagnostics = new ValidationResult();\n this.collectReferencedModels(model);\n\n for (const declaredName of declaredModels.values()) {\n await this.resolveExternalModel(declaredName);\n }\n\n // Merge any diagnostics from resolution phase\n result.merge(this.resolutionDiagnostics);\n\n for (const [lowerModelName, declaredName] of declaredModels) {\n // Check if model is referenced using case-insensitive comparison\n const isReferenced = this.referencedModels.has(lowerModelName);\n // Check if model is resolved using the declared name\n const isResolved = this.resolvedModels.has(declaredName);\n\n if (!isResolved) {\n if (isReferenced) {\n // Declared, referenced, but unresolvable \u2192 error with guidance\n result.addError({\n layer: \"manifest\",\n message: `External model '${declaredName}' is referenced in qualified paths but could not be resolved from the configured paths`,\n category: \"reference\",\n fixSuggestion: `Ensure the model exists on disk, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`,\n });\n } else {\n // Declared but unreferenced and unresolvable \u2192 warning only\n result.addWarning({\n layer: \"manifest\",\n message: `External model '${declaredName}' is declared but not referenced in any qualified paths and could not be found on disk`,\n category: \"reference\",\n fixSuggestion: `Remove the declaration if not needed, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`,\n });\n }\n } else if (isReferenced) {\n // Declared, referenced, and resolvable \u2192 validate qualified references\n const validationResult = this.validateQualifiedReferencesAgainstModel(model, declaredName);\n result.merge(validationResult, `[Composed/${declaredName}]`);\n }\n }\n\n return result;\n }\n\n private collectReferencedModels(model: Model): void {\n for (const [, layer] of model.layers) {\n for (const element of layer.listElements()) {\n for (const ref of element.references || []) {\n if (ref.target.startsWith(\"@\")) {\n try {\n const parsed = parseReferencePath(ref.target);\n if (parsed.modelName) {\n // Store lowercase for case-insensitive comparison with declared models\n this.referencedModels.add(parsed.modelName.toLowerCase());\n }\n } catch (error) {\n if (error instanceof ReferencePathParseError) {\n // Parse errors are already handled by base validator\n continue;\n } else {\n throw error;\n }\n }\n }\n }\n }\n }\n }\n\n private async resolveExternalModel(modelName: string): Promise<void> {\n // Try exact match first, then case-insensitive match\n let modelPath = this.modelPathOverrides[modelName];\n if (!modelPath) {\n // Case-insensitive lookup in modelPathOverrides\n const lowerModelName = modelName.toLowerCase();\n for (const [key, overridePath] of Object.entries(this.modelPathOverrides)) {\n if (key.toLowerCase() === lowerModelName) {\n modelPath = overridePath;\n break;\n }\n }\n }\n\n if (!modelPath) {\n return;\n }\n\n try {\n // Verify modelPath itself is a directory\n let rootStats: Stats | undefined;\n try {\n rootStats = await fs.stat(modelPath);\n } catch (err) {\n const errorCode = (err as NodeJS.ErrnoException)?.code;\n if (errorCode !== \"ENOENT\") {\n throw err;\n }\n }\n\n if (rootStats && !rootStats.isDirectory()) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `External model '${modelName}' path is not a directory: ${modelPath}`,\n category: \"reference\",\n fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root containing documentation-robotics/model/`,\n });\n return;\n }\n\n // Try standard layout first: {modelPath}/documentation-robotics/model/\n let modelDir = path.join(modelPath, \"documentation-robotics\", \"model\");\n let stats: Stats | undefined;\n try {\n stats = await fs.stat(modelDir);\n } catch (err) {\n const errorCode = (err as NodeJS.ErrnoException)?.code;\n if (errorCode !== \"ENOENT\") {\n throw err;\n }\n }\n\n // Fall back to legacy layout: {modelPath}/model/\n if (!stats) {\n modelDir = path.join(modelPath, \"model\");\n try {\n stats = await fs.stat(modelDir);\n } catch (err) {\n const errorCode = (err as NodeJS.ErrnoException)?.code;\n if (errorCode !== \"ENOENT\") {\n throw err;\n }\n }\n }\n\n // Fall back to detached layout: modelPath itself is the model directory\n // (contains layer directories like 01_motivation, 07_api, etc.)\n if (!stats) {\n modelDir = modelPath;\n try {\n stats = await fs.stat(modelDir);\n } catch (err) {\n const errorCode = (err as NodeJS.ErrnoException)?.code;\n if (errorCode !== \"ENOENT\") {\n throw err;\n }\n }\n }\n\n if (!stats) {\n this.addResolutionDiagnostic(\n modelName,\n new Error(\"Model directory not found\"),\n modelPath,\n true\n );\n return;\n }\n\n if (!stats.isDirectory()) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `External model '${modelName}' path is not a directory: ${modelDir}`,\n category: \"reference\",\n fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root or model directory`,\n });\n return;\n }\n\n const elementIds = new Set<string>();\n\n // Try to load all layer directories\n let entries: Dirent[] = [];\n try {\n entries = await fs.readdir(modelDir, { withFileTypes: true });\n } catch (error) {\n this.addResolutionDiagnostic(modelName, error, modelPath);\n return;\n }\n\n // Load elements from each layer directory\n for (const entry of entries) {\n if (entry.isDirectory() && /^\\d+_/.test(entry.name)) {\n // This looks like a layer directory (e.g., \"01_motivation\", \"08_data-model\")\n const layerPath = path.join(modelDir, entry.name);\n await this.collectElementsFromLayer(layerPath, elementIds);\n }\n }\n\n // Successfully resolved the model (track it even if empty)\n this.resolvedModels.set(modelName, {\n modelName,\n elementIds,\n });\n } catch (error) {\n // Failed to resolve model\n this.addResolutionDiagnostic(modelName, error, modelPath);\n }\n }\n\n private async collectElementsFromLayer(layerPath: string, elementIds: Set<string>): Promise<void> {\n try {\n const entries = await fs.readdir(layerPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith(\".yaml\") || entry.name.endsWith(\".yml\"))) {\n // Read the YAML file and extract element paths/IDs\n try {\n const content = await fs.readFile(path.join(layerPath, entry.name), \"utf-8\");\n this.extractElementPathsFromYAML(content, elementIds, layerPath, entry.name);\n } catch (error) {\n const filePath = path.join(layerPath, entry.name);\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `Failed to read YAML file at ${filePath}: ${getErrorMessage(error)}`,\n category: \"reference\",\n });\n continue;\n }\n }\n }\n } catch (error) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `Failed to read layer directory at ${layerPath}: ${getErrorMessage(error)}`,\n category: \"reference\",\n });\n }\n }\n\n private extractElementPathsFromYAML(\n content: string,\n elementIds: Set<string>,\n layerPath: string,\n fileName: string\n ): void {\n try {\n const parsed = parseYAML(content);\n\n if (!parsed) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `YAML file at ${path.join(layerPath, fileName)} parsed to empty content`,\n category: \"reference\",\n });\n return;\n }\n\n // Collect items to process from various YAML formats\n const items: unknown[] = [];\n\n // Format 1: elements: [{ path: \"...\" }, ...]\n if (parsed && typeof parsed === \"object\" && \"elements\" in parsed) {\n const elements = parsed.elements;\n if (Array.isArray(elements)) {\n items.push(...elements);\n }\n }\n // Format 2: Direct array [{ path: \"...\" }, ...]\n else if (Array.isArray(parsed)) {\n items.push(...parsed);\n }\n // Format 3: Object-of-objects { \"key\": { path: \"...\" }, ... }\n else if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n // Check if this looks like an object-of-objects (values have path/id fields)\n const values = Object.values(parsed);\n if (values.length > 0 && values[0] && typeof values[0] === \"object\" && (\"path\" in values[0] || \"id\" in values[0])) {\n items.push(...values);\n } else {\n // Object without path/id fields, push as-is for type safety\n items.push(parsed);\n }\n }\n\n // Extract paths from collected items\n for (const item of items) {\n if (item && typeof item === \"object\") {\n const elementPath = (item as Record<string, unknown>).path || (item as Record<string, unknown>).id;\n if (elementPath && typeof elementPath === \"string\") {\n elementIds.add(elementPath.trim());\n }\n }\n }\n } catch (error) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `Failed to parse YAML file at ${path.join(layerPath, fileName)}: ${getErrorMessage(error)}`,\n category: \"reference\",\n });\n }\n }\n\n private validateQualifiedReferencesAgainstModel(\n model: Model,\n modelName: string\n ): ValidationResult {\n const result = new ValidationResult();\n const resolvedModel = this.resolvedModels.get(modelName);\n\n if (!resolvedModel) {\n return result;\n }\n\n for (const [sourceLayerName, layer] of model.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n\n for (const ref of element.references || []) {\n if (!ref.target.startsWith(\"@\")) {\n continue;\n }\n\n try {\n const parsed = parseReferencePath(ref.target);\n // Compare model names case-insensitively\n if (parsed.modelName?.toLowerCase() !== modelName.toLowerCase()) {\n continue;\n }\n\n // Check if target element exists in the resolved external model\n if (!resolvedModel.elementIds.has(parsed.segment)) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Broken qualified reference: target '${parsed.segment}' does not exist in external model '${modelName}'`,\n category: \"reference\",\n fixSuggestion: `Create element '${parsed.segment}' in model '${modelName}' or remove this reference`,\n });\n }\n } catch (error) {\n if (error instanceof ReferencePathParseError) {\n // Parse errors are already handled by base validator\n continue;\n } else {\n throw error;\n }\n }\n }\n }\n }\n\n return result;\n }\n\n private addResolutionDiagnostic(\n modelName: string,\n error: unknown,\n modelPath: string,\n isPathError: boolean = false\n ): void {\n const errorMsg = getErrorMessage(error);\n const code = (error as NodeJS.ErrnoException)?.code;\n\n let message = \"\";\n let fixSuggestion = \"\";\n\n if (code === \"EACCES\") {\n message =\n `Permission denied accessing external model '${modelName}' at ${modelPath}. ` +\n `Check file permissions.`;\n fixSuggestion = `Ensure the model path is readable, or use '--model-path ${modelName}=/path/to/alternative-location' to specify another location`;\n } else if (code === \"EIO\") {\n message =\n `I/O error reading external model '${modelName}' at ${modelPath}. ` +\n `There may be a filesystem or device issue.`;\n fixSuggestion = `Check the filesystem for errors, or provide an alternative model path via '--model-path ${modelName}=/path/to/alternative-location'`;\n } else if (code === \"ENOENT\" || isPathError) {\n message =\n `Could not find external model '${modelName}' \u2014 expected at ${modelPath}/documentation-robotics/model or ${modelPath}/model`;\n fixSuggestion = `Ensure the model exists at the specified path, or use '--model-path ${modelName}=/path/to/project-root' to specify its location`;\n } else {\n message =\n `Failed to resolve external model '${modelName}' at ${modelPath}: ${errorMsg}`;\n fixSuggestion = `Check the path and filesystem permissions, or use '--model-path ${modelName}=/path/to/project-root' to specify a different location`;\n }\n\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message,\n category: \"reference\",\n fixSuggestion,\n });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }