recce-nightly 0.61.0.20250414__tar.gz → 0.62.0.20250416__tar.gz

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.

Potentially problematic release.


This version of recce-nightly might be problematic. Click here for more details.

Files changed (147) hide show
  1. {recce_nightly-0.61.0.20250414/recce_nightly.egg-info → recce_nightly-0.62.0.20250416}/PKG-INFO +1 -1
  2. recce_nightly-0.62.0.20250416/recce/VERSION +1 -0
  3. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/adapter/dbt_adapter/__init__.py +79 -10
  4. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/cli.py +23 -11
  5. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/404.html +1 -1
  6. recce_nightly-0.61.0.20250414/recce/data/_next/static/chunks/591-4322fa14221295de.js → recce_nightly-0.62.0.20250416/recce/data/_next/static/chunks/500-e51c92a025a51234.js +2 -2
  7. recce_nightly-0.62.0.20250416/recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +1 -0
  8. recce_nightly-0.62.0.20250416/recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +1 -0
  9. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/index.html +2 -2
  10. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/index.txt +2 -2
  11. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/models/types.py +10 -2
  12. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/server.py +40 -1
  13. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/state.py +6 -8
  14. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/breaking.py +97 -128
  15. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/recce_cloud.py +2 -0
  16. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416/recce_nightly.egg-info}/PKG-INFO +1 -1
  17. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce_nightly.egg-info/SOURCES.txt +5 -5
  18. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_core.py +1 -1
  19. recce_nightly-0.61.0.20250414/recce/VERSION +0 -1
  20. recce_nightly-0.61.0.20250414/recce/data/_next/static/chunks/9746af58-1afd0dcd70716610.js +0 -1
  21. recce_nightly-0.61.0.20250414/recce/data/_next/static/chunks/app/page-2628bcceaa0ae00e.js +0 -1
  22. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/LICENSE +0 -0
  23. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/README.md +0 -0
  24. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/__init__.py +0 -0
  25. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/adapter/__init__.py +0 -0
  26. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/adapter/base.py +0 -0
  27. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/adapter/dbt_adapter/dbt_version.py +0 -0
  28. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/adapter/sqlmesh_adapter.py +0 -0
  29. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/apis/__init__.py +0 -0
  30. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/apis/check_api.py +0 -0
  31. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/apis/check_func.py +0 -0
  32. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/apis/run_api.py +0 -0
  33. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/apis/run_func.py +0 -0
  34. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/artifact.py +0 -0
  35. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/config.py +0 -0
  36. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/core.py +0 -0
  37. {recce_nightly-0.61.0.20250414/recce/data/_next/static/ZJiNwGFIYljXBmgYs6I5G → recce_nightly-0.62.0.20250416/recce/data/_next/static/Kl6NGUBajFGsWgGKiDdQ2}/_buildManifest.js +0 -0
  38. {recce_nightly-0.61.0.20250414/recce/data/_next/static/ZJiNwGFIYljXBmgYs6I5G → recce_nightly-0.62.0.20250416/recce/data/_next/static/Kl6NGUBajFGsWgGKiDdQ2}/_ssgManifest.js +0 -0
  39. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -0
  40. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -0
  41. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -0
  42. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -0
  43. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -0
  44. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -0
  45. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -0
  46. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -0
  47. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -0
  48. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -0
  49. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -0
  50. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -0
  51. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -0
  52. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -0
  53. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -0
  54. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -0
  55. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -0
  56. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -0
  57. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -0
  58. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -0
  59. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -0
  60. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -0
  61. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -0
  62. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -0
  63. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -0
  64. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  65. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -0
  66. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -0
  67. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
  68. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
  69. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
  70. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
  71. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
  72. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
  73. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
  74. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
  75. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
  76. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
  77. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/favicon.ico +0 -0
  78. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/imgs/feedback/thumbs-down.png +0 -0
  79. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/imgs/feedback/thumbs-up.png +0 -0
  80. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/data/logo/recce-logo-white.png +0 -0
  81. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/diff.py +0 -0
  82. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/event/CONFIG +0 -0
  83. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/event/SENTRY_DNS +0 -0
  84. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/event/__init__.py +0 -0
  85. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/event/collector.py +0 -0
  86. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/event/track.py +0 -0
  87. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/exceptions.py +0 -0
  88. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/git.py +0 -0
  89. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/github.py +0 -0
  90. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/models/__init__.py +0 -0
  91. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/models/check.py +0 -0
  92. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/models/run.py +0 -0
  93. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/pull_request.py +0 -0
  94. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/run.py +0 -0
  95. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/summary.py +0 -0
  96. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/__init__.py +0 -0
  97. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/core.py +0 -0
  98. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/dataframe.py +0 -0
  99. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/histogram.py +0 -0
  100. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/lineage.py +0 -0
  101. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/profile.py +0 -0
  102. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/query.py +0 -0
  103. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/rowcount.py +0 -0
  104. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/schema.py +0 -0
  105. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/top_k.py +0 -0
  106. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/tasks/valuediff.py +0 -0
  107. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/__init__.py +0 -0
  108. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/cache.py +0 -0
  109. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/cll.py +0 -0
  110. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/io.py +0 -0
  111. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/lineage.py +0 -0
  112. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/logger.py +0 -0
  113. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/pydantic_model.py +0 -0
  114. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/util/singleton.py +0 -0
  115. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce/yaml/__init__.py +0 -0
  116. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce_nightly.egg-info/dependency_links.txt +0 -0
  117. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce_nightly.egg-info/entry_points.txt +0 -0
  118. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce_nightly.egg-info/requires.txt +0 -0
  119. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/recce_nightly.egg-info/top_level.txt +0 -0
  120. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/setup.cfg +0 -0
  121. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/setup.py +0 -0
  122. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/__init__.py +0 -0
  123. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/__init__.py +0 -0
  124. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/dbt_adapter/__init__.py +0 -0
  125. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/dbt_adapter/conftest.py +0 -0
  126. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/dbt_adapter/dbt_test_helper.py +0 -0
  127. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -0
  128. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/dbt_adapter/test_dbt_cll.py +0 -0
  129. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/adapter/dbt_adapter/test_selector.py +0 -0
  130. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/__init__.py +0 -0
  131. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/conftest.py +0 -0
  132. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_histogram.py +0 -0
  133. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_lineage.py +0 -0
  134. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_preset_checks.py +0 -0
  135. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_profile.py +0 -0
  136. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_query.py +0 -0
  137. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_row_count.py +0 -0
  138. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_schema.py +0 -0
  139. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_top_k.py +0 -0
  140. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/tasks/test_valuediff.py +0 -0
  141. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_cli.py +0 -0
  142. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_config.py +0 -0
  143. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_dbt.py +0 -0
  144. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_pull_request.py +0 -0
  145. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_server.py +0 -0
  146. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_state.py +0 -0
  147. {recce_nightly-0.61.0.20250414 → recce_nightly-0.62.0.20250416}/tests/test_summary.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: recce-nightly
