@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
  import { Model } from "./core/model.js";
2
2
  import { VisualizationServer } from "./server/server.js";
3
3
  import { getErrorMessage } from "./utils/errors.js";
4
- const port = parseInt(process.env.DR_VISUALIZE_PORT || "8080", 10), projectPath = process.env.DR_PROJECT_PATH || process.cwd(), authEnabled = process.env.DR_AUTH_ENABLED !== "false", authToken = process.env.DR_AUTH_TOKEN && process.env.DR_AUTH_TOKEN.length > 0 ? process.env.DR_AUTH_TOKEN : void 0, withDanger = process.env.DR_WITH_DANGER === "true", viewerPath = process.env.DR_VIEWER_PATH, isTelemetryEnabled = !1;
4
+ const port = parseInt(process.env.DR_VISUALIZE_PORT || "8080", 10), projectPath = process.env.DR_PROJECT_PATH || process.cwd(), authEnabled = process.env.DR_AUTH_ENABLED !== "false", authToken = process.env.DR_AUTH_TOKEN && process.env.DR_AUTH_TOKEN.length > 0 ? process.env.DR_AUTH_TOKEN : void 0, withDanger = process.env.DR_WITH_DANGER === "true", withChangesets = process.env.DR_WITH_CHANGESETS === "true", viewerPath = process.env.DR_VIEWER_PATH, isTelemetryEnabled = !1;
5
5
  let serverInstance = null, isShuttingDown = !1;
