recce-nightly 0.61.0.20250414__py3-none-any.whl → 0.62.0.20250416__py3-none-any.whl
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.
- recce/VERSION +1 -1
- recce/adapter/dbt_adapter/__init__.py +79 -10
- recce/cli.py +23 -11
- recce/data/404.html +1 -1
- recce/data/_next/static/chunks/{591-4322fa14221295de.js → 500-e51c92a025a51234.js} +2 -2
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +1 -0
- recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +1 -0
- recce/data/index.html +2 -2
- recce/data/index.txt +2 -2
- recce/models/types.py +10 -2
- recce/server.py +40 -1
- recce/state.py +6 -8
- recce/util/breaking.py +97 -128
- recce/util/recce_cloud.py +2 -0
- {recce_nightly-0.61.0.20250414.dist-info → recce_nightly-0.62.0.20250416.dist-info}/METADATA +1 -1
- {recce_nightly-0.61.0.20250414.dist-info → recce_nightly-0.62.0.20250416.dist-info}/RECORD +23 -23
- tests/test_core.py +1 -1
- recce/data/_next/static/chunks/9746af58-1afd0dcd70716610.js +0 -1
- recce/data/_next/static/chunks/app/page-2628bcceaa0ae00e.js +0 -1
- /recce/data/_next/static/{ZJiNwGFIYljXBmgYs6I5G → Kl6NGUBajFGsWgGKiDdQ2}/_buildManifest.js +0 -0
- /recce/data/_next/static/{ZJiNwGFIYljXBmgYs6I5G → Kl6NGUBajFGsWgGKiDdQ2}/_ssgManifest.js +0 -0
- {recce_nightly-0.61.0.20250414.dist-info → recce_nightly-0.62.0.20250416.dist-info}/WHEEL +0 -0
- {recce_nightly-0.61.0.20250414.dist-info → recce_nightly-0.62.0.20250416.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.61.0.20250414.dist-info → recce_nightly-0.62.0.20250416.dist-info}/licenses/LICENSE +0 -0
- {recce_nightly-0.61.0.20250414.dist-info → recce_nightly-0.62.0.20250416.dist-info}/top_level.txt +0 -0
recce/data/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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="image" href="/logo/recce-logo-white.png"/><link rel="stylesheet" href="/_next/static/css/c9ecb46a4b21c126.css" data-precedence="next"/><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><script src="/_next/static/chunks/e24bf851-0f8cbc99656833e7.js" async=""></script><script src="/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js" async=""></script><script src="/_next/static/chunks/3998a672-eaad84bdd88cc73e.js" async=""></script><script src="/_next/static/chunks/9746af58-1afd0dcd70716610.js" async=""></script><script src="/_next/static/chunks/ce84277d-f42c2c58049cea2d.js" async=""></script><script src="/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js" async=""></script><script src="/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js" async=""></script><script src="/_next/static/chunks/fee69bc6-f17d36c080742e74.js" async=""></script><script src="/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js" async=""></script><script src="/_next/static/chunks/450c323b-1bb5db526e54435a.js" async=""></script><script src="/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js" async=""></script><script src="/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js" async=""></script><script src="/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js" async=""></script><script src="/_next/static/chunks/7f27ae6c-413f6b869a04183a.js" async=""></script><script src="/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js" async=""></script><script src="/_next/static/chunks/c132bf7d-8102037f9ccf372a.js" async=""></script><script src="/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js" async=""></script><script src="/_next/static/chunks/591-4322fa14221295de.js" async=""></script><script src="/_next/static/chunks/app/page-2628bcceaa0ae00e.js" async=""></script><title>recce</title><meta name="description" content="Recce: Data validation toolkit for comprehensive PR review"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="32x32"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><style data-emotion="css-global rh8y69">:host,:root,[data-theme]{--chakra-ring-inset:var(--chakra-empty,/*!*/ /*!*/);--chakra-ring-offset-width:0px;--chakra-ring-offset-color:#fff;--chakra-ring-color:rgba(66, 153, 225, 0.6);--chakra-ring-offset-shadow:0 0 #0000;--chakra-ring-shadow:0 0 #0000;--chakra-space-x-reverse:0;--chakra-space-y-reverse:0;--chakra-colors-transparent:transparent;--chakra-colors-current:currentColor;--chakra-colors-black:#000000;--chakra-colors-white:#FFFFFF;--chakra-colors-whiteAlpha-50:rgba(255, 255, 255, 0.04);--chakra-colors-whiteAlpha-100:rgba(255, 255, 255, 0.06);--chakra-colors-whiteAlpha-200:rgba(255, 255, 255, 0.08);--chakra-colors-whiteAlpha-300:rgba(255, 255, 255, 0.16);--chakra-colors-whiteAlpha-400:rgba(255, 255, 255, 0.24);--chakra-colors-whiteAlpha-500:rgba(255, 255, 255, 0.36);--chakra-colors-whiteAlpha-600:rgba(255, 255, 255, 0.48);--chakra-colors-whiteAlpha-700:rgba(255, 255, 255, 0.64);--chakra-colors-whiteAlpha-800:rgba(255, 255, 255, 0.80);--chakra-colors-whiteAlpha-900:rgba(255, 255, 255, 0.92);--chakra-colors-blackAlpha-50:rgba(0, 0, 0, 0.04);--chakra-colors-blackAlpha-100:rgba(0, 0, 0, 0.06);--chakra-colors-blackAlpha-200:rgba(0, 0, 0, 0.08);--chakra-colors-blackAlpha-300:rgba(0, 0, 0, 0.16);--chakra-colors-blackAlpha-400:rgba(0, 0, 0, 0.24);--chakra-colors-blackAlpha-500:rgba(0, 0, 0, 0.36);--chakra-colors-blackAlpha-600:rgba(0, 0, 0, 0.48);--chakra-colors-blackAlpha-700:rgba(0, 0, 0, 0.64);--chakra-colors-blackAlpha-800:rgba(0, 0, 0, 0.80);--chakra-colors-blackAlpha-900:rgba(0, 0, 0, 0.92);--chakra-colors-gray-50:#F7FAFC;--chakra-colors-gray-100:#EDF2F7;--chakra-colors-gray-200:#E2E8F0;--chakra-colors-gray-300:#CBD5E0;--chakra-colors-gray-400:#A0AEC0;--chakra-colors-gray-500:#718096;--chakra-colors-gray-600:#4A5568;--chakra-colors-gray-700:#2D3748;--chakra-colors-gray-800:#1A202C;--chakra-colors-gray-900:#171923;--chakra-colors-red-50:#FFF5F5;--chakra-colors-red-100:#FED7D7;--chakra-colors-red-200:#FEB2B2;--chakra-colors-red-300:#FC8181;--chakra-colors-red-400:#F56565;--chakra-colors-red-500:#E53E3E;--chakra-colors-red-600:#C53030;--chakra-colors-red-700:#9B2C2C;--chakra-colors-red-800:#822727;--chakra-colors-red-900:#63171B;--chakra-colors-orange-50:#FFFAF0;--chakra-colors-orange-100:#FEEBC8;--chakra-colors-orange-200:#FBD38D;--chakra-colors-orange-300:#F6AD55;--chakra-colors-orange-400:#ED8936;--chakra-colors-orange-500:#DD6B20;--chakra-colors-orange-600:#C05621;--chakra-colors-orange-700:#9C4221;--chakra-colors-orange-800:#7B341E;--chakra-colors-orange-900:#652B19;--chakra-colors-yellow-50:#FFFFF0;--chakra-colors-yellow-100:#FEFCBF;--chakra-colors-yellow-200:#FAF089;--chakra-colors-yellow-300:#F6E05E;--chakra-colors-yellow-400:#ECC94B;--chakra-colors-yellow-500:#D69E2E;--chakra-colors-yellow-600:#B7791F;--chakra-colors-yellow-700:#975A16;--chakra-colors-yellow-800:#744210;--chakra-colors-yellow-900:#5F370E;--chakra-colors-green-50:#F0FFF4;--chakra-colors-green-100:#C6F6D5;--chakra-colors-green-200:#9AE6B4;--chakra-colors-green-300:#68D391;--chakra-colors-green-400:#48BB78;--chakra-colors-green-500:#38A169;--chakra-colors-green-600:#2F855A;--chakra-colors-green-700:#276749;--chakra-colors-green-800:#22543D;--chakra-colors-green-900:#1C4532;--chakra-colors-teal-50:#E6FFFA;--chakra-colors-teal-100:#B2F5EA;--chakra-colors-teal-200:#81E6D9;--chakra-colors-teal-300:#4FD1C5;--chakra-colors-teal-400:#38B2AC;--chakra-colors-teal-500:#319795;--chakra-colors-teal-600:#2C7A7B;--chakra-colors-teal-700:#285E61;--chakra-colors-teal-800:#234E52;--chakra-colors-teal-900:#1D4044;--chakra-colors-blue-50:#ebf8ff;--chakra-colors-blue-100:#bee3f8;--chakra-colors-blue-200:#90cdf4;--chakra-colors-blue-300:#63b3ed;--chakra-colors-blue-400:#4299e1;--chakra-colors-blue-500:#3182ce;--chakra-colors-blue-600:#2b6cb0;--chakra-colors-blue-700:#2c5282;--chakra-colors-blue-800:#2a4365;--chakra-colors-blue-900:#1A365D;--chakra-colors-cyan-50:#EDFDFD;--chakra-colors-cyan-100:#C4F1F9;--chakra-colors-cyan-200:#9DECF9;--chakra-colors-cyan-300:#76E4F7;--chakra-colors-cyan-400:#0BC5EA;--chakra-colors-cyan-500:#00B5D8;--chakra-colors-cyan-600:#00A3C4;--chakra-colors-cyan-700:#0987A0;--chakra-colors-cyan-800:#086F83;--chakra-colors-cyan-900:#065666;--chakra-colors-purple-50:#FAF5FF;--chakra-colors-purple-100:#E9D8FD;--chakra-colors-purple-200:#D6BCFA;--chakra-colors-purple-300:#B794F4;--chakra-colors-purple-400:#9F7AEA;--chakra-colors-purple-500:#805AD5;--chakra-colors-purple-600:#6B46C1;--chakra-colors-purple-700:#553C9A;--chakra-colors-purple-800:#44337A;--chakra-colors-purple-900:#322659;--chakra-colors-pink-50:#FFF5F7;--chakra-colors-pink-100:#FED7E2;--chakra-colors-pink-200:#FBB6CE;--chakra-colors-pink-300:#F687B3;--chakra-colors-pink-400:#ED64A6;--chakra-colors-pink-500:#D53F8C;--chakra-colors-pink-600:#B83280;--chakra-colors-pink-700:#97266D;--chakra-colors-pink-800:#702459;--chakra-colors-pink-900:#521B41;--chakra-borders-none:0;--chakra-borders-1px:1px solid;--chakra-borders-2px:2px solid;--chakra-borders-4px:4px solid;--chakra-borders-8px:8px solid;--chakra-fonts-heading:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-mono:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--chakra-fontSizes-3xs:0.45rem;--chakra-fontSizes-2xs:0.625rem;--chakra-fontSizes-xs:0.75rem;--chakra-fontSizes-sm:0.875rem;--chakra-fontSizes-md:1rem;--chakra-fontSizes-lg:1.125rem;--chakra-fontSizes-xl:1.25rem;--chakra-fontSizes-2xl:1.5rem;--chakra-fontSizes-3xl:1.875rem;--chakra-fontSizes-4xl:2.25rem;--chakra-fontSizes-5xl:3rem;--chakra-fontSizes-6xl:3.75rem;--chakra-fontSizes-7xl:4.5rem;--chakra-fontSizes-8xl:6rem;--chakra-fontSizes-9xl:8rem;--chakra-fontWeights-hairline:100;--chakra-fontWeights-thin:200;--chakra-fontWeights-light:300;--chakra-fontWeights-normal:400;--chakra-fontWeights-medium:500;--chakra-fontWeights-semibold:600;--chakra-fontWeights-bold:700;--chakra-fontWeights-extrabold:800;--chakra-fontWeights-black:900;--chakra-letterSpacings-tighter:-0.05em;--chakra-letterSpacings-tight:-0.025em;--chakra-letterSpacings-normal:0;--chakra-letterSpacings-wide:0.025em;--chakra-letterSpacings-wider:0.05em;--chakra-letterSpacings-widest:0.1em;--chakra-lineHeights-3:.75rem;--chakra-lineHeights-4:1rem;--chakra-lineHeights-5:1.25rem;--chakra-lineHeights-6:1.5rem;--chakra-lineHeights-7:1.75rem;--chakra-lineHeights-8:2rem;--chakra-lineHeights-9:2.25rem;--chakra-lineHeights-10:2.5rem;--chakra-lineHeights-normal:normal;--chakra-lineHeights-none:1;--chakra-lineHeights-shorter:1.25;--chakra-lineHeights-short:1.375;--chakra-lineHeights-base:1.5;--chakra-lineHeights-tall:1.625;--chakra-lineHeights-taller:2;--chakra-radii-none:0;--chakra-radii-sm:0.125rem;--chakra-radii-base:0.25rem;--chakra-radii-md:0.375rem;--chakra-radii-lg:0.5rem;--chakra-radii-xl:0.75rem;--chakra-radii-2xl:1rem;--chakra-radii-3xl:1.5rem;--chakra-radii-full:9999px;--chakra-space-1:0.25rem;--chakra-space-2:0.5rem;--chakra-space-3:0.75rem;--chakra-space-4:1rem;--chakra-space-5:1.25rem;--chakra-space-6:1.5rem;--chakra-space-7:1.75rem;--chakra-space-8:2rem;--chakra-space-9:2.25rem;--chakra-space-10:2.5rem;--chakra-space-12:3rem;--chakra-space-14:3.5rem;--chakra-space-16:4rem;--chakra-space-20:5rem;--chakra-space-24:6rem;--chakra-space-28:7rem;--chakra-space-32:8rem;--chakra-space-36:9rem;--chakra-space-40:10rem;--chakra-space-44:11rem;--chakra-space-48:12rem;--chakra-space-52:13rem;--chakra-space-56:14rem;--chakra-space-60:15rem;--chakra-space-64:16rem;--chakra-space-72:18rem;--chakra-space-80:20rem;--chakra-space-96:24rem;--chakra-space-px:1px;--chakra-space-0-5:0.125rem;--chakra-space-1-5:0.375rem;--chakra-space-2-5:0.625rem;--chakra-space-3-5:0.875rem;--chakra-shadows-xs:0 0 0 1px rgba(0, 0, 0, 0.05);--chakra-shadows-sm:0 1px 2px 0 rgba(0, 0, 0, 0.05);--chakra-shadows-base:0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);--chakra-shadows-md:0 4px 6px -1px rgba(0, 0, 0, 0.1),0 2px 4px -1px rgba(0, 0, 0, 0.06);--chakra-shadows-lg:0 10px 15px -3px rgba(0, 0, 0, 0.1),0 4px 6px -2px rgba(0, 0, 0, 0.05);--chakra-shadows-xl:0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04);--chakra-shadows-2xl:0 25px 50px -12px rgba(0, 0, 0, 0.25);--chakra-shadows-outline:0 0 0 3px rgba(66, 153, 225, 0.6);--chakra-shadows-inner:inset 0 2px 4px 0 rgba(0,0,0,0.06);--chakra-shadows-none:none;--chakra-shadows-dark-lg:rgba(0, 0, 0, 0.1) 0px 0px 0px 1px,rgba(0, 0, 0, 0.2) 0px 5px 10px,rgba(0, 0, 0, 0.4) 0px 15px 40px;--chakra-sizes-1:0.25rem;--chakra-sizes-2:0.5rem;--chakra-sizes-3:0.75rem;--chakra-sizes-4:1rem;--chakra-sizes-5:1.25rem;--chakra-sizes-6:1.5rem;--chakra-sizes-7:1.75rem;--chakra-sizes-8:2rem;--chakra-sizes-9:2.25rem;--chakra-sizes-10:2.5rem;--chakra-sizes-12:3rem;--chakra-sizes-14:3.5rem;--chakra-sizes-16:4rem;--chakra-sizes-20:5rem;--chakra-sizes-24:6rem;--chakra-sizes-28:7rem;--chakra-sizes-32:8rem;--chakra-sizes-36:9rem;--chakra-sizes-40:10rem;--chakra-sizes-44:11rem;--chakra-sizes-48:12rem;--chakra-sizes-52:13rem;--chakra-sizes-56:14rem;--chakra-sizes-60:15rem;--chakra-sizes-64:16rem;--chakra-sizes-72:18rem;--chakra-sizes-80:20rem;--chakra-sizes-96:24rem;--chakra-sizes-px:1px;--chakra-sizes-0-5:0.125rem;--chakra-sizes-1-5:0.375rem;--chakra-sizes-2-5:0.625rem;--chakra-sizes-3-5:0.875rem;--chakra-sizes-max:max-content;--chakra-sizes-min:min-content;--chakra-sizes-full:100%;--chakra-sizes-3xs:14rem;--chakra-sizes-2xs:16rem;--chakra-sizes-xs:20rem;--chakra-sizes-sm:24rem;--chakra-sizes-md:28rem;--chakra-sizes-lg:32rem;--chakra-sizes-xl:36rem;--chakra-sizes-2xl:42rem;--chakra-sizes-3xl:48rem;--chakra-sizes-4xl:56rem;--chakra-sizes-5xl:64rem;--chakra-sizes-6xl:72rem;--chakra-sizes-7xl:80rem;--chakra-sizes-8xl:90rem;--chakra-sizes-prose:60ch;--chakra-sizes-container-sm:640px;--chakra-sizes-container-md:768px;--chakra-sizes-container-lg:1024px;--chakra-sizes-container-xl:1280px;--chakra-zIndices-hide:-1;--chakra-zIndices-auto:auto;--chakra-zIndices-base:0;--chakra-zIndices-docked:10;--chakra-zIndices-dropdown:1000;--chakra-zIndices-sticky:1100;--chakra-zIndices-banner:1200;--chakra-zIndices-overlay:1300;--chakra-zIndices-modal:1400;--chakra-zIndices-popover:1500;--chakra-zIndices-skipLink:1600;--chakra-zIndices-toast:1700;--chakra-zIndices-tooltip:1800;--chakra-transition-property-common:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform;--chakra-transition-property-colors:background-color,border-color,color,fill,stroke;--chakra-transition-property-dimensions:width,height;--chakra-transition-property-position:left,right,top,bottom;--chakra-transition-property-background:background-color,background-image,background-position;--chakra-transition-easing-ease-in:cubic-bezier(0.4, 0, 1, 1);--chakra-transition-easing-ease-out:cubic-bezier(0, 0, 0.2, 1);--chakra-transition-easing-ease-in-out:cubic-bezier(0.4, 0, 0.2, 1);--chakra-transition-duration-ultra-fast:50ms;--chakra-transition-duration-faster:100ms;--chakra-transition-duration-fast:150ms;--chakra-transition-duration-normal:200ms;--chakra-transition-duration-slow:300ms;--chakra-transition-duration-slower:400ms;--chakra-transition-duration-ultra-slow:500ms;--chakra-blur-none:0;--chakra-blur-sm:4px;--chakra-blur-base:8px;--chakra-blur-md:12px;--chakra-blur-lg:16px;--chakra-blur-xl:24px;--chakra-blur-2xl:40px;--chakra-blur-3xl:64px;--chakra-breakpoints-base:0em;--chakra-breakpoints-sm:30em;--chakra-breakpoints-md:48em;--chakra-breakpoints-lg:62em;--chakra-breakpoints-xl:80em;--chakra-breakpoints-2xl:96em;}.chakra-ui-light :host:not([data-theme]),.chakra-ui-light :root:not([data-theme]),.chakra-ui-light [data-theme]:not([data-theme]),[data-theme=light] :host:not([data-theme]),[data-theme=light] :root:not([data-theme]),[data-theme=light] [data-theme]:not([data-theme]),:host[data-theme=light],:root[data-theme=light],[data-theme][data-theme=light]{--chakra-colors-chakra-body-text:var(--chakra-colors-gray-800);--chakra-colors-chakra-body-bg:var(--chakra-colors-white);--chakra-colors-chakra-border-color:var(--chakra-colors-gray-200);--chakra-colors-chakra-inverse-text:var(--chakra-colors-white);--chakra-colors-chakra-subtle-bg:var(--chakra-colors-gray-100);--chakra-colors-chakra-subtle-text:var(--chakra-colors-gray-600);--chakra-colors-chakra-placeholder-color:var(--chakra-colors-gray-500);}.chakra-ui-dark :host:not([data-theme]),.chakra-ui-dark :root:not([data-theme]),.chakra-ui-dark [data-theme]:not([data-theme]),[data-theme=dark] :host:not([data-theme]),[data-theme=dark] :root:not([data-theme]),[data-theme=dark] [data-theme]:not([data-theme]),:host[data-theme=dark],:root[data-theme=dark],[data-theme][data-theme=dark]{--chakra-colors-chakra-body-text:var(--chakra-colors-whiteAlpha-900);--chakra-colors-chakra-body-bg:var(--chakra-colors-gray-800);--chakra-colors-chakra-border-color:var(--chakra-colors-whiteAlpha-300);--chakra-colors-chakra-inverse-text:var(--chakra-colors-gray-800);--chakra-colors-chakra-subtle-bg:var(--chakra-colors-gray-700);--chakra-colors-chakra-subtle-text:var(--chakra-colors-gray-400);--chakra-colors-chakra-placeholder-color:var(--chakra-colors-whiteAlpha-400);}</style><style data-emotion="css-global fubdgu">html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;touch-action:manipulation;}body{position:relative;min-height:100%;margin:0;font-feature-settings:"kern";}:where(*, *::before, *::after){border-width:0;border-style:solid;box-sizing:border-box;word-wrap:break-word;}main{display:block;}hr{border-top-width:1px;box-sizing:content-box;height:0;overflow:visible;}:where(pre, code, kbd,samp){font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:1em;}a{background-color:transparent;color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;}abbr[title]{border-bottom:none;-webkit-text-decoration:underline;text-decoration:underline;-webkit-text-decoration:underline dotted;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;}:where(b, strong){font-weight:bold;}small{font-size:80%;}:where(sub,sup){font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sub{bottom:-0.25em;}sup{top:-0.5em;}img{border-style:none;}:where(button, input, optgroup, select, textarea){font-family:inherit;font-size:100%;line-height:1.15;margin:0;}:where(button, input){overflow:visible;}:where(button, select){text-transform:none;}:where(
|
|
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="image" href="/logo/recce-logo-white.png"/><link rel="stylesheet" href="/_next/static/css/c9ecb46a4b21c126.css" data-precedence="next"/><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><script src="/_next/static/chunks/e24bf851-0f8cbc99656833e7.js" async=""></script><script src="/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js" async=""></script><script src="/_next/static/chunks/3998a672-eaad84bdd88cc73e.js" async=""></script><script src="/_next/static/chunks/9746af58-d74bef4d03eea6ab.js" async=""></script><script src="/_next/static/chunks/ce84277d-f42c2c58049cea2d.js" async=""></script><script src="/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js" async=""></script><script src="/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js" async=""></script><script src="/_next/static/chunks/fee69bc6-f17d36c080742e74.js" async=""></script><script src="/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js" async=""></script><script src="/_next/static/chunks/450c323b-1bb5db526e54435a.js" async=""></script><script src="/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js" async=""></script><script src="/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js" async=""></script><script src="/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js" async=""></script><script src="/_next/static/chunks/7f27ae6c-413f6b869a04183a.js" async=""></script><script src="/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js" async=""></script><script src="/_next/static/chunks/c132bf7d-8102037f9ccf372a.js" async=""></script><script src="/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js" async=""></script><script src="/_next/static/chunks/500-e51c92a025a51234.js" async=""></script><script src="/_next/static/chunks/app/page-9adc25782272ed2e.js" async=""></script><title>recce</title><meta name="description" content="Recce: Data validation toolkit for comprehensive PR review"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="32x32"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><style data-emotion="css-global rh8y69">:host,:root,[data-theme]{--chakra-ring-inset:var(--chakra-empty,/*!*/ /*!*/);--chakra-ring-offset-width:0px;--chakra-ring-offset-color:#fff;--chakra-ring-color:rgba(66, 153, 225, 0.6);--chakra-ring-offset-shadow:0 0 #0000;--chakra-ring-shadow:0 0 #0000;--chakra-space-x-reverse:0;--chakra-space-y-reverse:0;--chakra-colors-transparent:transparent;--chakra-colors-current:currentColor;--chakra-colors-black:#000000;--chakra-colors-white:#FFFFFF;--chakra-colors-whiteAlpha-50:rgba(255, 255, 255, 0.04);--chakra-colors-whiteAlpha-100:rgba(255, 255, 255, 0.06);--chakra-colors-whiteAlpha-200:rgba(255, 255, 255, 0.08);--chakra-colors-whiteAlpha-300:rgba(255, 255, 255, 0.16);--chakra-colors-whiteAlpha-400:rgba(255, 255, 255, 0.24);--chakra-colors-whiteAlpha-500:rgba(255, 255, 255, 0.36);--chakra-colors-whiteAlpha-600:rgba(255, 255, 255, 0.48);--chakra-colors-whiteAlpha-700:rgba(255, 255, 255, 0.64);--chakra-colors-whiteAlpha-800:rgba(255, 255, 255, 0.80);--chakra-colors-whiteAlpha-900:rgba(255, 255, 255, 0.92);--chakra-colors-blackAlpha-50:rgba(0, 0, 0, 0.04);--chakra-colors-blackAlpha-100:rgba(0, 0, 0, 0.06);--chakra-colors-blackAlpha-200:rgba(0, 0, 0, 0.08);--chakra-colors-blackAlpha-300:rgba(0, 0, 0, 0.16);--chakra-colors-blackAlpha-400:rgba(0, 0, 0, 0.24);--chakra-colors-blackAlpha-500:rgba(0, 0, 0, 0.36);--chakra-colors-blackAlpha-600:rgba(0, 0, 0, 0.48);--chakra-colors-blackAlpha-700:rgba(0, 0, 0, 0.64);--chakra-colors-blackAlpha-800:rgba(0, 0, 0, 0.80);--chakra-colors-blackAlpha-900:rgba(0, 0, 0, 0.92);--chakra-colors-gray-50:#F7FAFC;--chakra-colors-gray-100:#EDF2F7;--chakra-colors-gray-200:#E2E8F0;--chakra-colors-gray-300:#CBD5E0;--chakra-colors-gray-400:#A0AEC0;--chakra-colors-gray-500:#718096;--chakra-colors-gray-600:#4A5568;--chakra-colors-gray-700:#2D3748;--chakra-colors-gray-800:#1A202C;--chakra-colors-gray-900:#171923;--chakra-colors-red-50:#FFF5F5;--chakra-colors-red-100:#FED7D7;--chakra-colors-red-200:#FEB2B2;--chakra-colors-red-300:#FC8181;--chakra-colors-red-400:#F56565;--chakra-colors-red-500:#E53E3E;--chakra-colors-red-600:#C53030;--chakra-colors-red-700:#9B2C2C;--chakra-colors-red-800:#822727;--chakra-colors-red-900:#63171B;--chakra-colors-orange-50:#FFFAF0;--chakra-colors-orange-100:#FEEBC8;--chakra-colors-orange-200:#FBD38D;--chakra-colors-orange-300:#F6AD55;--chakra-colors-orange-400:#ED8936;--chakra-colors-orange-500:#DD6B20;--chakra-colors-orange-600:#C05621;--chakra-colors-orange-700:#9C4221;--chakra-colors-orange-800:#7B341E;--chakra-colors-orange-900:#652B19;--chakra-colors-yellow-50:#FFFFF0;--chakra-colors-yellow-100:#FEFCBF;--chakra-colors-yellow-200:#FAF089;--chakra-colors-yellow-300:#F6E05E;--chakra-colors-yellow-400:#ECC94B;--chakra-colors-yellow-500:#D69E2E;--chakra-colors-yellow-600:#B7791F;--chakra-colors-yellow-700:#975A16;--chakra-colors-yellow-800:#744210;--chakra-colors-yellow-900:#5F370E;--chakra-colors-green-50:#F0FFF4;--chakra-colors-green-100:#C6F6D5;--chakra-colors-green-200:#9AE6B4;--chakra-colors-green-300:#68D391;--chakra-colors-green-400:#48BB78;--chakra-colors-green-500:#38A169;--chakra-colors-green-600:#2F855A;--chakra-colors-green-700:#276749;--chakra-colors-green-800:#22543D;--chakra-colors-green-900:#1C4532;--chakra-colors-teal-50:#E6FFFA;--chakra-colors-teal-100:#B2F5EA;--chakra-colors-teal-200:#81E6D9;--chakra-colors-teal-300:#4FD1C5;--chakra-colors-teal-400:#38B2AC;--chakra-colors-teal-500:#319795;--chakra-colors-teal-600:#2C7A7B;--chakra-colors-teal-700:#285E61;--chakra-colors-teal-800:#234E52;--chakra-colors-teal-900:#1D4044;--chakra-colors-blue-50:#ebf8ff;--chakra-colors-blue-100:#bee3f8;--chakra-colors-blue-200:#90cdf4;--chakra-colors-blue-300:#63b3ed;--chakra-colors-blue-400:#4299e1;--chakra-colors-blue-500:#3182ce;--chakra-colors-blue-600:#2b6cb0;--chakra-colors-blue-700:#2c5282;--chakra-colors-blue-800:#2a4365;--chakra-colors-blue-900:#1A365D;--chakra-colors-cyan-50:#EDFDFD;--chakra-colors-cyan-100:#C4F1F9;--chakra-colors-cyan-200:#9DECF9;--chakra-colors-cyan-300:#76E4F7;--chakra-colors-cyan-400:#0BC5EA;--chakra-colors-cyan-500:#00B5D8;--chakra-colors-cyan-600:#00A3C4;--chakra-colors-cyan-700:#0987A0;--chakra-colors-cyan-800:#086F83;--chakra-colors-cyan-900:#065666;--chakra-colors-purple-50:#FAF5FF;--chakra-colors-purple-100:#E9D8FD;--chakra-colors-purple-200:#D6BCFA;--chakra-colors-purple-300:#B794F4;--chakra-colors-purple-400:#9F7AEA;--chakra-colors-purple-500:#805AD5;--chakra-colors-purple-600:#6B46C1;--chakra-colors-purple-700:#553C9A;--chakra-colors-purple-800:#44337A;--chakra-colors-purple-900:#322659;--chakra-colors-pink-50:#FFF5F7;--chakra-colors-pink-100:#FED7E2;--chakra-colors-pink-200:#FBB6CE;--chakra-colors-pink-300:#F687B3;--chakra-colors-pink-400:#ED64A6;--chakra-colors-pink-500:#D53F8C;--chakra-colors-pink-600:#B83280;--chakra-colors-pink-700:#97266D;--chakra-colors-pink-800:#702459;--chakra-colors-pink-900:#521B41;--chakra-borders-none:0;--chakra-borders-1px:1px solid;--chakra-borders-2px:2px solid;--chakra-borders-4px:4px solid;--chakra-borders-8px:8px solid;--chakra-fonts-heading:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-mono:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--chakra-fontSizes-3xs:0.45rem;--chakra-fontSizes-2xs:0.625rem;--chakra-fontSizes-xs:0.75rem;--chakra-fontSizes-sm:0.875rem;--chakra-fontSizes-md:1rem;--chakra-fontSizes-lg:1.125rem;--chakra-fontSizes-xl:1.25rem;--chakra-fontSizes-2xl:1.5rem;--chakra-fontSizes-3xl:1.875rem;--chakra-fontSizes-4xl:2.25rem;--chakra-fontSizes-5xl:3rem;--chakra-fontSizes-6xl:3.75rem;--chakra-fontSizes-7xl:4.5rem;--chakra-fontSizes-8xl:6rem;--chakra-fontSizes-9xl:8rem;--chakra-fontWeights-hairline:100;--chakra-fontWeights-thin:200;--chakra-fontWeights-light:300;--chakra-fontWeights-normal:400;--chakra-fontWeights-medium:500;--chakra-fontWeights-semibold:600;--chakra-fontWeights-bold:700;--chakra-fontWeights-extrabold:800;--chakra-fontWeights-black:900;--chakra-letterSpacings-tighter:-0.05em;--chakra-letterSpacings-tight:-0.025em;--chakra-letterSpacings-normal:0;--chakra-letterSpacings-wide:0.025em;--chakra-letterSpacings-wider:0.05em;--chakra-letterSpacings-widest:0.1em;--chakra-lineHeights-3:.75rem;--chakra-lineHeights-4:1rem;--chakra-lineHeights-5:1.25rem;--chakra-lineHeights-6:1.5rem;--chakra-lineHeights-7:1.75rem;--chakra-lineHeights-8:2rem;--chakra-lineHeights-9:2.25rem;--chakra-lineHeights-10:2.5rem;--chakra-lineHeights-normal:normal;--chakra-lineHeights-none:1;--chakra-lineHeights-shorter:1.25;--chakra-lineHeights-short:1.375;--chakra-lineHeights-base:1.5;--chakra-lineHeights-tall:1.625;--chakra-lineHeights-taller:2;--chakra-radii-none:0;--chakra-radii-sm:0.125rem;--chakra-radii-base:0.25rem;--chakra-radii-md:0.375rem;--chakra-radii-lg:0.5rem;--chakra-radii-xl:0.75rem;--chakra-radii-2xl:1rem;--chakra-radii-3xl:1.5rem;--chakra-radii-full:9999px;--chakra-space-1:0.25rem;--chakra-space-2:0.5rem;--chakra-space-3:0.75rem;--chakra-space-4:1rem;--chakra-space-5:1.25rem;--chakra-space-6:1.5rem;--chakra-space-7:1.75rem;--chakra-space-8:2rem;--chakra-space-9:2.25rem;--chakra-space-10:2.5rem;--chakra-space-12:3rem;--chakra-space-14:3.5rem;--chakra-space-16:4rem;--chakra-space-20:5rem;--chakra-space-24:6rem;--chakra-space-28:7rem;--chakra-space-32:8rem;--chakra-space-36:9rem;--chakra-space-40:10rem;--chakra-space-44:11rem;--chakra-space-48:12rem;--chakra-space-52:13rem;--chakra-space-56:14rem;--chakra-space-60:15rem;--chakra-space-64:16rem;--chakra-space-72:18rem;--chakra-space-80:20rem;--chakra-space-96:24rem;--chakra-space-px:1px;--chakra-space-0-5:0.125rem;--chakra-space-1-5:0.375rem;--chakra-space-2-5:0.625rem;--chakra-space-3-5:0.875rem;--chakra-shadows-xs:0 0 0 1px rgba(0, 0, 0, 0.05);--chakra-shadows-sm:0 1px 2px 0 rgba(0, 0, 0, 0.05);--chakra-shadows-base:0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);--chakra-shadows-md:0 4px 6px -1px rgba(0, 0, 0, 0.1),0 2px 4px -1px rgba(0, 0, 0, 0.06);--chakra-shadows-lg:0 10px 15px -3px rgba(0, 0, 0, 0.1),0 4px 6px -2px rgba(0, 0, 0, 0.05);--chakra-shadows-xl:0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04);--chakra-shadows-2xl:0 25px 50px -12px rgba(0, 0, 0, 0.25);--chakra-shadows-outline:0 0 0 3px rgba(66, 153, 225, 0.6);--chakra-shadows-inner:inset 0 2px 4px 0 rgba(0,0,0,0.06);--chakra-shadows-none:none;--chakra-shadows-dark-lg:rgba(0, 0, 0, 0.1) 0px 0px 0px 1px,rgba(0, 0, 0, 0.2) 0px 5px 10px,rgba(0, 0, 0, 0.4) 0px 15px 40px;--chakra-sizes-1:0.25rem;--chakra-sizes-2:0.5rem;--chakra-sizes-3:0.75rem;--chakra-sizes-4:1rem;--chakra-sizes-5:1.25rem;--chakra-sizes-6:1.5rem;--chakra-sizes-7:1.75rem;--chakra-sizes-8:2rem;--chakra-sizes-9:2.25rem;--chakra-sizes-10:2.5rem;--chakra-sizes-12:3rem;--chakra-sizes-14:3.5rem;--chakra-sizes-16:4rem;--chakra-sizes-20:5rem;--chakra-sizes-24:6rem;--chakra-sizes-28:7rem;--chakra-sizes-32:8rem;--chakra-sizes-36:9rem;--chakra-sizes-40:10rem;--chakra-sizes-44:11rem;--chakra-sizes-48:12rem;--chakra-sizes-52:13rem;--chakra-sizes-56:14rem;--chakra-sizes-60:15rem;--chakra-sizes-64:16rem;--chakra-sizes-72:18rem;--chakra-sizes-80:20rem;--chakra-sizes-96:24rem;--chakra-sizes-px:1px;--chakra-sizes-0-5:0.125rem;--chakra-sizes-1-5:0.375rem;--chakra-sizes-2-5:0.625rem;--chakra-sizes-3-5:0.875rem;--chakra-sizes-max:max-content;--chakra-sizes-min:min-content;--chakra-sizes-full:100%;--chakra-sizes-3xs:14rem;--chakra-sizes-2xs:16rem;--chakra-sizes-xs:20rem;--chakra-sizes-sm:24rem;--chakra-sizes-md:28rem;--chakra-sizes-lg:32rem;--chakra-sizes-xl:36rem;--chakra-sizes-2xl:42rem;--chakra-sizes-3xl:48rem;--chakra-sizes-4xl:56rem;--chakra-sizes-5xl:64rem;--chakra-sizes-6xl:72rem;--chakra-sizes-7xl:80rem;--chakra-sizes-8xl:90rem;--chakra-sizes-prose:60ch;--chakra-sizes-container-sm:640px;--chakra-sizes-container-md:768px;--chakra-sizes-container-lg:1024px;--chakra-sizes-container-xl:1280px;--chakra-zIndices-hide:-1;--chakra-zIndices-auto:auto;--chakra-zIndices-base:0;--chakra-zIndices-docked:10;--chakra-zIndices-dropdown:1000;--chakra-zIndices-sticky:1100;--chakra-zIndices-banner:1200;--chakra-zIndices-overlay:1300;--chakra-zIndices-modal:1400;--chakra-zIndices-popover:1500;--chakra-zIndices-skipLink:1600;--chakra-zIndices-toast:1700;--chakra-zIndices-tooltip:1800;--chakra-transition-property-common:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform;--chakra-transition-property-colors:background-color,border-color,color,fill,stroke;--chakra-transition-property-dimensions:width,height;--chakra-transition-property-position:left,right,top,bottom;--chakra-transition-property-background:background-color,background-image,background-position;--chakra-transition-easing-ease-in:cubic-bezier(0.4, 0, 1, 1);--chakra-transition-easing-ease-out:cubic-bezier(0, 0, 0.2, 1);--chakra-transition-easing-ease-in-out:cubic-bezier(0.4, 0, 0.2, 1);--chakra-transition-duration-ultra-fast:50ms;--chakra-transition-duration-faster:100ms;--chakra-transition-duration-fast:150ms;--chakra-transition-duration-normal:200ms;--chakra-transition-duration-slow:300ms;--chakra-transition-duration-slower:400ms;--chakra-transition-duration-ultra-slow:500ms;--chakra-blur-none:0;--chakra-blur-sm:4px;--chakra-blur-base:8px;--chakra-blur-md:12px;--chakra-blur-lg:16px;--chakra-blur-xl:24px;--chakra-blur-2xl:40px;--chakra-blur-3xl:64px;--chakra-breakpoints-base:0em;--chakra-breakpoints-sm:30em;--chakra-breakpoints-md:48em;--chakra-breakpoints-lg:62em;--chakra-breakpoints-xl:80em;--chakra-breakpoints-2xl:96em;}.chakra-ui-light :host:not([data-theme]),.chakra-ui-light :root:not([data-theme]),.chakra-ui-light [data-theme]:not([data-theme]),[data-theme=light] :host:not([data-theme]),[data-theme=light] :root:not([data-theme]),[data-theme=light] [data-theme]:not([data-theme]),:host[data-theme=light],:root[data-theme=light],[data-theme][data-theme=light]{--chakra-colors-chakra-body-text:var(--chakra-colors-gray-800);--chakra-colors-chakra-body-bg:var(--chakra-colors-white);--chakra-colors-chakra-border-color:var(--chakra-colors-gray-200);--chakra-colors-chakra-inverse-text:var(--chakra-colors-white);--chakra-colors-chakra-subtle-bg:var(--chakra-colors-gray-100);--chakra-colors-chakra-subtle-text:var(--chakra-colors-gray-600);--chakra-colors-chakra-placeholder-color:var(--chakra-colors-gray-500);}.chakra-ui-dark :host:not([data-theme]),.chakra-ui-dark :root:not([data-theme]),.chakra-ui-dark [data-theme]:not([data-theme]),[data-theme=dark] :host:not([data-theme]),[data-theme=dark] :root:not([data-theme]),[data-theme=dark] [data-theme]:not([data-theme]),:host[data-theme=dark],:root[data-theme=dark],[data-theme][data-theme=dark]{--chakra-colors-chakra-body-text:var(--chakra-colors-whiteAlpha-900);--chakra-colors-chakra-body-bg:var(--chakra-colors-gray-800);--chakra-colors-chakra-border-color:var(--chakra-colors-whiteAlpha-300);--chakra-colors-chakra-inverse-text:var(--chakra-colors-gray-800);--chakra-colors-chakra-subtle-bg:var(--chakra-colors-gray-700);--chakra-colors-chakra-subtle-text:var(--chakra-colors-gray-400);--chakra-colors-chakra-placeholder-color:var(--chakra-colors-whiteAlpha-400);}</style><style data-emotion="css-global fubdgu">html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;touch-action:manipulation;}body{position:relative;min-height:100%;margin:0;font-feature-settings:"kern";}:where(*, *::before, *::after){border-width:0;border-style:solid;box-sizing:border-box;word-wrap:break-word;}main{display:block;}hr{border-top-width:1px;box-sizing:content-box;height:0;overflow:visible;}:where(pre, code, kbd,samp){font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:1em;}a{background-color:transparent;color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;}abbr[title]{border-bottom:none;-webkit-text-decoration:underline;text-decoration:underline;-webkit-text-decoration:underline dotted;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;}:where(b, strong){font-weight:bold;}small{font-size:80%;}:where(sub,sup){font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sub{bottom:-0.25em;}sup{top:-0.5em;}img{border-style:none;}:where(button, input, optgroup, select, textarea){font-family:inherit;font-size:100%;line-height:1.15;margin:0;}:where(button, input){overflow:visible;}:where(button, select){text-transform:none;}:where(
|
|
2
2
|
button::-moz-focus-inner,
|
|
3
3
|
[type="button"]::-moz-focus-inner,
|
|
4
4
|
[type="reset"]::-moz-focus-inner,
|
|
@@ -24,4 +24,4 @@
|
|
|
24
24
|
transparent 0%,
|
|
25
25
|
#3182ce 50%,
|
|
26
26
|
transparent 100%
|
|
27
|
-
);position:absolute;will-change:left;min-width:50%;-webkit-animation:animation-11lmxjq 1s ease infinite normal none running;animation:animation-11lmxjq 1s ease infinite normal none running;}@-webkit-keyframes animation-11lmxjq{0%{left:-40%;}100%{left:100%;}}@keyframes animation-11lmxjq{0%{left:-40%;}100%{left:100%;}}</style><div style="width:0%" data-indeterminate="" aria-valuemax="100" aria-valuemin="0" role="progressbar" class="css-h5ends"></div></div></div><div class="css-0"></div></div></div></div><span></span><span id="__chakra_env" hidden=""></span><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:HL[\"/_next/static/css/c9ecb46a4b21c126.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[37194,[],\"\"]\n4:I[86822,[],\"ClientPageRoot\"]\n5:I[
|
|
27
|
+
);position:absolute;will-change:left;min-width:50%;-webkit-animation:animation-11lmxjq 1s ease infinite normal none running;animation:animation-11lmxjq 1s ease infinite normal none running;}@-webkit-keyframes animation-11lmxjq{0%{left:-40%;}100%{left:100%;}}@keyframes animation-11lmxjq{0%{left:-40%;}100%{left:100%;}}</style><div style="width:0%" data-indeterminate="" aria-valuemax="100" aria-valuemin="0" role="progressbar" class="css-h5ends"></div></div></div><div class="css-0"></div></div></div></div><span></span><span id="__chakra_env" hidden=""></span><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:HL[\"/_next/static/css/c9ecb46a4b21c126.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[37194,[],\"\"]\n4:I[86822,[],\"ClientPageRoot\"]\n5:I[95888,[\"266\",\"static/chunks/e24bf851-0f8cbc99656833e7.js\",\"517\",\"static/chunks/c1ceaa8b-a1e442154d23515e.js\",\"678\",\"static/chunks/3998a672-eaad84bdd88cc73e.js\",\"509\",\"static/chunks/9746af58-d74bef4d03eea6ab.js\",\"648\",\"static/chunks/ce84277d-f42c2c58049cea2d.js\",\"989\",\"static/chunks/47d8844f-79a1b53c66a7d7ec.js\",\"147\",\"static/chunks/a30376cd-7d806e1602f2dc3a.js\",\"995\",\"static/chunks/fee69bc6-f17d36c080742e74.js\",\"739\",\"static/chunks/7a8a3e83-d7fa409d97b38b2b.js\",\"283\",\"static/chunks/450c323b-1bb5db526e54435a.js\",\"303\",\"static/chunks/36e1c10d-bb0210cbd6573a8d.js\",\"22\",\"static/chunks/29e3cc0d-8c150e37dff9631b.js\",\"25\",\"static/chunks/b63b1b3f-7395c74e11a14e95.js\",\"355\",\"static/chunks/7f27ae6c-413f6b869a04183a.js\",\"495\",\"static/chunks/6dc81886-c94b9b91bc2c3caf.js\",\"599\",\"static/chunks/c132bf7d-8102037f9ccf372a.js\",\"971\",\"static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js\",\"500\",\"static/chunks/500-e51c92a025a51234.js\",\"931\",\"static/chunks/app/page-9adc25782272ed2e.js\"],\"default\",1]\n6:I[79137,[],\"\"]\n7:I[63846,[],\"\"]\n9:I[67160,[],\"\"]\na:[]\n0:[\"$\",\"$L2\",null,{\"buildId\":\"Kl6NGUBajFGsWgGKiDdQ2\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"\"],\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[\"$\",\"$L4\",null,{\"props\":{\"params\":{},\"searchParams\":{}},\"Component\":\"$5\"}],[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/c9ecb46a4b21c126.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]]],null],null]},[[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"suppressHydrationWarning\":true,\"children\":[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helv"])</script><script>self.__next_f.push([1,"etica,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.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[null,\"$L8\"],\"globalErrorComponent\":\"$9\",\"missingSlots\":\"$Wa\"}]\n"])</script><script>self.__next_f.push([1,"8:[[\"$\",\"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\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"32x32\"}]]\n3:null\n"])</script></body></html>
|
recce/data/index.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
2:I[86822,[],"ClientPageRoot"]
|
|
2
|
-
3:I[
|
|
2
|
+
3:I[95888,["266","static/chunks/e24bf851-0f8cbc99656833e7.js","517","static/chunks/c1ceaa8b-a1e442154d23515e.js","678","static/chunks/3998a672-eaad84bdd88cc73e.js","509","static/chunks/9746af58-d74bef4d03eea6ab.js","648","static/chunks/ce84277d-f42c2c58049cea2d.js","989","static/chunks/47d8844f-79a1b53c66a7d7ec.js","147","static/chunks/a30376cd-7d806e1602f2dc3a.js","995","static/chunks/fee69bc6-f17d36c080742e74.js","739","static/chunks/7a8a3e83-d7fa409d97b38b2b.js","283","static/chunks/450c323b-1bb5db526e54435a.js","303","static/chunks/36e1c10d-bb0210cbd6573a8d.js","22","static/chunks/29e3cc0d-8c150e37dff9631b.js","25","static/chunks/b63b1b3f-7395c74e11a14e95.js","355","static/chunks/7f27ae6c-413f6b869a04183a.js","495","static/chunks/6dc81886-c94b9b91bc2c3caf.js","599","static/chunks/c132bf7d-8102037f9ccf372a.js","971","static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js","500","static/chunks/500-e51c92a025a51234.js","931","static/chunks/app/page-9adc25782272ed2e.js"],"default",1]
|
|
3
3
|
4:I[79137,[],""]
|
|
4
4
|
5:I[63846,[],""]
|
|
5
|
-
0:["
|
|
5
|
+
0:["Kl6NGUBajFGsWgGKiDdQ2",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/c9ecb46a4b21c126.css","precedence":"next","crossOrigin":"$undefined"}]]],null],null]},[[null,["$","html",null,{"lang":"en","children":["$","body",null,{"suppressHydrationWarning":true,"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
|
6
6
|
6:[["$","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"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"32x32"}]]
|
|
7
7
|
1:null
|
recce/models/types.py
CHANGED
|
@@ -89,13 +89,21 @@ ChangeStatus = Literal[
|
|
|
89
89
|
'modified',
|
|
90
90
|
]
|
|
91
91
|
ChangeCategory = Literal[
|
|
92
|
-
'breaking',
|
|
92
|
+
'breaking',
|
|
93
|
+
'non_breaking',
|
|
94
|
+
'partial_breaking',
|
|
95
|
+
'unknown',
|
|
93
96
|
]
|
|
94
97
|
|
|
95
98
|
|
|
99
|
+
class NodeChange(BaseModel):
|
|
100
|
+
category: ChangeCategory
|
|
101
|
+
columns: Optional[dict[str, ChangeStatus]] = None
|
|
102
|
+
|
|
103
|
+
|
|
96
104
|
class NodeDiff(BaseModel):
|
|
97
105
|
change_status: ChangeStatus
|
|
98
|
-
|
|
106
|
+
change: Optional[NodeChange] = None # Only available if change_status is 'modified'
|
|
99
107
|
|
|
100
108
|
|
|
101
109
|
class LineageDiff(BaseModel):
|
recce/server.py
CHANGED
|
@@ -25,7 +25,7 @@ from .core import load_context, default_context
|
|
|
25
25
|
from .event import log_api_event, log_single_env_event
|
|
26
26
|
from .exceptions import RecceException
|
|
27
27
|
from .run import load_preset_checks
|
|
28
|
-
from .state import RecceStateLoader
|
|
28
|
+
from .state import RecceStateLoader, RecceShareStateManager
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger('uvicorn')
|
|
31
31
|
|
|
@@ -35,6 +35,7 @@ class AppState:
|
|
|
35
35
|
state_loader: Optional[RecceStateLoader] = None
|
|
36
36
|
kwargs: Optional[dict] = None
|
|
37
37
|
flag: Optional[dict] = None
|
|
38
|
+
auth_options: Optional[dict] = None
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
@asynccontextmanager
|
|
@@ -178,8 +179,13 @@ async def recce_instance_info():
|
|
|
178
179
|
app_state: AppState = app.state
|
|
179
180
|
flag = app_state.flag
|
|
180
181
|
read_only = flag.get('read_only', False)
|
|
182
|
+
|
|
183
|
+
auth_options = app_state.auth_options
|
|
184
|
+
api_token = auth_options.get('api_token')
|
|
185
|
+
|
|
181
186
|
return {
|
|
182
187
|
"read_only": read_only,
|
|
188
|
+
"authed": True if api_token else False,
|
|
183
189
|
# TODO: Add more instance info which won't change during the instance lifecycle
|
|
184
190
|
# review_mode
|
|
185
191
|
# cloud_mode
|
|
@@ -506,6 +512,39 @@ async def sync_status(response: Response):
|
|
|
506
512
|
return {"status": "idle"}
|
|
507
513
|
|
|
508
514
|
|
|
515
|
+
class ShareStateOutput(BaseModel):
|
|
516
|
+
status: str
|
|
517
|
+
message: str
|
|
518
|
+
share_url: Optional[str] = None
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
@app.post("/api/share", response_model=ShareStateOutput)
|
|
522
|
+
async def share_state():
|
|
523
|
+
"""
|
|
524
|
+
Share the recce state to the external storage. (one-way sync)
|
|
525
|
+
"""
|
|
526
|
+
app_state: AppState = app.state
|
|
527
|
+
state_manager = RecceShareStateManager(app_state.auth_options)
|
|
528
|
+
if not state_manager.verify():
|
|
529
|
+
error, hint = state_manager.error_and_hint
|
|
530
|
+
raise HTTPException(status_code=400, detail=f"Failed to share state: {error}. {hint}")
|
|
531
|
+
|
|
532
|
+
context = default_context()
|
|
533
|
+
state_loader = context.state_loader
|
|
534
|
+
|
|
535
|
+
file_name = 'recce_state.json'
|
|
536
|
+
if state_loader.state_file:
|
|
537
|
+
file_name = os.path.basename(state_loader.state_file)
|
|
538
|
+
|
|
539
|
+
state = state_loader.state
|
|
540
|
+
if state_loader.state is None:
|
|
541
|
+
state = context.export_state()
|
|
542
|
+
|
|
543
|
+
response = state_manager.share_state(file_name, state)
|
|
544
|
+
|
|
545
|
+
return ShareStateOutput(**response)
|
|
546
|
+
|
|
547
|
+
|
|
509
548
|
class VersionOut(BaseModel):
|
|
510
549
|
version: str
|
|
511
550
|
latestVersion: str
|
recce/state.py
CHANGED
|
@@ -728,13 +728,13 @@ class RecceShareStateManager:
|
|
|
728
728
|
|
|
729
729
|
# It is a class to share state file on Recce Cloud.
|
|
730
730
|
|
|
731
|
-
def __init__(self,
|
|
732
|
-
self.
|
|
731
|
+
def __init__(self, auth_options: Optional[Dict[str, str]] = None):
|
|
732
|
+
self.auth_options = auth_options or {}
|
|
733
733
|
self.error_message = None
|
|
734
734
|
self.hint_message = None
|
|
735
735
|
|
|
736
736
|
def verify(self) -> bool:
|
|
737
|
-
if self.
|
|
737
|
+
if self.auth_options.get('api_token') is None:
|
|
738
738
|
self.error_message = RECCE_API_TOKEN_MISSING.error_message
|
|
739
739
|
self.hint_message = RECCE_API_TOKEN_MISSING.hint_message
|
|
740
740
|
return False
|
|
@@ -744,12 +744,10 @@ class RecceShareStateManager:
|
|
|
744
744
|
def error_and_hint(self) -> (Union[str, None], Union[str, None]):
|
|
745
745
|
return self.error_message, self.hint_message
|
|
746
746
|
|
|
747
|
-
def share_state(self, file_name: str, state: RecceState) ->
|
|
747
|
+
def share_state(self, file_name: str, state: RecceState) -> Dict:
|
|
748
748
|
import tempfile
|
|
749
749
|
|
|
750
750
|
with tempfile.NamedTemporaryFile() as tmp:
|
|
751
751
|
state.to_file(tmp.name, file_type=SupportedFileTypes.FILE)
|
|
752
|
-
response = RecceCloud(token=self.
|
|
753
|
-
|
|
754
|
-
return f"Failed to share the state file. Reason: {response.get('message')}"
|
|
755
|
-
return response.get('share_url')
|
|
752
|
+
response = RecceCloud(token=self.auth_options.get('api_token')).share_state(file_name, open(tmp.name, 'rb'))
|
|
753
|
+
return response
|
recce/util/breaking.py
CHANGED
|
@@ -1,125 +1,80 @@
|
|
|
1
|
+
import time
|
|
1
2
|
from dataclasses import dataclass
|
|
2
|
-
from typing import
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
import sqlglot.expressions as exp
|
|
5
|
-
from sqlglot import parse_one,
|
|
6
|
-
from sqlglot.
|
|
7
|
-
from sqlglot.optimizer import
|
|
8
|
-
|
|
9
|
-
ChangeCategory = Literal['breaking', 'partial_breaking', 'non_breaking', 'unknown']
|
|
10
|
-
ColumnChangeStatus = Literal['added', 'removed', 'modified']
|
|
11
|
-
VALID_EXPRESSIONS = (
|
|
12
|
-
exp.Where,
|
|
13
|
-
exp.Join,
|
|
14
|
-
exp.Order,
|
|
15
|
-
exp.Group,
|
|
16
|
-
exp.Having,
|
|
17
|
-
exp.Limit,
|
|
18
|
-
exp.Offset,
|
|
19
|
-
exp.Window,
|
|
20
|
-
exp.Union,
|
|
21
|
-
exp.Intersect,
|
|
22
|
-
exp.Except,
|
|
23
|
-
exp.Merge,
|
|
24
|
-
exp.Delete,
|
|
25
|
-
exp.Update,
|
|
26
|
-
exp.Insert,
|
|
27
|
-
exp.Subquery,
|
|
28
|
-
)
|
|
6
|
+
from sqlglot import parse_one, Dialect
|
|
7
|
+
from sqlglot.errors import SqlglotError
|
|
8
|
+
from sqlglot.optimizer import traverse_scope, Scope
|
|
9
|
+
from sqlglot.optimizer.qualify import qualify
|
|
29
10
|
|
|
11
|
+
from recce.models.types import NodeChange, ChangeStatus
|
|
30
12
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
category: ChangeCategory
|
|
34
|
-
changed_columns: Optional[dict[str, ColumnChangeStatus]]
|
|
35
|
-
|
|
36
|
-
def __init__(
|
|
37
|
-
self,
|
|
38
|
-
category: ChangeCategory,
|
|
39
|
-
changed_columns: Optional[dict[str, ColumnChangeStatus]] = None
|
|
40
|
-
):
|
|
41
|
-
self.category = category
|
|
42
|
-
self.changed_columns = changed_columns
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
CHANGE_CATEGORY_UNKNOWN = ChangeCategoryResult('unknown')
|
|
46
|
-
CHANGE_CATEGORY_BREAKING = ChangeCategoryResult('breaking')
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _debug(*args):
|
|
50
|
-
pass
|
|
51
|
-
# print(*args)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def is_breaking_change(old_sql, new_sql, dialect=None):
|
|
55
|
-
result = parse_change_category(old_sql, new_sql, old_schema=None, new_schema=None, dialect=dialect)
|
|
56
|
-
return result.category != 'non_breaking'
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _is_breaking_change(original_sql, modified_sql, dialect=None) -> bool:
|
|
60
|
-
if original_sql == modified_sql:
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
dialect = Dialect.get(dialect)
|
|
65
|
-
|
|
66
|
-
def _parse(sql):
|
|
67
|
-
ast = parse_one(sql, dialect=dialect)
|
|
68
|
-
try:
|
|
69
|
-
ast = optimize(ast, dialect=dialect)
|
|
70
|
-
except Exception:
|
|
71
|
-
# cannot optimize, skip it.
|
|
72
|
-
pass
|
|
73
|
-
return ast
|
|
74
|
-
|
|
75
|
-
original_ast = _parse(original_sql)
|
|
76
|
-
modified_ast = _parse(modified_sql)
|
|
77
|
-
except Exception:
|
|
78
|
-
return True
|
|
79
|
-
|
|
80
|
-
if not isinstance(original_ast, exp.Select) or not isinstance(modified_ast, exp.Select):
|
|
81
|
-
raise ValueError("Currently only SELECT statements are supported for comparison")
|
|
82
|
-
|
|
83
|
-
edits = diff(original_ast, modified_ast, delta_only=True)
|
|
84
|
-
|
|
85
|
-
inserted_expressions = {
|
|
86
|
-
e.expression for e in edits if isinstance(e, Insert)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for edit in edits:
|
|
90
|
-
if isinstance(edit, Insert):
|
|
91
|
-
inserted_expr = edit.expression
|
|
92
|
-
|
|
93
|
-
if isinstance(inserted_expr, VALID_EXPRESSIONS):
|
|
94
|
-
return True
|
|
95
|
-
|
|
96
|
-
if isinstance(inserted_expr, exp.UDTF):
|
|
97
|
-
return True
|
|
13
|
+
CHANGE_CATEGORY_UNKNOWN = NodeChange(category='unknown')
|
|
14
|
+
CHANGE_CATEGORY_BREAKING = NodeChange(category='breaking')
|
|
98
15
|
|
|
99
|
-
if (
|
|
100
|
-
not isinstance(inserted_expr.parent, exp.Select) and
|
|
101
|
-
inserted_expr.parent not in inserted_expressions
|
|
102
|
-
):
|
|
103
|
-
return True
|
|
104
|
-
elif not isinstance(edit, Keep):
|
|
105
|
-
return True
|
|
106
16
|
|
|
107
|
-
|
|
17
|
+
@dataclass
|
|
18
|
+
class BreakingPerformanceTracking:
|
|
19
|
+
lineage_diff_start = None
|
|
20
|
+
lineage_diff_elapsed = None
|
|
21
|
+
modified_nodes = 0
|
|
22
|
+
sqlglot_error_nodes = 0
|
|
23
|
+
other_error_nodes = 0
|
|
24
|
+
checkpoints = {}
|
|
25
|
+
|
|
26
|
+
def start_lineage_diff(self):
|
|
27
|
+
self.lineage_diff_start = time.perf_counter_ns()
|
|
28
|
+
|
|
29
|
+
def record_checkpoint(self, label: str):
|
|
30
|
+
if self.lineage_diff_start is None:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
self.checkpoints[label] = (time.perf_counter_ns() - self.lineage_diff_start) / 1000000
|
|
34
|
+
|
|
35
|
+
def end_lineage_diff(self):
|
|
36
|
+
if self.lineage_diff_start is None:
|
|
37
|
+
return
|
|
38
|
+
self.lineage_diff_elapsed = (time.perf_counter_ns() - self.lineage_diff_start) / 1000000
|
|
39
|
+
|
|
40
|
+
def increment_modified_nodes(self):
|
|
41
|
+
self.modified_nodes += 1
|
|
42
|
+
|
|
43
|
+
def increment_sqlglot_error_nodes(self):
|
|
44
|
+
self.sqlglot_error_nodes += 1
|
|
45
|
+
|
|
46
|
+
def increment_other_error_nodes(self):
|
|
47
|
+
self.other_error_nodes += 1
|
|
48
|
+
|
|
49
|
+
def to_dict(self):
|
|
50
|
+
return {
|
|
51
|
+
'lineage_diff_elapsed_ms': self.lineage_diff_elapsed,
|
|
52
|
+
'modified_nodes': self.modified_nodes,
|
|
53
|
+
'sqlglot_error_nodes': self.sqlglot_error_nodes,
|
|
54
|
+
'other_error_nodes': self.other_error_nodes,
|
|
55
|
+
'checkpoints': self.checkpoints,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def reset(self):
|
|
59
|
+
self.lineage_diff_start = None
|
|
60
|
+
self.lineage_diff_elapsed = None
|
|
61
|
+
self.modified_nodes = 0
|
|
62
|
+
self.sqlglot_error_nodes = 0
|
|
63
|
+
self.other_error_nodes = 0
|
|
64
|
+
self.checkpoints = {}
|
|
108
65
|
|
|
109
66
|
|
|
110
67
|
def _diff_select_scope(
|
|
111
68
|
old_scope: Scope,
|
|
112
69
|
new_scope: Scope,
|
|
113
|
-
scope_changes_map: dict[Scope,
|
|
114
|
-
) ->
|
|
70
|
+
scope_changes_map: dict[Scope, NodeChange]
|
|
71
|
+
) -> NodeChange:
|
|
115
72
|
assert old_scope.expression.key == 'select'
|
|
116
73
|
assert new_scope.expression.key == 'select'
|
|
117
74
|
|
|
118
|
-
result =
|
|
75
|
+
result = NodeChange(category='non_breaking')
|
|
119
76
|
|
|
120
|
-
# check if the upstream scopes is
|
|
121
|
-
if old_scope.sources.keys() != new_scope.sources.keys():
|
|
122
|
-
return CHANGE_CATEGORY_BREAKING
|
|
77
|
+
# check if the upstream scopes is not breaking
|
|
123
78
|
for source_name, source in new_scope.sources.items():
|
|
124
79
|
if scope_changes_map.get(source) is not None:
|
|
125
80
|
change_category = scope_changes_map[source]
|
|
@@ -136,7 +91,7 @@ def _diff_select_scope(
|
|
|
136
91
|
if old_select.args.get(arg_key) != new_select.args.get(arg_key):
|
|
137
92
|
return CHANGE_CATEGORY_BREAKING
|
|
138
93
|
|
|
139
|
-
def source_column_change_status(ref_column: exp.Column) -> Optional[
|
|
94
|
+
def source_column_change_status(ref_column: exp.Column) -> Optional[ChangeStatus]:
|
|
140
95
|
table_name = ref_column.table
|
|
141
96
|
column_name = ref_column.name
|
|
142
97
|
source = new_scope.sources.get(table_name, None) # type: exp.Table | Scope
|
|
@@ -147,7 +102,7 @@ def _diff_select_scope(
|
|
|
147
102
|
if ref_change_category is None:
|
|
148
103
|
return None
|
|
149
104
|
|
|
150
|
-
return ref_change_category.
|
|
105
|
+
return ref_change_category.columns.get(column_name)
|
|
151
106
|
|
|
152
107
|
# selects
|
|
153
108
|
old_column_map = {projection.alias_or_name: projection for projection in old_select.selects}
|
|
@@ -162,6 +117,9 @@ def _diff_select_scope(
|
|
|
162
117
|
def _has_aggregate(expr: exp.Expression) -> bool:
|
|
163
118
|
return expr.find(exp.AggFunc) is not None
|
|
164
119
|
|
|
120
|
+
def _has_star(expr: exp.Expression) -> bool:
|
|
121
|
+
return expr.find(exp.Star) is not None
|
|
122
|
+
|
|
165
123
|
old_column = old_column_map.get(column_name)
|
|
166
124
|
new_column = new_column_map.get(column_name)
|
|
167
125
|
if old_column is None:
|
|
@@ -194,6 +152,17 @@ def _diff_select_scope(
|
|
|
194
152
|
changed_columns[column_name] = 'modified'
|
|
195
153
|
result.category = 'partial_breaking'
|
|
196
154
|
else:
|
|
155
|
+
if _has_star(new_column):
|
|
156
|
+
for source_name, (_, source) in new_scope.selected_sources.items():
|
|
157
|
+
change = scope_changes_map.get(source)
|
|
158
|
+
if change is not None:
|
|
159
|
+
for sub_column_name in change.columns.keys():
|
|
160
|
+
column_change_status = change.columns[sub_column_name]
|
|
161
|
+
changed_columns[sub_column_name] = column_change_status
|
|
162
|
+
if column_change_status in ['removed', 'modified']:
|
|
163
|
+
result.category = 'partial_breaking'
|
|
164
|
+
continue
|
|
165
|
+
|
|
197
166
|
ref_columns = new_column.find_all(exp.Column)
|
|
198
167
|
for ref_column in ref_columns:
|
|
199
168
|
if source_column_change_status(ref_column) is not None:
|
|
@@ -201,11 +170,10 @@ def _diff_select_scope(
|
|
|
201
170
|
return CHANGE_CATEGORY_BREAKING
|
|
202
171
|
if _has_udtf(new_column):
|
|
203
172
|
return CHANGE_CATEGORY_BREAKING
|
|
204
|
-
|
|
205
173
|
result.category = 'partial_breaking'
|
|
206
174
|
changed_columns[column_name] = 'modified'
|
|
207
175
|
|
|
208
|
-
def selected_column_change_status(ref_column: exp.Column) -> Optional[
|
|
176
|
+
def selected_column_change_status(ref_column: exp.Column) -> Optional[ChangeStatus]:
|
|
209
177
|
column_name = ref_column.name
|
|
210
178
|
return changed_columns.get(column_name)
|
|
211
179
|
|
|
@@ -254,15 +222,15 @@ def _diff_select_scope(
|
|
|
254
222
|
if selected_column_change_status(ref_column) is not None:
|
|
255
223
|
return CHANGE_CATEGORY_BREAKING
|
|
256
224
|
|
|
257
|
-
result.
|
|
225
|
+
result.columns = changed_columns
|
|
258
226
|
return result
|
|
259
227
|
|
|
260
228
|
|
|
261
229
|
def _diff_union_scope(
|
|
262
230
|
old_scope: Scope,
|
|
263
231
|
new_scope: Scope,
|
|
264
|
-
scope_changes_map: dict[Scope,
|
|
265
|
-
) ->
|
|
232
|
+
scope_changes_map: dict[Scope, NodeChange]
|
|
233
|
+
) -> NodeChange:
|
|
266
234
|
assert old_scope.expression.key == 'union'
|
|
267
235
|
assert new_scope.expression.key == 'union'
|
|
268
236
|
assert len(old_scope.union_scopes) == len(new_scope.union_scopes)
|
|
@@ -279,8 +247,8 @@ def _diff_union_scope(
|
|
|
279
247
|
return result_right
|
|
280
248
|
if result_right.category == 'partial_breaking':
|
|
281
249
|
result.category = 'partial_breaking'
|
|
282
|
-
for column_name, column_change_status in result_right.
|
|
283
|
-
result.
|
|
250
|
+
for column_name, column_change_status in result_right.columns.items():
|
|
251
|
+
result.columns[column_name] = column_change_status
|
|
284
252
|
|
|
285
253
|
return result
|
|
286
254
|
|
|
@@ -291,10 +259,10 @@ def parse_change_category(
|
|
|
291
259
|
old_schema=None,
|
|
292
260
|
new_schema=None,
|
|
293
261
|
dialect=None,
|
|
294
|
-
|
|
295
|
-
) ->
|
|
262
|
+
perf_tracking: BreakingPerformanceTracking = None,
|
|
263
|
+
) -> NodeChange:
|
|
296
264
|
if old_sql == new_sql:
|
|
297
|
-
return
|
|
265
|
+
return NodeChange(category='non_breaking')
|
|
298
266
|
|
|
299
267
|
try:
|
|
300
268
|
dialect = Dialect.get(dialect)
|
|
@@ -303,20 +271,21 @@ def parse_change_category(
|
|
|
303
271
|
exp = parse_one(sql, dialect=dialect)
|
|
304
272
|
if schema:
|
|
305
273
|
try:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
kwargs["rules"] = optimizer_rules
|
|
309
|
-
exp = optimize(exp, schema=schema, dialect=dialect, **kwargs)
|
|
310
|
-
except Exception as e:
|
|
274
|
+
exp = qualify(exp, schema=schema, dialect=dialect)
|
|
275
|
+
except Exception:
|
|
311
276
|
# cannot optimize, skip it.
|
|
312
|
-
_debug(e)
|
|
313
277
|
pass
|
|
314
278
|
return exp
|
|
315
279
|
|
|
316
280
|
old_exp = _parse(old_sql, old_schema)
|
|
317
281
|
new_exp = _parse(new_sql, new_schema)
|
|
318
|
-
except
|
|
319
|
-
|
|
282
|
+
except SqlglotError:
|
|
283
|
+
if perf_tracking:
|
|
284
|
+
perf_tracking.increment_sqlglot_error_nodes()
|
|
285
|
+
return CHANGE_CATEGORY_UNKNOWN
|
|
286
|
+
except Exception:
|
|
287
|
+
if perf_tracking:
|
|
288
|
+
perf_tracking.increment_other_error_nodes()
|
|
320
289
|
return CHANGE_CATEGORY_UNKNOWN
|
|
321
290
|
|
|
322
291
|
old_scopes = traverse_scope(old_exp)
|
|
@@ -330,7 +299,7 @@ def parse_change_category(
|
|
|
330
299
|
scope_changes_map[new_scope] = CHANGE_CATEGORY_BREAKING
|
|
331
300
|
continue
|
|
332
301
|
if old_scope == new_scope:
|
|
333
|
-
scope_changes_map[new_scope] =
|
|
302
|
+
scope_changes_map[new_scope] = NodeChange(category='non_breaking')
|
|
334
303
|
continue
|
|
335
304
|
|
|
336
305
|
scope_type = old_scope.expression.key
|
|
@@ -344,7 +313,7 @@ def parse_change_category(
|
|
|
344
313
|
if old_scope.expression != new_scope.expression:
|
|
345
314
|
result = CHANGE_CATEGORY_BREAKING
|
|
346
315
|
else:
|
|
347
|
-
result =
|
|
316
|
+
result = NodeChange(category='non_breaking', columns={})
|
|
348
317
|
|
|
349
318
|
if result.category == 'breaking' or result.category == 'unknown':
|
|
350
319
|
return result
|
recce/util/recce_cloud.py
CHANGED
|
@@ -123,6 +123,8 @@ class RecceCloud:
|
|
|
123
123
|
api_url = f'{self.base_url}/recce-state/upload'
|
|
124
124
|
files = {'file': (file_name, file_io, 'application/json')}
|
|
125
125
|
response = self._request('POST', api_url, files=files)
|
|
126
|
+
if response.status_code == 403:
|
|
127
|
+
return {'status': 'error', 'message': response.json().get('detail')}
|
|
126
128
|
if response.status_code != 200:
|
|
127
129
|
raise RecceCloudException(
|
|
128
130
|
message='Failed to share Recce state.',
|