3
- Version: 0.61.0.20250414
3
+ Version: 0.62.0.20250416
4
4
  Summary: Environment diff tool for dbt
5
5
  Home-page: https://github.com/InfuseAI/recce
6
6
  Author: InfuseAI Dev Team
@@ -0,0 +1 @@
1
+ 0.62.0.20250416
@@ -13,8 +13,9 @@ from typing import Callable, Dict, List, Optional, Tuple, Iterator, Any, Set, Un
13
13
  from recce.event import log_performance
14
14
  from recce.exceptions import RecceException
15
15
  from recce.util.cll import cll, CLLPerformanceTracking
16
- from ...tasks.profile import ProfileTask
17
16
  from recce.util.lineage import find_upstream, find_downstream
17
+ from ...tasks.profile import ProfileTask
18
+ from ...util.breaking import parse_change_category, BreakingPerformanceTracking
18
19
 
19
20
  try:
20
21
  import agate
@@ -31,7 +32,7 @@ from recce.adapter.base import BaseAdapter
31
32
  from recce.state import ArtifactsRoot
32
33
  from .dbt_version import DbtVersion
33
34
  from ...models import RunType
34
- from ...models.types import LineageDiff, NodeDiff
35
+ from ...models.types import LineageDiff, NodeDiff, NodeChange
35
36
  from ...tasks import Task, QueryTask, QueryBaseTask, QueryDiffTask, ValueDiffTask, ValueDiffDetailTask, ProfileDiffTask, \