6
6
  async function main() {
7
7
  try {
@@ -23,6 +23,7 @@ async function main() {
23
23
  authEnabled,
24
24
  authToken,
25
25
  withDanger,
26
+ withChangesets,
26
27
  viewerPath
27
28
  });
28
29
  serverInstance = server, await server.start(port), authEnabled && console.log(`TOKEN:${server.getAuthToken()}`), setupShutdownHandlers();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/server-entry.ts"],
4
- "sourcesContent": ["/**\n * Visualization Server Entry Point\n * This is spawned as a subprocess by `dr visualize` (see cli/src/commands/visualize.ts),\n * using whichever runtime (Node.js or Bun) is already running the parent CLI process.\n * It receives configuration via environment variables and starts the server.\n */\n\nimport { Model } from \"./core/model.js\";\nimport { VisualizationServer } from \"./server/server.js\";\nimport { getErrorMessage } from \"./utils/errors.js\";\n\n// Get configuration from environment variables\nconst port = parseInt(process.env.DR_VISUALIZE_PORT || \"8080\", 10);\nconst projectPath = process.env.DR_PROJECT_PATH || process.cwd();\nconst authEnabled = process.env.DR_AUTH_ENABLED !== \"false\";\n// Only pass authToken if it's a non-empty string, otherwise let server generate one\nconst authToken =\n process.env.DR_AUTH_TOKEN && process.env.DR_AUTH_TOKEN.length > 0\n ? process.env.DR_AUTH_TOKEN\n : undefined;\nconst withDanger = process.env.DR_WITH_DANGER === \"true\";\nconst viewerPath = process.env.DR_VIEWER_PATH;\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\nlet serverInstance: VisualizationServer | null = null;\nlet isShuttingDown = false;\n\nasync function main() {\n try {\n // CRITICAL: Initialize telemetry SDK in subprocess\n // Without this, no spans will be created or exported even though TELEMETRY_ENABLED=true\n if (isTelemetryEnabled) {\n try {\n const { initTelemetry } = await import(\"./telemetry/index.js\");\n await initTelemetry();\n\n // Load and display actual OTLP configuration\n const { loadOTLPConfig } = await import(\"./telemetry/config.js\");\n const otlpConfig = await loadOTLPConfig();\n\n if (process.env.DEBUG || process.env.VERBOSE) {\n console.log(`[Telemetry] SDK initialized in server subprocess`);\n console.log(`[Telemetry] OTLP traces endpoint: ${otlpConfig.endpoint}`);\n console.log(`[Telemetry] OTLP logs endpoint: ${otlpConfig.logsEndpoint}`);\n console.log(`[Telemetry] Service name: ${otlpConfig.serviceName}`);\n\n // Runtime detection and compatibility info\n const isBun = typeof (globalThis as any).Bun !== \"undefined\";\n if (isBun) {\n console.log(`[Telemetry] Runtime: Bun (using fetch-based OTLP exporter)`);\n } else {\n console.log(`[Telemetry] Runtime: Node.js (using http-based OTLP exporter)`);\n }\n }\n } catch (error) {\n // Log telemetry initialization errors but don't fail the server\n console.error(\"[Telemetry] Failed to initialize telemetry:\", error);\n console.error(\"[Telemetry] Server will continue without telemetry\");\n }\n } else if (process.env.DEBUG || process.env.VERBOSE) {\n console.log(`[Telemetry] TELEMETRY_ENABLED is false - telemetry disabled`);\n }\n\n // Load model with full content\n const model = await Model.load(projectPath, { lazyLoad: false });\n\n // Create and start server\n const server = new VisualizationServer(model, {\n authEnabled,\n authToken,\n withDanger,\n viewerPath,\n });\n serverInstance = server; // Store for signal handlers\n await server.start(port);\n\n // Output token for parent process to read\n if (authEnabled) {\n console.log(`TOKEN:${server.getAuthToken()}`);\n }\n\n // Setup graceful shutdown handlers\n setupShutdownHandlers();\n } catch (error) {\n const message = getErrorMessage(error);\n console.error(`Error: ${message}`);\n if (error instanceof Error) {\n console.error(`Stack: ${error.stack}`);\n }\n await gracefulShutdown(1);\n }\n}\n\n/**\n * Gracefully shutdown the server and flush telemetry\n */\nasync function gracefulShutdown(exitCode: number = 0): Promise<void> {\n if (isShuttingDown) {\n return; // Prevent multiple shutdown attempts\n }\n isShuttingDown = true;\n\n try {\n // Stop the visualization server\n if (serverInstance) {\n serverInstance.stop();\n serverInstance = null;\n }\n\n // Flush telemetry spans before exit\n if (isTelemetryEnabled) {\n console.log(\"[Telemetry] Shutting down telemetry and flushing spans...\");\n const { shutdownTelemetry } = await import(\"./telemetry/index.js\");\n await shutdownTelemetry();\n console.log(\"[Telemetry] Telemetry shutdown complete\");\n } else {\n if (process.env.DEBUG) {\n console.log(\"[Telemetry] Telemetry is disabled (TELEMETRY_ENABLED=false at build time)\");\n }\n }\n } catch (error) {\n // Don't block shutdown on errors\n if (process.env.DEBUG) {\n console.error(\"Error during shutdown:\", error);\n }\n } finally {\n process.exit(exitCode);\n }\n}\n\n/**\n * Setup handlers for graceful shutdown on signals\n */\nfunction setupShutdownHandlers(): void {\n // Handle Ctrl-C (SIGINT)\n process.on(\"SIGINT\", async () => {\n console.log(\"\\n[SIGINT] Shutting down visualization server...\");\n await gracefulShutdown(0);\n });\n\n // Handle termination signal (SIGTERM)\n process.on(\"SIGTERM\", async () => {\n console.log(\"\\n[SIGTERM] Shutting down visualization server...\");\n await gracefulShutdown(0);\n });\n\n // Handle uncaught exceptions\n process.on(\"uncaughtException\", async (error) => {\n console.error(\"Uncaught exception:\", error);\n await gracefulShutdown(1);\n });\n\n // Handle unhandled promise rejections\n process.on(\"unhandledRejection\", async (reason) => {\n console.error(\"Unhandled rejection:\", reason);\n await gracefulShutdown(1);\n });\n}\n\nmain();\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Visualization Server Entry Point\n * This is spawned as a subprocess by `dr visualize` (see cli/src/commands/visualize.ts),\n * using whichever runtime (Node.js or Bun) is already running the parent CLI process.\n * It receives configuration via environment variables and starts the server.\n */\n\nimport { Model } from \"./core/model.js\";\nimport { VisualizationServer } from \"./server/server.js\";\nimport { getErrorMessage } from \"./utils/errors.js\";\n\n// Get configuration from environment variables\nconst port = parseInt(process.env.DR_VISUALIZE_PORT || \"8080\", 10);\nconst projectPath = process.env.DR_PROJECT_PATH || process.cwd();\nconst authEnabled = process.env.DR_AUTH_ENABLED !== \"false\";\n// Only pass authToken if it's a non-empty string, otherwise let server generate one\nconst authToken =\n process.env.DR_AUTH_TOKEN && process.env.DR_AUTH_TOKEN.length > 0\n ? process.env.DR_AUTH_TOKEN\n : undefined;\nconst withDanger = process.env.DR_WITH_DANGER === \"true\";\nconst withChangesets = process.env.DR_WITH_CHANGESETS === \"true\";\nconst viewerPath = process.env.DR_VIEWER_PATH;\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\nlet serverInstance: VisualizationServer | null = null;\nlet isShuttingDown = false;\n\nasync function main() {\n try {\n // CRITICAL: Initialize telemetry SDK in subprocess\n // Without this, no spans will be created or exported even though TELEMETRY_ENABLED=true\n if (isTelemetryEnabled) {\n try {\n const { initTelemetry } = await import(\"./telemetry/index.js\");\n await initTelemetry();\n\n // Load and display actual OTLP configuration\n const { loadOTLPConfig } = await import(\"./telemetry/config.js\");\n const otlpConfig = await loadOTLPConfig();\n\n if (process.env.DEBUG || process.env.VERBOSE) {\n console.log(`[Telemetry] SDK initialized in server subprocess`);\n console.log(`[Telemetry] OTLP traces endpoint: ${otlpConfig.endpoint}`);\n console.log(`[Telemetry] OTLP logs endpoint: ${otlpConfig.logsEndpoint}`);\n console.log(`[Telemetry] Service name: ${otlpConfig.serviceName}`);\n\n // Runtime detection and compatibility info\n const isBun = typeof (globalThis as any).Bun !== \"undefined\";\n if (isBun) {\n console.log(`[Telemetry] Runtime: Bun (using fetch-based OTLP exporter)`);\n } else {\n console.log(`[Telemetry] Runtime: Node.js (using http-based OTLP exporter)`);\n }\n }\n } catch (error) {\n // Log telemetry initialization errors but don't fail the server\n console.error(\"[Telemetry] Failed to initialize telemetry:\", error);\n console.error(\"[Telemetry] Server will continue without telemetry\");\n }\n } else if (process.env.DEBUG || process.env.VERBOSE) {\n console.log(`[Telemetry] TELEMETRY_ENABLED is false - telemetry disabled`);\n }\n\n // Load model with full content\n const model = await Model.load(projectPath, { lazyLoad: false });\n\n // Create and start server\n const server = new VisualizationServer(model, {\n authEnabled,\n authToken,\n withDanger,\n withChangesets,\n viewerPath,\n });\n serverInstance = server; // Store for signal handlers\n await server.start(port);\n\n // Output token for parent process to read\n if (authEnabled) {\n console.log(`TOKEN:${server.getAuthToken()}`);\n }\n\n // Setup graceful shutdown handlers\n setupShutdownHandlers();\n } catch (error) {\n const message = getErrorMessage(error);\n console.error(`Error: ${message}`);\n if (error instanceof Error) {\n console.error(`Stack: ${error.stack}`);\n }\n await gracefulShutdown(1);\n }\n}\n\n/**\n * Gracefully shutdown the server and flush telemetry\n */\nasync function gracefulShutdown(exitCode: number = 0): Promise<void> {\n if (isShuttingDown) {\n return; // Prevent multiple shutdown attempts\n }\n isShuttingDown = true;\n\n try {\n // Stop the visualization server\n if (serverInstance) {\n serverInstance.stop();\n serverInstance = null;\n }\n\n // Flush telemetry spans before exit\n if (isTelemetryEnabled) {\n console.log(\"[Telemetry] Shutting down telemetry and flushing spans...\");\n const { shutdownTelemetry } = await import(\"./telemetry/index.js\");\n await shutdownTelemetry();\n console.log(\"[Telemetry] Telemetry shutdown complete\");\n } else {\n if (process.env.DEBUG) {\n console.log(\"[Telemetry] Telemetry is disabled (TELEMETRY_ENABLED=false at build time)\");\n }\n }\n } catch (error) {\n // Don't block shutdown on errors\n if (process.env.DEBUG) {\n console.error(\"Error during shutdown:\", error);\n }\n } finally {\n process.exit(exitCode);\n }\n}\n\n/**\n * Setup handlers for graceful shutdown on signals\n */\nfunction setupShutdownHandlers(): void {\n // Handle Ctrl-C (SIGINT)\n process.on(\"SIGINT\", async () => {\n console.log(\"\\n[SIGINT] Shutting down visualization server...\");\n await gracefulShutdown(0);\n });\n\n // Handle termination signal (SIGTERM)\n process.on(\"SIGTERM\", async () => {\n console.log(\"\\n[SIGTERM] Shutting down visualization server...\");\n await gracefulShutdown(0);\n });\n\n // Handle uncaught exceptions\n process.on(\"uncaughtException\", async (error) => {\n console.error(\"Uncaught exception:\", error);\n await gracefulShutdown(1);\n });\n\n // Handle unhandled promise rejections\n process.on(\"unhandledRejection\", async (reason) => {\n console.error(\"Unhandled rejection:\", reason);\n await gracefulShutdown(1);\n });\n}\n\nmain();\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -21,7 +21,7 @@ async function initTelemetry() {
21
21
  `), process.stderr.write(` - Logs endpoint: ${otlpConfig.logsEndpoint}
22
22
  `), process.stderr.write(` - Service name: ${otlpConfig.serviceName}
23
23
  `));
24
- const cliVersion = "0.2.0";
24
+ const cliVersion = "0.3.0";
25
25
  let projectName = "unknown";
26
26
  try {
27
27
  const [{ readFileSync, existsSync }, { parse }] = await Promise.all([
@@ -2,22 +2,20 @@ function stripAnsi(text) {
2
2
  return text.replace(/\x1b\[[0-9;]*m/g, "");
3
3
  }
4
4
  function installAnsiSuppressor() {
5
- if (process.stdout.isTTY === !0)
6
- return;
7
5
  const originalLog = console.log, originalError = console.error, originalWarn = console.warn, originalInfo = console.info;
8
- console.log = function(...args) {
6
+ process.stdout.isTTY !== !0 && (console.log = function(...args) {
9
7
  const stripped = args.map((arg) => typeof arg == "string" ? stripAnsi(arg) : arg);
10
8
  return originalLog.apply(console, stripped);
11
- }, console.error = function(...args) {
9
+ }, console.info = function(...args) {
10
+ const stripped = args.map((arg) => typeof arg == "string" ? stripAnsi(arg) : arg);
11
+ return originalInfo.apply(console, stripped);
12
+ }), process.stderr.isTTY !== !0 && (console.error = function(...args) {
12
13
  const stripped = args.map((arg) => typeof arg == "string" ? stripAnsi(arg) : arg);
13
14
  return originalError.apply(console, stripped);
14
15
  }, console.warn = function(...args) {
15
16
  const stripped = args.map((arg) => typeof arg == "string" ? stripAnsi(arg) : arg);
16
17
  return originalWarn.apply(console, stripped);
17
- }, console.info = function(...args) {
18
- const stripped = args.map((arg) => typeof arg == "string" ? stripAnsi(arg) : arg);
19
- return originalInfo.apply(console, stripped);
20
- };
18
+ });
21
19
  }
22
20
  export {
23
21
  installAnsiSuppressor
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/ansi-suppressor.ts"],
4
- "sourcesContent": ["/**\n * Global ANSI code suppression for non-TTY output\n * Intercepts console output and strips ANSI escape sequences when stdout is not a TTY\n */\n\nfunction stripAnsi(text: string): string {\n // eslint-disable-next-line no-control-regex\n return text.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport function installAnsiSuppressor(): void {\n // Only suppress if stdout is not a TTY\n if (process.stdout.isTTY === true) {\n return;\n }\n\n const originalLog = console.log;\n const originalError = console.error;\n const originalWarn = console.warn;\n const originalInfo = console.info;\n\n console.log = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalLog.apply(console, stripped);\n };\n\n console.error = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalError.apply(console, stripped);\n };\n\n console.warn = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalWarn.apply(console, stripped);\n };\n\n console.info = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalInfo.apply(console, stripped);\n };\n}\n"],
5
- "mappings": "AAKA,SAAS,UAAU,MAAY;AAE7B,SAAO,KAAK,QAAQ,mBAAmB,EAAE;AAC3C;AAEM,SAAU,wBAAqB;AAEnC,MAAI,QAAQ,OAAO,UAAU;AAC3B;AAGF,QAAM,cAAc,QAAQ,KACtB,gBAAgB,QAAQ,OACxB,eAAe,QAAQ,MACvB,eAAe,QAAQ;AAE7B,UAAQ,MAAM,YAAa,MAAW;AACpC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,YAAY,MAAM,SAAS,QAAQ;EAC5C,GAEA,QAAQ,QAAQ,YAAa,MAAW;AACtC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,cAAc,MAAM,SAAS,QAAQ;EAC9C,GAEA,QAAQ,OAAO,YAAa,MAAW;AACrC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,aAAa,MAAM,SAAS,QAAQ;EAC7C,GAEA,QAAQ,OAAO,YAAa,MAAW;AACrC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,aAAa,MAAM,SAAS,QAAQ;EAC7C;AACF;",
4
+ "sourcesContent": ["/**\n * Global ANSI code suppression for non-TTY output\n * Intercepts console output and strips ANSI escape sequences when the target stream is not a TTY\n * Handles stdout (log/info) and stderr (error/warn) independently based on their respective TTY status\n */\n\nfunction stripAnsi(text: string): string {\n // eslint-disable-next-line no-control-regex\n return text.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport function installAnsiSuppressor(): void {\n const originalLog = console.log;\n const originalError = console.error;\n const originalWarn = console.warn;\n const originalInfo = console.info;\n\n // Suppress ANSI codes from stdout (log/info) if stdout is not a TTY\n const shouldSuppressStdout = process.stdout.isTTY !== true;\n if (shouldSuppressStdout) {\n console.log = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalLog.apply(console, stripped);\n };\n\n console.info = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalInfo.apply(console, stripped);\n };\n }\n\n // Suppress ANSI codes from stderr (error/warn) if stderr is not a TTY\n const shouldSuppressStderr = process.stderr.isTTY !== true;\n if (shouldSuppressStderr) {\n console.error = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalError.apply(console, stripped);\n };\n\n console.warn = function (...args: any[]) {\n const stripped = args.map((arg) => {\n if (typeof arg === \"string\") {\n return stripAnsi(arg);\n }\n return arg;\n });\n return originalWarn.apply(console, stripped);\n };\n }\n}\n"],
5
+ "mappings": "AAMA,SAAS,UAAU,MAAY;AAE7B,SAAO,KAAK,QAAQ,mBAAmB,EAAE;AAC3C;AAEM,SAAU,wBAAqB;AACnC,QAAM,cAAc,QAAQ,KACtB,gBAAgB,QAAQ,OACxB,eAAe,QAAQ,MACvB,eAAe,QAAQ;AAI7B,EAD6B,QAAQ,OAAO,UAAU,OAEpD,QAAQ,MAAM,YAAa,MAAW;AACpC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,YAAY,MAAM,SAAS,QAAQ;EAC5C,GAEA,QAAQ,OAAO,YAAa,MAAW;AACrC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,aAAa,MAAM,SAAS,QAAQ;EAC7C,IAI2B,QAAQ,OAAO,UAAU,OAEpD,QAAQ,QAAQ,YAAa,MAAW;AACtC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,cAAc,MAAM,SAAS,QAAQ;EAC9C,GAEA,QAAQ,OAAO,YAAa,MAAW;AACrC,UAAM,WAAW,KAAK,IAAI,CAAC,QACrB,OAAO,OAAQ,WACV,UAAU,GAAG,IAEf,GACR;AACD,WAAO,aAAa,MAAM,SAAS,QAAQ;EAC7C;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,41 @@
1
+ import { CLIError, ErrorCategory } from "./errors.js";
2
+ function isPlainObject(value) {
3
+ return !!value && typeof value == "object" && !Array.isArray(value);
4
+ }
5
+ function applyAttributePatch(current, patch) {
6
+ const merged = { ...current ?? {} };
7
+ for (const [key, value] of Object.entries(patch))
8
+ value === null ? delete merged[key] : value !== void 0 && (merged[key] = value);
9
+ return merged;
10
+ }
11
+ function effectiveAttributePatch(patch) {
12
+ return patch !== void 0 && Object.keys(patch).length > 0 ? patch : void 0;
13
+ }
14
+ function resolveAttributeUpdate(current, update, flags = { attributes: "attributes", attributePatch: "attributePatch" }) {
15
+ if (update.attributes !== void 0 && update.attributePatch !== void 0)
16
+ throw new CLIError(`Cannot use ${flags.attributes} and ${flags.attributePatch} together`, ErrorCategory.USER, [
17
+ `Use ${flags.attributes} to replace all attributes, or ${flags.attributePatch} to change only the keys it names`
18
+ ]);
19
+ if (update.attributes !== void 0)
20
+ return update.attributes;
21
+ if (update.attributePatch !== void 0)
22
+ return applyAttributePatch(current, update.attributePatch);
23
+ }
24
+ function parseJsonObjectOption(raw, flag) {
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(raw);
28
+ } catch {
29
+ throw new CLIError(`Invalid JSON in ${flag}`, ErrorCategory.USER, ["Ensure your JSON is valid and properly formatted"]);
30
+ }
31
+ if (!isPlainObject(parsed))
32
+ throw new CLIError(`${flag} must be a JSON object`, ErrorCategory.USER, [`Example: ${flag} '{"summary":"New summary"}'`]);
33
+ return parsed;
34
+ }
35
+ export {
36
+ applyAttributePatch,
37
+ effectiveAttributePatch,
38
+ parseJsonObjectOption,
39
+ resolveAttributeUpdate
40
+ };
41
+ //# sourceMappingURL=attribute-patch.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/attribute-patch.ts"],
4
+ "sourcesContent": ["/**\n * Attribute updates for `dr update` and the MCP `model_update` tool.\n *\n * `attributes` replaces an element's attributes; `attributePatch` merges into them, so changing one\n * attribute does not mean repeating every required one. A `null` value in a patch removes that key.\n * Both callers resolve the new attributes here so they behave the same; schema validation then runs\n * on the result.\n */\n\nimport { CLIError, ErrorCategory } from \"./errors.js\";\n\nexport type AttributePatch = Record<string, unknown>;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Merges `patch` over `current` (shallow: a patched key's value replaces the old one), removing keys\n * whose patch value is `null`. Returns a new object; `current` is not modified (staged `before`\n * snapshots share the element's attributes object).\n */\nexport function applyAttributePatch(\n current: Record<string, unknown> | undefined,\n patch: AttributePatch\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...(current ?? {}) };\n for (const [key, value] of Object.entries(patch)) {\n if (value === null) {\n delete merged[key];\n } else if (value !== undefined) {\n merged[key] = value;\n }\n }\n return merged;\n}\n\n/**\n * The patch as an update: an empty patch names no key, so it changes nothing and is not an update\n * (callers then report \"No fields specified\" and stage nothing).\n */\nexport function effectiveAttributePatch(patch: AttributePatch | undefined): AttributePatch | undefined {\n return patch !== undefined && Object.keys(patch).length > 0 ? patch : undefined;\n}\n\n/**\n * The attributes an update asks for: `attributes` as given (replace), or `attributePatch` merged\n * over `current`, or undefined when the update leaves attributes alone. Passing both is an error.\n *\n * @param flags how the two options are spelled to the caller, for the error message\n */\nexport function resolveAttributeUpdate(\n current: Record<string, unknown> | undefined,\n update: { attributes?: Record<string, unknown>; attributePatch?: AttributePatch },\n flags: { attributes: string; attributePatch: string } = { attributes: \"attributes\", attributePatch: \"attributePatch\" }\n): Record<string, unknown> | undefined {\n if (update.attributes !== undefined && update.attributePatch !== undefined) {\n throw new CLIError(`Cannot use ${flags.attributes} and ${flags.attributePatch} together`, ErrorCategory.USER, [\n `Use ${flags.attributes} to replace all attributes, or ${flags.attributePatch} to change only the keys it names`,\n ]);\n }\n if (update.attributes !== undefined) return update.attributes;\n if (update.attributePatch !== undefined) return applyAttributePatch(current, update.attributePatch);\n return undefined;\n}\n\n/** Parses a CLI JSON option that must be an object (`--attributes`, `--attribute-patch`). */\nexport function parseJsonObjectOption(raw: string, flag: string): Record<string, unknown> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new CLIError(`Invalid JSON in ${flag}`, ErrorCategory.USER, [\"Ensure your JSON is valid and properly formatted\"]);\n }\n if (!isPlainObject(parsed)) {\n throw new CLIError(`${flag} must be a JSON object`, ErrorCategory.USER, [`Example: ${flag} '{\"summary\":\"New summary\"}'`]);\n }\n return parsed;\n}\n"],
5
+ "mappings": "AASA,SAAS,UAAU,qBAAqB;AAIxC,SAAS,cAAc,OAAc;AACnC,SAAO,CAAC,CAAC,SAAS,OAAO,SAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AACrE;AAOM,SAAU,oBACd,SACA,OAAqB;AAErB,QAAM,SAAkC,EAAE,GAAI,WAAW,CAAA,EAAG;AAC5D,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK;AAC7C,IAAI,UAAU,OACZ,OAAO,OAAO,GAAG,IACR,UAAU,WACnB,OAAO,GAAG,IAAI;AAGlB,SAAO;AACT;AAMM,SAAU,wBAAwB,OAAiC;AACvE,SAAO,UAAU,UAAa,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACxE;AAQM,SAAU,uBACd,SACA,QACA,QAAwD,EAAE,YAAY,cAAc,gBAAgB,iBAAgB,GAAE;AAEtH,MAAI,OAAO,eAAe,UAAa,OAAO,mBAAmB;AAC/D,UAAM,IAAI,SAAS,cAAc,MAAM,UAAU,QAAQ,MAAM,cAAc,aAAa,cAAc,MAAM;MAC5G,OAAO,MAAM,UAAU,kCAAkC,MAAM,cAAc;KAC9E;AAEH,MAAI,OAAO,eAAe;AAAW,WAAO,OAAO;AACnD,MAAI,OAAO,mBAAmB;AAAW,WAAO,oBAAoB,SAAS,OAAO,cAAc;AAEpG;AAGM,SAAU,sBAAsB,KAAa,MAAY;AAC7D,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;EACzB,QAAQ;AACN,UAAM,IAAI,SAAS,mBAAmB,IAAI,IAAI,cAAc,MAAM,CAAC,kDAAkD,CAAC;EACxH;AACA,MAAI,CAAC,cAAc,MAAM;AACvB,UAAM,IAAI,SAAS,GAAG,IAAI,0BAA0B,cAAc,MAAM,CAAC,YAAY,IAAI,8BAA8B,CAAC;AAE1H,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,29 @@
1
+ const CLI_FLAGS = {
2
+ sourceFile: "--source-file",
3
+ sourceSymbol: "--source-symbol",
4
+ sourceProvenance: "--source-provenance",
5
+ sourceRepoRemote: "--source-repo-remote",
6
+ sourceRepoCommit: "--source-repo-commit",
7
+ clearSourceReference: "--clear-source-reference"
8
+ };
9
+ function optionName(surface, option) {
10
+ return surface === "mcp" ? option : CLI_FLAGS[option];
11
+ }
12
+ function optionExample(surface, option, value) {
13
+ return surface === "mcp" ? `${option}: "${value}"` : `${CLI_FLAGS[option]} "${value}"`;
14
+ }
15
+ function schemaLookupHint(surface, layer, type) {
16
+ return surface === "mcp" ? `Call "spec_type" with layer "${layer}" and type "${type}" to see the required attributes` : `Run "dr schema node ${layer}.${type}" to see the required attributes`;
17
+ }
18
+ function undefinedTypeWarning(surface, layer, elementId, type, replacements) {
19
+ const message = `Element '${elementId}' has type '${type}', which the spec does not define for the ${layer} layer, so its attributes were not checked against a type schema`, replacement = replacements[0];
20
+ let hint;
21
+ return surface === "mcp" ? hint = replacement ? `Pass type "${replacement}" to model_update to move the element to a type the spec defines` : `Call "spec_type" with layer "${layer}" and no type to list the layer's types, then pass one as type to model_update` : hint = replacement ? `Run "dr update ${elementId} --type ${replacement}" to move the element to a type the spec defines` : `Run "dr schema types ${layer}" to list the layer's types, then "dr update ${elementId} --type <type>"`, { message, hint };
22
+ }
23
+ export {
24
+ optionExample,
25
+ optionName,
26
+ schemaLookupHint,
27
+ undefinedTypeWarning
28
+ };
29
+ //# sourceMappingURL=error-hints.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/error-hints.ts"],
4
+ "sourcesContent": ["/**\n * Surface-aware error hints.\n *\n * The CLI and the MCP server share validation code, but a user at the terminal passes\n * `--source-provenance` and runs `dr schema`, while an agent over MCP passes the\n * `sourceProvenance` parameter and calls the `spec_type` tool. Errors built from shared code\n * take a surface and name things the way that caller can act on them.\n */\n\nexport type HintSurface = \"cli\" | \"mcp\";\n\n/** Option keys that shared validators name in their messages. */\nexport type HintOption =\n | \"sourceFile\"\n | \"sourceSymbol\"\n | \"sourceProvenance\"\n | \"sourceRepoRemote\"\n | \"sourceRepoCommit\"\n | \"clearSourceReference\";\n\nconst CLI_FLAGS: Record<HintOption, string> = {\n sourceFile: \"--source-file\",\n sourceSymbol: \"--source-symbol\",\n sourceProvenance: \"--source-provenance\",\n sourceRepoRemote: \"--source-repo-remote\",\n sourceRepoCommit: \"--source-repo-commit\",\n clearSourceReference: \"--clear-source-reference\",\n};\n\n/** How the caller names an option: `--source-file` on the CLI, `sourceFile` over MCP. */\nexport function optionName(surface: HintSurface, option: HintOption): string {\n return surface === \"mcp\" ? option : CLI_FLAGS[option];\n}\n\n/** How the caller passes an option with a value, for examples in hints. */\nexport function optionExample(surface: HintSurface, option: HintOption, value: string): string {\n return surface === \"mcp\" ? `${option}: \"${value}\"` : `${CLI_FLAGS[option]} \"${value}\"`;\n}\n\n/** Where to look up a type's required attributes: `dr schema` on the CLI, `spec_type` over MCP. */\nexport function schemaLookupHint(surface: HintSurface, layer: string, type: string): string {\n return surface === \"mcp\"\n ? `Call \"spec_type\" with layer \"${layer}\" and type \"${type}\" to see the required attributes`\n : `Run \"dr schema node ${layer}.${type}\" to see the required attributes`;\n}\n\n/**\n * Warning for an update to an element whose current type the spec does not define (an obsolete\n * `product.capability`). With no type schema there is nothing to check its attributes against, so\n * the update goes through; the hint names the type change that fixes it, because looking up the\n * obsolete type with `spec_type` or `dr schema node` cannot work.\n */\nexport function undefinedTypeWarning(\n surface: HintSurface,\n layer: string,\n elementId: string,\n type: string,\n replacements: string[]\n): { message: string; hint: string } {\n const message =\n `Element '${elementId}' has type '${type}', which the spec does not define for the ${layer} layer, ` +\n `so its attributes were not checked against a type schema`;\n const replacement = replacements[0];\n let hint: string;\n if (surface === \"mcp\") {\n hint = replacement\n ? `Pass type \"${replacement}\" to model_update to move the element to a type the spec defines`\n : `Call \"spec_type\" with layer \"${layer}\" and no type to list the layer's types, then pass one as type to model_update`;\n } else {\n hint = replacement\n ? `Run \"dr update ${elementId} --type ${replacement}\" to move the element to a type the spec defines`\n : `Run \"dr schema types ${layer}\" to list the layer's types, then \"dr update ${elementId} --type <type>\"`;\n }\n return { message, hint };\n}\n"],
5
+ "mappings": "AAoBA,MAAM,YAAwC;EAC5C,YAAY;EACZ,cAAc;EACd,kBAAkB;EAClB,kBAAkB;EAClB,kBAAkB;EAClB,sBAAsB;;AAIlB,SAAU,WAAW,SAAsB,QAAkB;AACjE,SAAO,YAAY,QAAQ,SAAS,UAAU,MAAM;AACtD;AAGM,SAAU,cAAc,SAAsB,QAAoB,OAAa;AACnF,SAAO,YAAY,QAAQ,GAAG,MAAM,MAAM,KAAK,MAAM,GAAG,UAAU,MAAM,CAAC,KAAK,KAAK;AACrF;AAGM,SAAU,iBAAiB,SAAsB,OAAe,MAAY;AAChF,SAAO,YAAY,QACf,gCAAgC,KAAK,eAAe,IAAI,qCACxD,uBAAuB,KAAK,IAAI,IAAI;AAC1C;AAQM,SAAU,qBACd,SACA,OACA,WACA,MACA,cAAsB;AAEtB,QAAM,UACJ,YAAY,SAAS,eAAe,IAAI,6CAA6C,KAAK,oEAEtF,cAAc,aAAa,CAAC;AAClC,MAAI;AACJ,SAAI,YAAY,QACd,OAAO,cACH,cAAc,WAAW,qEACzB,gCAAgC,KAAK,mFAEzC,OAAO,cACH,kBAAkB,SAAS,WAAW,WAAW,qDACjD,wBAAwB,KAAK,gDAAgD,SAAS,mBAErF,EAAE,SAAS,KAAI;AACxB;",
6
+ "names": []
7
+ }
@@ -162,8 +162,8 @@ function handleWarning(message, suggestions) {
162
162
  function handleSuccess(message, details, options) {
163
163
  if (isJson()) {
164
164
  const output = {
165
- status: "ok",
166
- ...details
165
+ ...details,
166
+ status: "ok"
167
167
  };
168
168
  console.log(JSON.stringify(output));
169
169
  } else if (console.log(ansis.green(`\u2713 ${message}`)), details && (options?.verbose || details.changesetStatus))
@@ -176,7 +176,13 @@ function keyToDisplayName(key) {
176
176
  return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
177
177
  }
178
178
  function handleInfo(message, details) {
179
- if (!isJson() && (console.log(message), details))
179
+ if (isJson()) {
180
+ const output = {
181
+ level: "info",
182
+ message
183
+ };
184
+ details && (output.details = details), console.error(JSON.stringify(output));
185
+ } else if (console.log(message), details)
180
186
  for (const [key, value] of Object.entries(details))
181
187
  console.log(ansis.dim(` ${key}: ${value}`));
182
188
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/errors.ts"],
4
- "sourcesContent": ["/**\n * Error handling utilities with formatted output\n * Matches Python CLI error message formatting\n *\n * Error Categories:\n * - User Error (exit code 1): Invalid input, wrong format, element exists\n * - Not Found (exit code 2): File/element/model not found\n * - System Error (exit code 3): Permission denied, I/O failure\n * - Validation Error (exit code 4): Schema/reference/semantic validation failed\n * - Breaking Change (exit code 5): Version migration required\n */\n\nimport ansis from \"ansis\";\nimport { isJson } from \"./globals.js\";\n\nconst MAX_SUGGESTIONS = 5;\n\nexport enum ErrorCategory {\n USER = 1,\n NOT_FOUND = 2,\n SYSTEM = 3,\n VALIDATION = 4,\n BREAKING_CHANGE = 5,\n}\n\nexport interface ErrorContext {\n operation?: string;\n context?: string;\n relatedElements?: string[];\n partialProgress?: { completed: number; total: number };\n}\n\nexport class CLIError extends Error {\n constructor(\n message: string,\n public exitCode: number = ErrorCategory.USER,\n public suggestions?: string[],\n public context?: ErrorContext\n ) {\n super(message);\n this.name = \"CLIError\";\n }\n\n format(): string {\n const lines: string[] = [];\n\n lines.push(ansis.red(`Error: ${this.message}`));\n\n if (this.context?.operation) {\n lines.push(ansis.dim(`During: ${this.context.operation}`));\n }\n\n if (this.context?.partialProgress) {\n const { completed, total } = this.context.partialProgress;\n lines.push(\n ansis.yellow(`\u26A0 Partial progress: ${completed}/${total} completed (operation rolled back)`)\n );\n }\n\n if (this.context?.relatedElements && this.context.relatedElements.length > 0) {\n lines.push(ansis.dim(\"Related elements:\"));\n for (const elem of this.context.relatedElements.slice(0, MAX_SUGGESTIONS)) {\n lines.push(ansis.dim(` \u2022 ${elem}`));\n }\n if (this.context.relatedElements.length > MAX_SUGGESTIONS) {\n lines.push(\n ansis.dim(` ... and ${this.context.relatedElements.length - MAX_SUGGESTIONS} more`)\n );\n }\n }\n\n if (this.suggestions && this.suggestions.length > 0) {\n lines.push(\"\");\n lines.push(ansis.dim(\"Suggestions:\"));\n for (const suggestion of this.suggestions) {\n lines.push(ansis.dim(` \u2022 ${suggestion}`));\n }\n }\n\n return lines.join(\"\\n\");\n }\n}\n\nexport class ValidationError extends CLIError {\n constructor(\n message: string,\n public errors: Array<{ layer?: string; elementId?: string; message: string }> = [],\n suggestions?: string[],\n context?: ErrorContext\n ) {\n super(message, ErrorCategory.VALIDATION, suggestions, context);\n this.name = \"ValidationError\";\n }\n\n format(): string {\n const lines: string[] = [];\n\n lines.push(ansis.red(`Error: ${this.message}`));\n\n if (this.errors.length > 0) {\n // Show error count and grouping\n const errorsByLayer: Record<string, Array<{ elementId?: string; message: string }>> = {};\n for (const error of this.errors) {\n const layer = error.layer || \"general\";\n if (!errorsByLayer[layer]) {\n errorsByLayer[layer] = [];\n }\n errorsByLayer[layer].push({ elementId: error.elementId, message: error.message });\n }\n\n lines.push(\"\");\n lines.push(ansis.dim(`Validation errors: ${this.errors.length} found`));\n lines.push(\"\");\n\n const layerNames = Object.keys(errorsByLayer).sort();\n\n for (const layer of layerNames) {\n const layerErrors = errorsByLayer[layer];\n lines.push(ansis.dim(` [${layer}] ${layerErrors.length} error(s):`));\n\n for (let i = 0; i < Math.min(layerErrors.length, MAX_SUGGESTIONS); i++) {\n const error = layerErrors[i];\n let detail = \"\";\n if (error.elementId) {\n detail = ` ${error.elementId}: ${error.message}`;\n } else {\n detail = ` ${error.message}`;\n }\n lines.push(ansis.dim(detail));\n }\n\n if (layerErrors.length > MAX_SUGGESTIONS) {\n lines.push(\n ansis.dim(` ... and ${layerErrors.length - MAX_SUGGESTIONS} more in this layer`)\n );\n }\n }\n }\n\n if (this.suggestions && this.suggestions.length > 0) {\n lines.push(\"\");\n lines.push(ansis.dim(\"Suggestions:\"));\n for (const suggestion of this.suggestions) {\n lines.push(ansis.dim(` \u2022 ${suggestion}`));\n }\n }\n\n return lines.join(\"\\n\");\n }\n}\n\nexport class FileNotFoundError extends CLIError {\n constructor(path: string, context?: string) {\n const message = context ? `${context}: ${path}` : `File not found: ${path}`;\n super(message, ErrorCategory.NOT_FOUND, [\n `Check that the path is correct: ${path}`,\n 'Use \"dr init\" to create a new model',\n 'Use \"dr list <layer>\" to see available elements',\n ]);\n this.name = \"FileNotFoundError\";\n }\n}\n\nexport class ElementNotFoundError extends CLIError {\n constructor(elementId: string) {\n super(`Element not found: ${elementId}`, ErrorCategory.NOT_FOUND, [\n `Use \"dr search ${elementId}\" to find similar elements`,\n 'Use \"dr list <layer>\" to list all elements in a layer',\n \"Check the element ID format: {layer}.{type}.{kebab-case-name}\",\n ]);\n this.name = \"ElementNotFoundError\";\n }\n}\n\nexport class ModelNotFoundError extends CLIError {\n constructor(rootPath: string = \".\") {\n super(`No model found at ${rootPath}`, ErrorCategory.NOT_FOUND, [\n `Run \"dr init\" to initialize a new model`,\n \"Check that you are in the correct directory\",\n ]);\n this.name = \"ModelNotFoundError\";\n }\n}\n\nexport class InvalidJSONError extends CLIError {\n constructor(input: string, context?: string) {\n const message = context ? `Invalid JSON in ${context}` : \"Invalid JSON format\";\n super(message, ErrorCategory.USER, [\n `Check your JSON syntax: ${input}`,\n 'Use single quotes to wrap JSON: --attributes \\'{\"key\":\"value\"}\\'',\n \"Escape special characters properly\",\n ]);\n this.name = \"InvalidJSONError\";\n }\n}\n\nexport function handleError(error: unknown): never {\n if (error instanceof CLIError) {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"error\",\n code: error.exitCode,\n message: error.message,\n };\n if (error.suggestions && error.suggestions.length > 0) {\n output.suggestions = error.suggestions;\n }\n if (error.context) {\n if (error.context.operation) output.operation = error.context.operation;\n if (error.context.relatedElements && error.context.relatedElements.length > 0) {\n output.relatedElements = error.context.relatedElements;\n }\n if (error.context.partialProgress) {\n output.partialProgress = error.context.partialProgress;\n }\n }\n console.log(JSON.stringify(output));\n } else {\n console.error(error.format());\n }\n // Don't call process.exit() - rethrow so CLI wrapper can handle telemetry shutdown\n throw error;\n } else if (error instanceof Error) {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"error\",\n code: ErrorCategory.USER,\n message: error.message,\n };\n if (process.env.DEBUG && error.stack) {\n output.stack = error.stack;\n }\n console.log(JSON.stringify(output));\n } else {\n console.error(ansis.red(`Error: ${error.message}`));\n if (process.env.DEBUG) {\n console.error(ansis.dim(error.stack));\n }\n }\n // Rethrow to allow CLI wrapper to handle shutdown\n throw error;\n } else {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"error\",\n code: ErrorCategory.USER,\n message: \"An unexpected error occurred\",\n };\n if (process.env.DEBUG) {\n output.details = String(error);\n }\n console.log(JSON.stringify(output));\n } else {\n console.error(ansis.red(\"An unexpected error occurred\"));\n if (process.env.DEBUG) {\n console.error(ansis.dim(String(error)));\n }\n }\n // Create proper error to throw\n throw new Error(String(error));\n }\n}\n\nexport function handleWarning(message: string, suggestions?: string[]): void {\n if (isJson()) {\n // In JSON mode, emit warnings to stderr as structured JSON\n const output: Record<string, unknown> = {\n level: \"warning\",\n message,\n };\n if (suggestions && suggestions.length > 0) {\n output.suggestions = suggestions;\n }\n console.error(JSON.stringify(output));\n } else {\n const lines: string[] = [];\n lines.push(ansis.yellow(`Warning: ${message}`));\n if (suggestions && suggestions.length > 0) {\n lines.push(\"\");\n lines.push(ansis.dim(\"Suggestions:\"));\n for (const suggestion of suggestions) {\n lines.push(ansis.dim(` \u2022 ${suggestion}`));\n }\n }\n console.warn(lines.join(\"\\n\"));\n }\n}\n\nexport function handleSuccess(message: string, details?: Record<string, unknown>, options?: { verbose?: boolean }): void {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"ok\",\n ...details,\n };\n console.log(JSON.stringify(output));\n } else {\n console.log(ansis.green(`\u2713 ${message}`));\n // Only show details in non-JSON mode if verbose flag is set or details include changesetStatus/status\n if (details && (options?.verbose || details.changesetStatus)) {\n // Convert camelCase keys to PascalCase for display\n for (const [key, value] of Object.entries(details)) {\n const displayKey = keyToDisplayName(key);\n console.log(ansis.dim(` ${displayKey}: ${value}`));\n }\n }\n }\n}\n\nfunction keyToDisplayName(key: string): string {\n // Convert camelCase and snake_case to PascalCase/Title Case\n return key\n .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase to words\n .replace(/_/g, ' ') // snake_case to words\n .split(' ')\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\nexport function handleInfo(message: string, details?: Record<string, string>): void {\n if (!isJson()) {\n console.log(message);\n if (details) {\n for (const [key, value] of Object.entries(details)) {\n console.log(ansis.dim(` ${key}: ${value}`));\n }\n }\n }\n}\n\n/**\n * Utility to extract common valid options for better error suggestions\n */\nexport function formatValidOptions(options: string[], heading: string = \"Valid options\"): string {\n if (options.length === 0) return \"\";\n if (options.length === 1) return `${heading}: ${options[0]}`;\n return `${heading}: ${options.slice(0, -1).join(\", \")} or ${options[options.length - 1]}`;\n}\n\n/**\n * Categorize errors based on type, using stable error.code for system errors\n * Returns the appropriate ErrorCategory for a given error\n */\nexport function categorizeError(error: unknown): ErrorCategory {\n if (error instanceof CLIError) {\n return error.exitCode;\n }\n\n // Check for system errors using stable error.code\n if (error instanceof Error && \"code\" in error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (typeof code === \"string\") {\n // File not found errors should map to NOT_FOUND category\n if (code === \"ENOENT\") {\n return ErrorCategory.NOT_FOUND;\n }\n // Other system-level errors\n const SYSTEM_ERROR_CODES = new Set([\"EACCES\", \"ENOSPC\", \"EIO\", \"EROFS\", \"EPERM\"]);\n if (SYSTEM_ERROR_CODES.has(code)) {\n return ErrorCategory.SYSTEM;\n }\n }\n }\n\n // Default to USER for other errors\n return ErrorCategory.USER;\n}\n\n/**\n * Extract error message from any error type\n * Handles Error instances, strings, and unknown types consistently\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n}\n\n/**\n * Simple fuzzy match to find similar strings (for typo suggestions)\n */\nexport function findSimilar(input: string, options: string[], maxDistance: number = 2): string[] {\n return options\n .map((option) => ({\n option,\n distance: levenshteinDistance(input.toLowerCase(), option.toLowerCase()),\n }))\n .filter(({ distance }) => distance <= maxDistance)\n .sort((a, b) => a.distance - b.distance)\n .slice(0, 3)\n .map(({ option }) => option);\n}\n\n/**\n * Calculate Levenshtein distance for typo detection\n */\nfunction levenshteinDistance(a: string, b: string): number {\n const matrix: number[][] = [];\n\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n\n for (let j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n matrix[i][j] = Math.min(\n matrix[i - 1][j - 1] + 1,\n matrix[i][j - 1] + 1,\n matrix[i - 1][j] + 1\n );\n }\n }\n }\n\n return matrix[b.length][a.length];\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Error handling utilities with formatted output\n * Matches Python CLI error message formatting\n *\n * Error Categories:\n * - User Error (exit code 1): Invalid input, wrong format, element exists\n * - Not Found (exit code 2): File/element/model not found\n * - System Error (exit code 3): Permission denied, I/O failure\n * - Validation Error (exit code 4): Schema/reference/semantic validation failed\n * - Breaking Change (exit code 5): Version migration required\n */\n\nimport ansis from \"ansis\";\nimport { isJson } from \"./globals.js\";\n\nconst MAX_SUGGESTIONS = 5;\n\nexport enum ErrorCategory {\n USER = 1,\n NOT_FOUND = 2,\n SYSTEM = 3,\n VALIDATION = 4,\n BREAKING_CHANGE = 5,\n}\n\nexport interface ErrorContext {\n operation?: string;\n context?: string;\n relatedElements?: string[];\n partialProgress?: { completed: number; total: number };\n}\n\nexport class CLIError extends Error {\n constructor(\n message: string,\n public exitCode: number = ErrorCategory.USER,\n public suggestions?: string[],\n public context?: ErrorContext\n ) {\n super(message);\n this.name = \"CLIError\";\n }\n\n format(): string {\n const lines: string[] = [];\n\n lines.push(ansis.red(`Error: ${this.message}`));\n\n if (this.context?.operation) {\n lines.push(ansis.dim(`During: ${this.context.operation}`));\n }\n\n if (this.context?.partialProgress) {\n const { completed, total } = this.context.partialProgress;\n lines.push(\n ansis.yellow(`\u26A0 Partial progress: ${completed}/${total} completed (operation rolled back)`)\n );\n }\n\n if (this.context?.relatedElements && this.context.relatedElements.length > 0) {\n lines.push(ansis.dim(\"Related elements:\"));\n for (const elem of this.context.relatedElements.slice(0, MAX_SUGGESTIONS)) {\n lines.push(ansis.dim(` \u2022 ${elem}`));\n }\n if (this.context.relatedElements.length > MAX_SUGGESTIONS) {\n lines.push(\n ansis.dim(` ... and ${this.context.relatedElements.length - MAX_SUGGESTIONS} more`)\n );\n }\n }\n\n if (this.suggestions && this.suggestions.length > 0) {\n lines.push(\"\");\n lines.push(ansis.dim(\"Suggestions:\"));\n for (const suggestion of this.suggestions) {\n lines.push(ansis.dim(` \u2022 ${suggestion}`));\n }\n }\n\n return lines.join(\"\\n\");\n }\n}\n\nexport class ValidationError extends CLIError {\n constructor(\n message: string,\n public errors: Array<{ layer?: string; elementId?: string; message: string }> = [],\n suggestions?: string[],\n context?: ErrorContext\n ) {\n super(message, ErrorCategory.VALIDATION, suggestions, context);\n this.name = \"ValidationError\";\n }\n\n format(): string {\n const lines: string[] = [];\n\n lines.push(ansis.red(`Error: ${this.message}`));\n\n if (this.errors.length > 0) {\n // Show error count and grouping\n const errorsByLayer: Record<string, Array<{ elementId?: string; message: string }>> = {};\n for (const error of this.errors) {\n const layer = error.layer || \"general\";\n if (!errorsByLayer[layer]) {\n errorsByLayer[layer] = [];\n }\n errorsByLayer[layer].push({ elementId: error.elementId, message: error.message });\n }\n\n lines.push(\"\");\n lines.push(ansis.dim(`Validation errors: ${this.errors.length} found`));\n lines.push(\"\");\n\n const layerNames = Object.keys(errorsByLayer).sort();\n\n for (const layer of layerNames) {\n const layerErrors = errorsByLayer[layer];\n lines.push(ansis.dim(` [${layer}] ${layerErrors.length} error(s):`));\n\n for (let i = 0; i < Math.min(layerErrors.length, MAX_SUGGESTIONS); i++) {\n const error = layerErrors[i];\n let detail = \"\";\n if (error.elementId) {\n detail = ` ${error.elementId}: ${error.message}`;\n } else {\n detail = ` ${error.message}`;\n }\n lines.push(ansis.dim(detail));\n }\n\n if (layerErrors.length > MAX_SUGGESTIONS) {\n lines.push(\n ansis.dim(` ... and ${layerErrors.length - MAX_SUGGESTIONS} more in this layer`)\n );\n }\n }\n }\n\n if (this.suggestions && this.suggestions.length > 0) {\n lines.push(\"\");\n lines.push(ansis.dim(\"Suggestions:\"));\n for (const suggestion of this.suggestions) {\n lines.push(ansis.dim(` \u2022 ${suggestion}`));\n }\n }\n\n return lines.join(\"\\n\");\n }\n}\n\nexport class FileNotFoundError extends CLIError {\n constructor(path: string, context?: string) {\n const message = context ? `${context}: ${path}` : `File not found: ${path}`;\n super(message, ErrorCategory.NOT_FOUND, [\n `Check that the path is correct: ${path}`,\n 'Use \"dr init\" to create a new model',\n 'Use \"dr list <layer>\" to see available elements',\n ]);\n this.name = \"FileNotFoundError\";\n }\n}\n\nexport class ElementNotFoundError extends CLIError {\n constructor(elementId: string) {\n super(`Element not found: ${elementId}`, ErrorCategory.NOT_FOUND, [\n `Use \"dr search ${elementId}\" to find similar elements`,\n 'Use \"dr list <layer>\" to list all elements in a layer',\n \"Check the element ID format: {layer}.{type}.{kebab-case-name}\",\n ]);\n this.name = \"ElementNotFoundError\";\n }\n}\n\nexport class ModelNotFoundError extends CLIError {\n constructor(rootPath: string = \".\") {\n super(`No model found at ${rootPath}`, ErrorCategory.NOT_FOUND, [\n `Run \"dr init\" to initialize a new model`,\n \"Check that you are in the correct directory\",\n ]);\n this.name = \"ModelNotFoundError\";\n }\n}\n\nexport class InvalidJSONError extends CLIError {\n constructor(input: string, context?: string) {\n const message = context ? `Invalid JSON in ${context}` : \"Invalid JSON format\";\n super(message, ErrorCategory.USER, [\n `Check your JSON syntax: ${input}`,\n 'Use single quotes to wrap JSON: --attributes \\'{\"key\":\"value\"}\\'',\n \"Escape special characters properly\",\n ]);\n this.name = \"InvalidJSONError\";\n }\n}\n\nexport function handleError(error: unknown): never {\n if (error instanceof CLIError) {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"error\",\n code: error.exitCode,\n message: error.message,\n };\n if (error.suggestions && error.suggestions.length > 0) {\n output.suggestions = error.suggestions;\n }\n if (error.context) {\n if (error.context.operation) output.operation = error.context.operation;\n if (error.context.relatedElements && error.context.relatedElements.length > 0) {\n output.relatedElements = error.context.relatedElements;\n }\n if (error.context.partialProgress) {\n output.partialProgress = error.context.partialProgress;\n }\n }\n console.log(JSON.stringify(output));\n } else {\n console.error(error.format());\n }\n // Don't call process.exit() - rethrow so CLI wrapper can handle telemetry shutdown\n throw error;\n } else if (error instanceof Error) {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"error\",\n code: ErrorCategory.USER,\n message: error.message,\n };\n if (process.env.DEBUG && error.stack) {\n output.stack = error.stack;\n }\n console.log(JSON.stringify(output));\n } else {\n console.error(ansis.red(`Error: ${error.message}`));\n if (process.env.DEBUG) {\n console.error(ansis.dim(error.stack));\n }\n }\n // Rethrow to allow CLI wrapper to handle shutdown\n throw error;\n } else {\n if (isJson()) {\n const output: Record<string, unknown> = {\n status: \"error\",\n code: ErrorCategory.USER,\n message: \"An unexpected error occurred\",\n };\n if (process.env.DEBUG) {\n output.details = String(error);\n }\n console.log(JSON.stringify(output));\n } else {\n console.error(ansis.red(\"An unexpected error occurred\"));\n if (process.env.DEBUG) {\n console.error(ansis.dim(String(error)));\n }\n }\n // Create proper error to throw\n throw new Error(String(error));\n }\n}\n\nexport function handleWarning(message: string, suggestions?: string[]): void {\n if (isJson()) {\n // In JSON mode, emit warnings to stderr as structured JSON\n const output: Record<string, unknown> = {\n level: \"warning\",\n message,\n };\n if (suggestions && suggestions.length > 0) {\n output.suggestions = suggestions;\n }\n console.error(JSON.stringify(output));\n } else {\n const lines: string[] = [];\n lines.push(ansis.yellow(`Warning: ${message}`));\n if (suggestions && suggestions.length > 0) {\n lines.push(\"\");\n lines.push(ansis.dim(\"Suggestions:\"));\n for (const suggestion of suggestions) {\n lines.push(ansis.dim(` \u2022 ${suggestion}`));\n }\n }\n console.warn(lines.join(\"\\n\"));\n }\n}\n\nexport function handleSuccess(message: string, details?: Record<string, unknown>, options?: { verbose?: boolean }): void {\n if (isJson()) {\n const output: Record<string, unknown> = {\n ...details,\n status: \"ok\",\n };\n console.log(JSON.stringify(output));\n } else {\n console.log(ansis.green(`\u2713 ${message}`));\n // Only show details in non-JSON mode if verbose flag is set or details include changesetStatus/status\n if (details && (options?.verbose || details.changesetStatus)) {\n // Convert camelCase keys to PascalCase for display\n for (const [key, value] of Object.entries(details)) {\n const displayKey = keyToDisplayName(key);\n console.log(ansis.dim(` ${displayKey}: ${value}`));\n }\n }\n }\n}\n\nfunction keyToDisplayName(key: string): string {\n // Convert camelCase and snake_case to PascalCase/Title Case\n return key\n .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase to words\n .replace(/_/g, ' ') // snake_case to words\n .split(' ')\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\nexport function handleInfo(message: string, details?: Record<string, string>): void {\n if (isJson()) {\n const output: Record<string, unknown> = {\n level: \"info\",\n message,\n };\n if (details) {\n output.details = details;\n }\n console.error(JSON.stringify(output));\n } else {\n console.log(message);\n if (details) {\n for (const [key, value] of Object.entries(details)) {\n console.log(ansis.dim(` ${key}: ${value}`));\n }\n }\n }\n}\n\n/**\n * Utility to extract common valid options for better error suggestions\n */\nexport function formatValidOptions(options: string[], heading: string = \"Valid options\"): string {\n if (options.length === 0) return \"\";\n if (options.length === 1) return `${heading}: ${options[0]}`;\n return `${heading}: ${options.slice(0, -1).join(\", \")} or ${options[options.length - 1]}`;\n}\n\n/**\n * Categorize errors based on type, using stable error.code for system errors\n * Returns the appropriate ErrorCategory for a given error\n */\nexport function categorizeError(error: unknown): ErrorCategory {\n if (error instanceof CLIError) {\n return error.exitCode;\n }\n\n // Check for system errors using stable error.code\n if (error instanceof Error && \"code\" in error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (typeof code === \"string\") {\n // File not found errors should map to NOT_FOUND category\n if (code === \"ENOENT\") {\n return ErrorCategory.NOT_FOUND;\n }\n // Other system-level errors\n const SYSTEM_ERROR_CODES = new Set([\"EACCES\", \"ENOSPC\", \"EIO\", \"EROFS\", \"EPERM\"]);\n if (SYSTEM_ERROR_CODES.has(code)) {\n return ErrorCategory.SYSTEM;\n }\n }\n }\n\n // Default to USER for other errors\n return ErrorCategory.USER;\n}\n\n/**\n * Extract error message from any error type\n * Handles Error instances, strings, and unknown types consistently\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n}\n\n/**\n * Simple fuzzy match to find similar strings (for typo suggestions)\n */\nexport function findSimilar(input: string, options: string[], maxDistance: number = 2): string[] {\n return options\n .map((option) => ({\n option,\n distance: levenshteinDistance(input.toLowerCase(), option.toLowerCase()),\n }))\n .filter(({ distance }) => distance <= maxDistance)\n .sort((a, b) => a.distance - b.distance)\n .slice(0, 3)\n .map(({ option }) => option);\n}\n\n/**\n * Calculate Levenshtein distance for typo detection\n */\nfunction levenshteinDistance(a: string, b: string): number {\n const matrix: number[][] = [];\n\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n\n for (let j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n matrix[i][j] = Math.min(\n matrix[i - 1][j - 1] + 1,\n matrix[i][j - 1] + 1,\n matrix[i - 1][j] + 1\n );\n }\n }\n }\n\n return matrix[b.length][a.length];\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["ErrorCategory"]
7
7
  }
@@ -1,5 +1,6 @@
1
1
  import { fileExists } from "./file-io.js";
2
2
  import { join, dirname, resolve } from "path";
3
+ import { stat } from "fs/promises";
3
4
  const MAX_SEARCH_DEPTH = 5, PROJECT_FOLDER_NAME = "documentation-robotics", SPEC_FOLDER_NAME = ".dr", FARM_FILE_NAME = "farm.yaml";
4
5
  async function findProjectRoot(startPath) {
5
6
  let currentPath = startPath || process.cwd();
@@ -56,12 +57,36 @@ Ensure DR_FARM_PATH points to a directory containing a farm.yaml file.`);
56
57
  }
57
58
  return null;
58
59
  }
60
+ const MODEL_LAYOUT_CANDIDATES = [
61
+ { layout: "standard", segments: [PROJECT_FOLDER_NAME, "model"] },
62
+ { layout: "legacy", segments: ["model"] },
63
+ { layout: "detached", segments: [] }
64
+ ];
65
+ async function resolveModelDirectory(basePath, options = {}) {
66
+ const resolvedBase = resolve(basePath);
67
+ for (const candidate of MODEL_LAYOUT_CANDIDATES) {
68
+ const modelDir = join(resolvedBase, ...candidate.segments), stats = await statIfExists(modelDir);
69
+ if (stats && !(options.requireManifest && (!stats.isDirectory() || !await statIfExists(join(modelDir, "manifest.yaml")))))
70
+ return { modelDir, layout: candidate.layout, isDirectory: stats.isDirectory() };
71
+ }
72
+ return null;
73
+ }
74
+ async function statIfExists(target) {
75
+ try {
76
+ return await stat(target);
77
+ } catch (err) {
78
+ if (err?.code === "ENOENT")
79
+ return;
80
+ throw err;
81
+ }
82
+ }
59
83
  export {
60
84
  findFarmRoot,
61
85
  findProjectRoot,
62
86
  getDocumentationRobotsPath,
63
87
  getModelPath,
64
88
  getSpecReferencePath,
65
- isInDRProject
89
+ isInDRProject,
90
+ resolveModelDirectory
66
91
  };
67
92
  //# sourceMappingURL=project-paths.js.map
@@ -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, 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;",
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\";\nimport { stat } from \"fs/promises\";\nimport type { Stats } from \"fs\";\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\n/**\n * Layouts under which a model directory can be found below a base path.\n * - `standard`: `{base}/documentation-robotics/model/` (a project that self-hosts its model)\n * - `legacy`: `{base}/model/`\n * - `detached`: `{base}` itself holds `manifest.yaml` and the layer folders\n */\nexport type ModelLayout = \"standard\" | \"legacy\" | \"detached\";\n\nexport interface ResolvedModelDirectory {\n /** Absolute path of the directory that holds the model (manifest.yaml and layer folders) */\n modelDir: string;\n layout: ModelLayout;\n /** False when something that is not a directory sits at the resolved path */\n isDirectory: boolean;\n}\n\nconst MODEL_LAYOUT_CANDIDATES: ReadonlyArray<{ layout: ModelLayout; segments: string[] }> = [\n { layout: \"standard\", segments: [PROJECT_FOLDER_NAME, \"model\"] },\n { layout: \"legacy\", segments: [\"model\"] },\n { layout: \"detached\", segments: [] },\n];\n\n/**\n * Locate a model directory under `basePath`, trying the standard, legacy and detached\n * layouts in that order. Shared by the composed reference validator (resolving external\n * models) and `dr farm add` (adopting a codebase's self-hosted model).\n *\n * Only a missing path moves the search on to the next candidate; permission and I/O\n * errors propagate so callers never mistake them for \"no model here\".\n *\n * @param basePath - Project root, model folder or any path to probe\n * @param options.requireManifest - When true, a candidate counts only if it is a directory\n * that contains `manifest.yaml`; otherwise the first existing path wins (even a file, so\n * the caller can report it)\n * @returns The resolved directory, or null when none of the candidates exists\n */\nexport async function resolveModelDirectory(\n basePath: string,\n options: { requireManifest?: boolean } = {}\n): Promise<ResolvedModelDirectory | null> {\n const resolvedBase = resolve(basePath);\n\n for (const candidate of MODEL_LAYOUT_CANDIDATES) {\n const modelDir = join(resolvedBase, ...candidate.segments);\n const stats = await statIfExists(modelDir);\n if (!stats) {\n continue;\n }\n\n if (options.requireManifest) {\n if (!stats.isDirectory() || !(await statIfExists(join(modelDir, \"manifest.yaml\")))) {\n continue;\n }\n }\n\n return { modelDir, layout: candidate.layout, isDirectory: stats.isDirectory() };\n }\n\n return null;\n}\n\nasync function statIfExists(target: string): Promise<Stats | undefined> {\n try {\n return await stat(target);\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n}\n"],
5
+ "mappings": "AAiBA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,SAAS,eAAe;AACvC,SAAS,YAAY;AAGrB,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;AAkBA,MAAM,0BAAsF;EAC1F,EAAE,QAAQ,YAAY,UAAU,CAAC,qBAAqB,OAAO,EAAC;EAC9D,EAAE,QAAQ,UAAU,UAAU,CAAC,OAAO,EAAC;EACvC,EAAE,QAAQ,YAAY,UAAU,CAAA,EAAE;;AAiBpC,eAAsB,sBACpB,UACA,UAAyC,CAAA,GAAE;AAE3C,QAAM,eAAe,QAAQ,QAAQ;AAErC,aAAW,aAAa,yBAAyB;AAC/C,UAAM,WAAW,KAAK,cAAc,GAAG,UAAU,QAAQ,GACnD,QAAQ,MAAM,aAAa,QAAQ;AACzC,QAAK,SAID,UAAQ,oBACN,CAAC,MAAM,YAAW,KAAM,CAAE,MAAM,aAAa,KAAK,UAAU,eAAe,CAAC;AAKlF,aAAO,EAAE,UAAU,QAAQ,UAAU,QAAQ,aAAa,MAAM,YAAW,EAAE;EAC/E;AAEA,SAAO;AACT;AAEA,eAAe,aAAa,QAAc;AACxC,MAAI;AACF,WAAO,MAAM,KAAK,MAAM;EAC1B,SAAS,KAAK;AACZ,QAAK,KAA+B,SAAS;AAC3C;AAEF,UAAM;EACR;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,38 +1,40 @@
1
1
  import { CLIError } from "./errors.js";
2
- function validateSourceReferenceOptions(options) {
2
+ import { optionExample, optionName } from "./error-hints.js";
3
+ function validateSourceReferenceOptions(options, surface = "cli") {
4
+ const name = (option) => optionName(surface, option), example = (option, value) => optionExample(surface, option, value);
3
5
  if (!(options.sourceFile || options.sourceSymbol || options.sourceProvenance || options.sourceRepoRemote || options.sourceRepoCommit)) {
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.");
6
+ process.stderr.isTTY && console.warn(`Warning: no source provenance specified. Add ${name("sourceFile")} and ${name("sourceProvenance")} to maintain traceability for drift detection and /dr-sync workflows.`);
5
7
  return;
6
8
  }
7
9
  if (options.sourceSymbol && !options.sourceFile)
8
- throw new CLIError("--source-symbol requires --source-file", 1, [
10
+ throw new CLIError(`${name("sourceSymbol")} requires ${name("sourceFile")}`, 1, [
9
11
  "Provide the source file that contains the symbol",
10
- 'Example: --source-file "src/auth.ts" --source-symbol "validateToken"'
12
+ `Example: ${example("sourceFile", "src/auth.ts")} ${example("sourceSymbol", "validateToken")}`
11
13
  ]);
12
14
  if (!options.sourceProvenance)
13
- throw new CLIError("--source-provenance is required when specifying source reference", 1, [
15
+ throw new CLIError(`${name("sourceProvenance")} is required when specifying source reference`, 1, [
14
16
  "Specify provenance type: extracted, manual, inferred, or generated"
15
17
  ]);
16
18
  const fileOptionalProvenances = ["inferred", "generated"];
17
19
  if (!options.sourceFile && !fileOptionalProvenances.includes(options.sourceProvenance))
18
- throw new CLIError("--source-file is required when specifying source reference", 1, [
20
+ throw new CLIError(`${name("sourceFile")} is required when specifying source reference`, 1, [
19
21
  "Provide source file path relative to repository root",
20
- 'Example: --source-file "src/services/auth.ts"',
21
- "Note: --source-file is optional when --source-provenance is 'inferred' or 'generated'"
22
+ `Example: ${example("sourceFile", "src/services/auth.ts")}`,
23
+ `Note: ${name("sourceFile")} is optional when ${name("sourceProvenance")} is 'inferred' or 'generated'`
22
24
  ]);
23
25
  const validProvenance = ["extracted", "manual", "inferred", "generated"];
24
26
  if (!validProvenance.includes(options.sourceProvenance))
25
- throw new CLIError(`Invalid --source-provenance value: "${options.sourceProvenance}"`, 1, [
27
+ throw new CLIError(`Invalid ${name("sourceProvenance")} value: "${options.sourceProvenance}"`, 1, [
26
28
  `Must be one of: ${validProvenance.join(", ")}`
27
29
  ]);
28
30
  if (options.sourceRepoCommit && !/^[0-9a-f]{40}$/.test(options.sourceRepoCommit))
29
- throw new CLIError(`Invalid --source-repo-commit value: "${options.sourceRepoCommit}"`, 1, [
31
+ throw new CLIError(`Invalid ${name("sourceRepoCommit")} value: "${options.sourceRepoCommit}"`, 1, [
30
32
  "Must be exactly 40 hexadecimal characters (full Git commit SHA)"
31
33
  ]);
32
34
  if (options.sourceRepoRemote && !options.sourceRepoCommit)
33
- throw new CLIError("--source-repo-commit is required when --source-repo-remote is provided", 1, ["Provide both remote URL and commit SHA for repository context"]);
35
+ throw new CLIError(`${name("sourceRepoCommit")} is required when ${name("sourceRepoRemote")} is provided`, 1, ["Provide both remote URL and commit SHA for repository context"]);
34
36
  if (options.sourceRepoCommit && !options.sourceRepoRemote)
35
- throw new CLIError("--source-repo-remote is required when --source-repo-commit is provided", 1, ["Provide both remote URL and commit SHA for repository context"]);
37
+ throw new CLIError(`${name("sourceRepoRemote")} is required when ${name("sourceRepoCommit")} is provided`, 1, ["Provide both remote URL and commit SHA for repository context"]);
36
38
  }
37
39
  function buildSourceReference(options) {
38
40
  if (!options.sourceProvenance)
@@ -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 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;",
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\";\nimport { optionExample, optionName, type HintOption, type HintSurface } from \"./error-hints.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(\n options: SourceReferenceOptions,\n surface: HintSurface = \"cli\"\n): void {\n const name = (option: HintOption) => optionName(surface, option);\n const example = (option: HintOption, value: string) => optionExample(surface, option, value);\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 ${name(\"sourceFile\")} and ${name(\"sourceProvenance\")} 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(`${name(\"sourceSymbol\")} requires ${name(\"sourceFile\")}`, 1, [\n \"Provide the source file that contains the symbol\",\n `Example: ${example(\"sourceFile\", \"src/auth.ts\")} ${example(\"sourceSymbol\", \"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(`${name(\"sourceProvenance\")} 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(`${name(\"sourceFile\")} is required when specifying source reference`, 1, [\n \"Provide source file path relative to repository root\",\n `Example: ${example(\"sourceFile\", \"src/services/auth.ts\")}`,\n `Note: ${name(\"sourceFile\")} is optional when ${name(\"sourceProvenance\")} 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 ${name(\"sourceProvenance\")} 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 ${name(\"sourceRepoCommit\")} 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 `${name(\"sourceRepoCommit\")} is required when ${name(\"sourceRepoRemote\")} 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 `${name(\"sourceRepoRemote\")} is required when ${name(\"sourceRepoCommit\")} 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;AACzB,SAAS,eAAe,kBAAqD;AAavE,SAAU,+BACd,SACA,UAAuB,OAAK;AAE5B,QAAM,OAAO,CAAC,WAAuB,WAAW,SAAS,MAAM,GACzD,UAAU,CAAC,QAAoB,UAAkB,cAAc,SAAS,QAAQ,KAAK;AAQ3F,MAAI,EANF,QAAQ,cACR,QAAQ,gBACR,QAAQ,oBACR,QAAQ,oBACR,QAAQ,mBAEa;AACrB,IAAI,QAAQ,OAAO,SACjB,QAAQ,KACN,gDAAgD,KAAK,YAAY,CAAC,QAAQ,KAAK,kBAAkB,CAAC,uEAAuE;AAG7K;EACF;AAGA,MAAI,QAAQ,gBAAgB,CAAC,QAAQ;AACnC,UAAM,IAAI,SAAS,GAAG,KAAK,cAAc,CAAC,aAAa,KAAK,YAAY,CAAC,IAAI,GAAG;MAC9E;MACA,YAAY,QAAQ,cAAc,aAAa,CAAC,IAAI,QAAQ,gBAAgB,eAAe,CAAC;KAC7F;AAIH,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,SAAS,GAAG,KAAK,kBAAkB,CAAC,iDAAiD,GAAG;MAChG;KACD;AAIH,QAAM,0BAA0B,CAAC,YAAY,WAAW;AACxD,MAAI,CAAC,QAAQ,cAAc,CAAC,wBAAwB,SAAS,QAAQ,gBAAgB;AACnF,UAAM,IAAI,SAAS,GAAG,KAAK,YAAY,CAAC,iDAAiD,GAAG;MAC1F;MACA,YAAY,QAAQ,cAAc,sBAAsB,CAAC;MACzD,SAAS,KAAK,YAAY,CAAC,qBAAqB,KAAK,kBAAkB,CAAC;KACzE;AAIH,QAAM,kBAAkB,CAAC,aAAa,UAAU,YAAY,WAAW;AACvE,MAAI,CAAC,gBAAgB,SAAS,QAAQ,gBAAgB;AACpD,UAAM,IAAI,SAAS,WAAW,KAAK,kBAAkB,CAAC,YAAY,QAAQ,gBAAgB,KAAK,GAAG;MAChG,mBAAmB,gBAAgB,KAAK,IAAI,CAAC;KAC9C;AAIH,MAAI,QAAQ,oBAAoB,CAAC,iBAAiB,KAAK,QAAQ,gBAAgB;AAC7E,UAAM,IAAI,SAAS,WAAW,KAAK,kBAAkB,CAAC,YAAY,QAAQ,gBAAgB,KAAK,GAAG;MAChG;KACD;AAIH,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,GAAG,KAAK,kBAAkB,CAAC,qBAAqB,KAAK,kBAAkB,CAAC,gBACxE,GACA,CAAC,+DAA+D,CAAC;AAIrE,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,GAAG,KAAK,kBAAkB,CAAC,qBAAqB,KAAK,kBAAkB,CAAC,gBACxE,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,7 +1,7 @@
1
1
  import { readJSON, fileExists } from "./file-io.js";
2
2
  import { join } from "path";
3
3
  import { createRequire } from "node:module";
4
- const BUNDLED_SPEC_VERSION = "0.10.0";
4
+ const BUNDLED_SPEC_VERSION = "0.11.0";
5
5
  function resolveDevVersion() {
6
6
  try {
7
7
  return createRequire(import.meta.url)("../../package.json").version ?? "0.0.0";
@@ -10,7 +10,7 @@ function resolveDevVersion() {
10
10
  }
11
11
  }
12
12
  function getCliVersion() {
13
- return "0.2.0";
13
+ return "0.3.0";
14
14
  }
15
15
  function getCliBundledSpecVersion() {
16
16
  return BUNDLED_SPEC_VERSION;