36
37
  RowCountTask, RowCountDiffTask, TopKDiffTask, HistogramDiffTask
37
38
 
@@ -741,6 +742,32 @@ class DbtAdapter(BaseAdapter):
741
742
  *current.get('nodes', {}).keys()
742
743
  }
743
744
 
745
+ # Start to diff
746
+ perf_tracking = BreakingPerformanceTracking()
747
+ perf_tracking.start_lineage_diff()
748
+
749
+ base_manifest = as_manifest(self.get_manifest(True))
750
+ curr_manifest = as_manifest(self.get_manifest(False))
751
+ perf_tracking.record_checkpoint('manifest')
752
+
753
+ def ref_func(*args):
754
+ if len(args) == 1:
755
+ node = args[0]
756
+ elif len(args) > 1:
757
+ node = args[1]
758
+ else:
759
+ return None
760
+ return node
761
+
762
+ def source_func(source_name, table_name):
763
+ source_name = source_name.replace('-', '_')
764
+ return f"__{source_name}__{table_name}"
765
+
766
+ jinja_context = dict(
767
+ ref=ref_func,
768
+ source=source_func,
769
+ )
770
+
744
771
  # for each node, compare the base and current lineage
745
772
  diff = {}
746
773
  for key in keys:
@@ -752,23 +779,65 @@ class DbtAdapter(BaseAdapter):
752
779
  if base_checksum is None or curr_checksum is None or base_checksum == curr_checksum:
753
780
  continue
754
781
 
755
- change_category = 'breaking'
756
782
  if curr_node.get('resource_type') == 'model':
757
783
  try:
758
- from recce.util.breaking import is_breaking_change
759
- base_sql = self.generate_sql(base_node.get('raw_code'))
760
- curr_sql = self.generate_sql(curr_node.get('raw_code'))
784
+ perf_tracking.increment_modified_nodes()
785
+
786
+ def _get_schema(lineage):
787
+ schema = {}
788
+ nodes = lineage['nodes']
789
+ parent_list = lineage['parent_map'].get(key, [])
790
+ for parent_id in parent_list:
791
+ parent_node = nodes.get(parent_id)
792
+ if parent_node is None:
793
+ continue
794
+ columns = parent_node.get('columns') or {}
795
+ name = parent_node.get('name')
796
+ if parent_node.get('resource_type') == 'source':
797
+ parts = parent_id.split('.')
798
+ source = parts[2]
799
+ table = parts[3]
800
+ source = source.replace('-', '_')
801
+ name = f"__{source}__{table}"
802
+ schema[name] = {
803
+ name: column.get('type') for name, column in columns.items()
804
+ }
805
+ return schema
806
+
807
+ base_sql = self.generate_sql(
808
+ base_node.get('raw_code'),
809
+ context=jinja_context,
810
+ provided_manifest=base_manifest
811
+ )
812
+ curr_sql = self.generate_sql(
813
+ curr_node.get('raw_code'),
814
+ context=jinja_context,
815
+ provided_manifest=curr_manifest
816
+ )
817
+ base_schema = _get_schema(base)
818
+ curr_schema = _get_schema(current)
761
819
  dialect = self.adapter.connections.TYPE
762
- if not is_breaking_change(base_sql, curr_sql, dialect=dialect):
763
- change_category = 'non-breaking'
820
+
821
+ change = parse_change_category(
822
+ base_sql,
823
+ curr_sql,
824
+ old_schema=base_schema,
825
+ new_schema=curr_schema,
826
+ dialect=dialect,
827
+ perf_tracking=perf_tracking,
828
+ )
764
829
  except Exception:
765
- pass
830
+ change = NodeChange(category='unknown')
766
831
 
767
- diff[key] = NodeDiff(change_status='modified', change_category=change_category)
832
+ diff[key] = NodeDiff(change_status='modified', change=change)
768
833
  elif base_node:
769
834
  diff[key] = NodeDiff(change_status='removed')
770
835
  elif curr_node:
771
836
  diff[key] = NodeDiff(change_status='added')
837
+
838
+ perf_tracking.end_lineage_diff()
839
+ log_performance('model lineage diff', perf_tracking.to_dict())
840
+
772
841
  return LineageDiff(
773
842
  base=base,
774
843
  current=current,
@@ -201,6 +201,8 @@ def diff(sql, primary_keys: List[str] = None, keep_shape: bool = False, keep_equ
201
201
  @click.option('--host', default='localhost', show_default=True, help='The host to bind to.')
202
202
  @click.option('--port', default=8000, show_default=True, help='The port to bind to.', type=int)
203
203
  @click.option('--review', is_flag=True, help='Open the state file in the review mode.')
204
+ @click.option('--api-token', help='The token used by Recce Cloud API.', type=click.STRING,
205
+ envvar='RECCE_API_TOKEN')
204
206
  @add_options(dbt_related_options)
205
207
  @add_options(sqlmesh_related_options)
206
208
  @add_options(recce_options)
@@ -257,6 +259,10 @@ def server(host, port, state_file=None, **kwargs):
257
259
  cloud_onboarding_state = get_recce_cloud_onboarding_state(kwargs.get('cloud_token'))
258
260
  flag['show_onboarding_guide'] = False if cloud_onboarding_state == 'completed' else True
259
261
 
262
+ auth_options = {}
263
+ api_token = kwargs.get('api_token') if kwargs.get('api_token') else get_recce_api_token()
264
+ auth_options['api_token'] = api_token
265
+
260
266
  # Check Single Environment Onboarding Mode if the review mode is False
261
267
  if not os.path.isdir(kwargs.get('target_base_path')) and is_review is False:
262
268
  # Mark as single env onboarding mode if user provides the target-path only
@@ -293,7 +299,7 @@ def server(host, port, state_file=None, **kwargs):
293
299
  console.print(f"[[red]Error[/red]] {message}")
294
300
  exit(1)
295
301
 
296
- state = AppState(state_loader=state_loader, kwargs=kwargs, flag=flag)
302
+ state = AppState(state_loader=state_loader, kwargs=kwargs, flag=flag, auth_options=auth_options)
297
303
  app.state = state
298
304
 
299
305
  uvicorn.run(app, host=host, port=port, lifespan='on')
@@ -752,7 +758,7 @@ def artifact(**kwargs):
752
758
  return recce_ci_artifact(**kwargs)
753
759
 
754
760
 
755
- @cli.command(cls=TrackCommand)
761
+ @cli.command(cls=TrackCommand, hidden=True)
756
762
  @click.argument('state_file', type=click.Path(exists=True))
757
763
  @click.option('--api-token', help='The token used by Recce Cloud API.', type=click.STRING,
758
764
  envvar='RECCE_API_TOKEN')
@@ -763,15 +769,16 @@ def share(state_file, **kwargs):
763
769
  """
764
770
  from rich.console import Console
765
771
 
766
- handle_debug_flag(**kwargs)
767
- cloud_options = {
768
- 'api_token': kwargs.get('api_token'),
769
- }
770
-
771
772
  console = Console()
773
+ handle_debug_flag(**kwargs)
774
+ cloud_options = None
772
775
 
773
776
  # read or input the api token
774
- api_token = cloud_options.get('api_token') if cloud_options.get('api_token') else get_recce_api_token()
777
+ api_token = kwargs.get('api_token') if kwargs.get('api_token') else get_recce_api_token()
778
+ if api_token is None:
779
+ console.print("Recce Share is coming soon — stay tuned!")
780
+ exit(1)
781
+
775
782
  if api_token is None:
776
783
  console.print("Please login Recce Cloud and copy the API token from the setting page.\n"
777
784
  f"{RECCE_CLOUD_API_HOST}/settings#tokens\n"
@@ -779,7 +786,7 @@ def share(state_file, **kwargs):
779
786
  api_token = click.prompt('Your Recce API token', type=str, hide_input=True, show_default=False)
780
787
  update_user_profile({'api_token': api_token})
781
788
 
782
- cloud_options['api_token'] = api_token
789
+ auth_options = {'api_token': api_token}
783
790
 
784
791
  # load local state
785
792
  state_loader = create_state_loader(review_mode=True, cloud_mode=False, state_file=state_file,
@@ -792,7 +799,7 @@ def share(state_file, **kwargs):
792
799
  exit(1)
793
800
 
794
801
  # check if state exists in cloud
795
- state_manager = RecceShareStateManager(cloud_options)
802
+ state_manager = RecceShareStateManager(auth_options)
796
803
  if not state_manager.verify():
797
804
  error, hint = state_manager.error_and_hint
798
805
  console.print(f"[[red]Error[/red]] {error}")
@@ -803,7 +810,12 @@ def share(state_file, **kwargs):
803
810
  state_file_name = os.path.basename(state_file)
804
811
 
805
812
  try:
806
- console.print(f'Shared Link: {state_manager.share_state(state_file_name, state_loader.state)}')
813
+ response = state_manager.share_state(state_file_name, state_loader.state)
814
+ if response.get('status') == 'error':
815
+ console.print("[[red]Error[/red]] Failed to share the state.\n"
816
+ f"Reason: {response.get('message')}")
817
+ else:
818
+ console.print(f"Shared Link: {response.get('share_url')}")
807
819
  except RecceCloudException as e:
808
820
  console.print(f"[[red]Error[/red]] {e}")
809
821
  console.print(f"Reason: {e.reason}")
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-567d72f0bc0820d5.js"/><script src="/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js" async=""></script><script src="/_next/static/chunks/700-3b65fc3666820d00.js" async=""></script><script src="/_next/static/chunks/main-app-0225a2255968e566.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>recce</title><meta name="description" content="Recce: Data validation toolkit for comprehensive PR review"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script src="/_next/static/chunks/webpack-567d72f0bc0820d5.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:I[37194,[],\"\"]\n3:I[79137,[],\"\"]\n4:I[63846,[],\"\"]\na:I[67160,[],\"\"]\n5:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n6:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n7:{\"display\":\"inline-block\"}\n8:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nb:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L1\",null,{\"buildId\":\"ZJiNwGFIYljXBmgYs6I5G\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L2\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"suppressHydrationWarning\":true,\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$5\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$6\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$8\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$L9\"],\"globalErrorComponent\":\"$a\",\"missingSlots\":\"$Wb\"}]\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"recce\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Recce: Data validation toolkit for comprehensive PR review\"}]]\n2:null\n"])</script></body></html>
1
+ <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-567d72f0bc0820d5.js"/><script src="/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js" async=""></script><script src="/_next/static/chunks/700-3b65fc3666820d00.js" async=""></script><script src="/_next/static/chunks/main-app-0225a2255968e566.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>recce</title><meta name="description" content="Recce: Data validation toolkit for comprehensive PR review"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script src="/_next/static/chunks/webpack-567d72f0bc0820d5.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:I[37194,[],\"\"]\n3:I[79137,[],\"\"]\n4:I[63846,[],\"\"]\na:I[67160,[],\"\"]\n5:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n6:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n7:{\"display\":\"inline-block\"}\n8:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nb:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L1\",null,{\"buildId\":\"Kl6NGUBajFGsWgGKiDdQ2\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L2\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"suppressHydrationWarning\":true,\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$5\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$6\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$8\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$L9\"],\"globalErrorComponent\":\"$a\",\"missingSlots\":\"$Wb\"}]\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"recce\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Recce: Data validation toolkit for comprehensive PR review\"}]]\n2:null\n"])</script></body></html>